Merge branch 'master' into simple-extensions

This commit is contained in:
Tatsuhiro Tsujikawa 2016-02-07 21:09:08 +09:00
commit fc39f2d9d2
122 changed files with 6971 additions and 3759 deletions

View File

@ -17,7 +17,7 @@ BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BinPackParameters: true
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
DerivePointerAlignment: false
ExperimentalAutoDetectBinPacking: false
IndentCaseLabels: false

View File

@ -1462,3 +1462,16 @@ full real name when contributing!
See `Contribution Guidelines
<https://nghttp2.org/documentation/contribute.html>`_ for more
details.
Release schedule
----------------
In general, we follow `Semantic Versioning <http://semver.org/>`_. We
release MINOR version update every month, and usually we ship it
around 25th day of every month.
We may release PATCH releases between the regular releases, mainly for
severe security bug fixes.
We have no plan to break API compatibility changes involving soname
bump, so MAJOR version will stay 1 for the foreseeable future.

View File

@ -25,7 +25,7 @@ dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.6.1-DEV], [t-tujikawa@users.sourceforge.net])
AC_INIT([nghttp2], [1.7.1-DEV], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
@ -47,7 +47,7 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 18)
AC_SUBST(LT_REVISION, 0)
AC_SUBST(LT_REVISION, 1)
AC_SUBST(LT_AGE, 4)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
@ -189,10 +189,20 @@ else
d])
fi
save_CXXFLAGS="$CXXFLAGS"
CXXFLAGS=
AX_CXX_COMPILE_STDCXX_11([noext], [optional])
CXX1XCXXFLAGS="$CXXFLAGS"
CXXFLAGS="$save_CXXFLAGS"
AC_SUBST([CXX1XCXXFLAGS])
AC_LANG_PUSH(C++)
save_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="$CXXFLAGS $CXX1XCXXFLAGS"
# Check that std::future is available.
AC_MSG_CHECKING([whether std::future is available])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
@ -226,6 +236,8 @@ std::map<int, int>().emplace(1, 2);
[have_std_map_emplace=no
AC_MSG_RESULT([no])])
CXXFLAGS=$save_CXXFLAGS
AC_LANG_POP()
# Checks for libraries.
@ -256,9 +268,16 @@ if test "x${have_zlib}" = "xno"; then
fi
# dl: openssl requires libdl when it is statically linked.
LIBS_OLD=$LIBS
AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], [])
LIBS=$LIBS_OLD
case "${host_os}" in
*bsd*)
# dlopen is in libc on *BSD
;;
*)
save_LIBS=$LIBS
AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], [])
LIBS=$save_LIBS
;;
esac
# cunit
PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no])
@ -291,7 +310,7 @@ AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ])
# libev (for src)
# libev does not have pkg-config file. Check it in an old way.
LIBS_OLD=$LIBS
save_LIBS=$LIBS
# android requires -lm for floor
AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no], [-lm])
if test "x${have_libev}" = "xyes"; then
@ -303,7 +322,7 @@ if test "x${have_libev}" = "xyes"; then
AC_SUBST([LIBEV_CFLAGS])
fi
fi
LIBS=$LIBS_OLD
LIBS=$save_LIBS
# openssl (for src)
PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1],
@ -349,10 +368,9 @@ AM_CONDITIONAL([HAVE_LIBXML2], [ test "x${have_libxml2}" = "xyes" ])
# jemalloc
have_jemalloc=no
if test "x${request_jemalloc}" != "xno"; then
LIBS_OLD=$LIBS
save_LIBS=$LIBS
AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes], [],
[$PTHREAD_LDFLAGS])
LIBS=$LIBS_OLD
if test "x${have_jemalloc}" = "xyes"; then
jemalloc_libs=${ac_cv_search_malloc_stats_print}
@ -360,13 +378,14 @@ if test "x${request_jemalloc}" != "xno"; then
# On Darwin, malloc_stats_print is je_malloc_stats_print
AC_SEARCH_LIBS([je_malloc_stats_print], [jemalloc], [have_jemalloc=yes], [],
[$PTHREAD_LDFLAGS])
LIBS=$LIBS_OLD
if test "x${have_jemalloc}" = "xyes"; then
jemalloc_libs=${ac_cv_search_je_malloc_stats_print}
fi
fi
LIBS=$save_LIBS
if test "x${have_jemalloc}" = "xyes" &&
test "x${jemalloc_libs}" != "xnone required"; then
JEMALLOC_LIBS=${jemalloc_libs}
@ -642,10 +661,14 @@ AM_CONDITIONAL([ENABLE_TINY_NGHTTPD],
[ test "x${have_epoll}" = "xyes" &&
test "x${have_timerfd_create}" = "xyes"])
ac_save_CFLAGS=$CFLAGS
save_CFLAGS=$CFLAGS
save_CXXFLAGS=$CXXFLAGS
CFLAGS=
CXXFLAGS=
if test "x$werror" != "xno"; then
# For C compiler
AX_CHECK_COMPILE_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"])
AX_CHECK_COMPILE_FLAG([-Wextra], [CFLAGS="$CFLAGS -Wextra"])
AX_CHECK_COMPILE_FLAG([-Werror], [CFLAGS="$CFLAGS -Werror"])
@ -691,12 +714,23 @@ if test "x$werror" != "xno"; then
AX_CHECK_COMPILE_FLAG([-Wredundant-decls], [CFLAGS="$CFLAGS -Wredundant-decls"])
# Only work with Clang for the moment
AX_CHECK_COMPILE_FLAG([-Wheader-guard], [CFLAGS="$CFLAGS -Wheader-guard"])
# For C++ compiler
AC_LANG_PUSH(C++)
AX_CHECK_COMPILE_FLAG([-Wall], [CXXFLAGS="$CXXFLAGS -Wall"])
AX_CHECK_COMPILE_FLAG([-Werror], [CXXFLAGS="$CXXFLAGS -Werror"])
AX_CHECK_COMPILE_FLAG([-Wformat-security], [CXXFLAGS="$CXXFLAGS -Wformat-security"])
AC_LANG_POP()
fi
WARNCFLAGS=$CFLAGS
CFLAGS=$ac_save_CFLAGS
WARNCXXFLAGS=$CXXFLAGS
CFLAGS=$save_CFLAGS
CXXFLAGS=$save_CXXFLAGS
AC_SUBST([WARNCFLAGS])
AC_SUBST([WARNCXXFLAGS])
EXTRACFLAG=
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [EXTRACFLAG="-fvisibility=hidden"])
@ -782,6 +816,7 @@ AC_MSG_NOTICE([summary of build options:
C preprocessor: ${CPP}
CPPFLAGS: ${CPPFLAGS}
WARNCFLAGS: ${WARNCFLAGS}
CXX1XCXXFLAGS: ${CXX1XCXXFLAGS}
EXTRACFLAG: ${EXTRACFLAG}
LIBS: ${LIBS}
Library:

View File

@ -8,7 +8,7 @@ _nghttpd()
_get_comp_words_by_ref cur prev
case $cur in
-*)
COMPREPLY=( $( compgen -W '--mime-types-file --error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --dh-param-file --daemon --verify-client --echo-upload --workers --no-tls --color --early-response --max-concurrent-streams ' -- "$cur" ) )
COMPREPLY=( $( compgen -W '--htdocs --verbose --daemon --echo-upload --error-gzip --push --header-table-size --padding --hexdump --max-concurrent-streams --no-tls --mime-types-file --no-content-length --workers --version --color --early-response --dh-param-file --trailer --address --verify-client --help ' -- "$cur" ) )
;;
*)
_filedir

View File

@ -8,7 +8,7 @@ _nghttpx()
_get_comp_words_by_ref cur prev
case $cur in
-*)
COMPREPLY=( $( compgen -W '--worker-read-rate --frontend-no-tls --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --verify-client-cacert --include --backend-request-buffer --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --mruby-file --stream-read-timeout --tls-ticket-key-memcached --accesslog-syslog --frontend-http2-read-timeout --listener-disable-timeout --frontend-http2-connection-window-bits --ciphers --strip-incoming-x-forwarded-for --private-key-passwd-file --backend-keep-alive-timeout --backend-http-proxy-uri --backend-http1-connections-per-host --rlimit-nofile --tls-dyn-rec-warmup-threshold --no-via --ocsp-update-interval --backend-write-timeout --client --tls-ticket-key-memcached-max-retry --http2-no-cookie-crumbling --worker-read-burst --client-proxy --http2-bridge --accesslog-format --errorlog-syslog --errorlog-file --http2-max-concurrent-streams --frontend-write-timeout --tls-ticket-key-cipher --read-burst --backend-ipv4 --backend-ipv6 --backend --insecure --log-level --host-rewrite --tls-proto-list --backend-http2-connections-per-worker --tls-ticket-key-memcached-interval --dh-param-file --worker-frontend-connections --syslog-facility --fastopen --no-location-rewrite --tls-session-cache-memcached --no-ocsp --backend-response-buffer --workers --frontend-http2-window-bits --worker-write-rate --add-request-header --backend-tls-sni-field --subcert --help --frontend-frame-debug --pid-file --frontend-http2-dump-request-header --daemon --write-rate --altsvc --user --add-x-forwarded-for --header-field-buffer --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --no-server-push --backend-http2-window-bits --padding --stream-write-timeout --cacert --version --add-response-header --backend-read-timeout --frontend --accesslog-file --http2-proxy --max-header-fields --backend-no-tls --client-private-key-file --client-cert-file --accept-proxy-protocol --tls-dyn-rec-idle-timeout --verify-client --read-rate ' -- "$cur" ) )
COMPREPLY=( $( compgen -W '--worker-read-rate --frontend-no-tls --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --verify-client-cacert --include --backend-request-buffer --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --mruby-file --stream-read-timeout --tls-ticket-key-memcached --forwarded-for --accesslog-syslog --frontend-http2-read-timeout --listener-disable-timeout --frontend-http2-connection-window-bits --ciphers --strip-incoming-x-forwarded-for --private-key-passwd-file --backend-keep-alive-timeout --backend-http-proxy-uri --backend-http1-connections-per-host --rlimit-nofile --tls-dyn-rec-warmup-threshold --no-via --ocsp-update-interval --backend-write-timeout --client --tls-ticket-key-memcached-max-retry --http2-no-cookie-crumbling --worker-read-burst --client-proxy --http2-bridge --accesslog-format --errorlog-syslog --errorlog-file --http2-max-concurrent-streams --frontend-write-timeout --tls-ticket-key-cipher --read-burst --backend-ipv4 --backend-ipv6 --backend --insecure --log-level --host-rewrite --tls-proto-list --backend-http2-connections-per-worker --tls-ticket-key-memcached-interval --dh-param-file --worker-frontend-connections --syslog-facility --fastopen --no-location-rewrite --tls-session-cache-memcached --no-ocsp --backend-response-buffer --workers --add-forwarded --frontend-http2-window-bits --worker-write-rate --add-request-header --backend-tls-sni-field --subcert --help --frontend-frame-debug --pid-file --frontend-http2-dump-request-header --daemon --write-rate --altsvc --user --add-x-forwarded-for --header-field-buffer --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --no-server-push --backend-http2-window-bits --padding --stream-write-timeout --cacert --forwarded-by --version --add-response-header --backend-read-timeout --frontend --accesslog-file --http2-proxy --max-header-fields --backend-no-tls --client-private-key-file --client-cert-file --accept-proxy-protocol --tls-dyn-rec-idle-timeout --verify-client --read-rate --strip-incoming-forwarded ' -- "$cur" ) )
;;
*)
_filedir

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "H2LOAD" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
.TH "H2LOAD" "1" "January 25, 2016" "1.7.0" "nghttp2"
.SH NAME
h2load \- HTTP/2 benchmarking tool
.
@ -208,11 +208,16 @@ port values.
.UNINDENT
.INDENT 0.0
.TP
.B \-B, \-\-base\-uri=<URI>
.B \-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.
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.
.UNINDENT
.INDENT 0.0
.TP

View File

@ -171,12 +171,17 @@ OPTIONS
Definition of a base URI overrides all scheme, host or
port values.
.. option:: -B, --base-uri=<URI>
.. option:: -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.
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.
.. option:: --npn-list=<LIST>

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTP" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
.TH "NGHTTP" "1" "January 25, 2016" "1.7.0" "nghttp2"
.SH NAME
nghttp \- HTTP/2 client
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPD" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
.TH "NGHTTPD" "1" "January 25, 2016" "1.7.0" "nghttp2"
.SH NAME
nghttpd \- HTTP/2 server
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPX" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
.TH "NGHTTPX" "1" "January 25, 2016" "1.7.0" "nghttp2"
.SH NAME
nghttpx \- HTTP/2 proxy
.
@ -305,7 +305,7 @@ Default: \fB16K\fP
.B \-\-backend\-response\-buffer=<SIZE>
Set buffer size used to store backend response.
.sp
Default: \fB16K\fP
Default: \fB128K\fP
.UNINDENT
.INDENT 0.0
.TP
@ -384,7 +384,7 @@ After accepting connection failed, connection listener
is disabled for a given amount of time. Specifying 0
disables this feature.
.sp
Default: \fB0\fP
Default: \fB30s\fP
.UNINDENT
.SS SSL/TLS
.INDENT 0.0
@ -845,6 +845,51 @@ requests.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-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 \fI\%\-\-forwarded\-by\fP and
\fI\%\-\-forwarded\-for\fP options respectively. Note that nghttpx
does not translate non\-standard X\-Forwarded\-* header
fields into Forwarded header field, and vice versa.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-strip\-incoming\-forwarded
Strip Forwarded header field from inbound client
requests.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-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.
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.
.sp
Default: \fBobfuscated\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-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.
.sp
Default: \fBobfuscated\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-no\-via
Don\(aqt append to Via header field. If Via header field
is received, it is left unaltered.
@ -1010,6 +1055,34 @@ option name with leading \fB\-\-\fP stripped (e.g., \fBfrontend\fP). Put
\fB=\fP between option name and value. Don\(aqt put extra leading or
trailing spaces.
.sp
When specifying arguments including characters which have special
meaning to a shell, we usually use quotes so that shell does not
interpret them. When writing this configuration file, quotes for
this purpose must not be used. For example, specify additional
request header field, do this:
.INDENT 7.0
.INDENT 3.5
.sp
.nf
.ft C
add\-request\-header=foo: bar
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
instead of:
.INDENT 7.0
.INDENT 3.5
.sp
.nf
.ft C
add\-request\-header="foo: bar"
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
The options which do not take argument in the command\-line \fItake\fP
argument in the configuration file. Specify \fByes\fP as an argument
(e.g., \fBhttp2\-proxy=yes\fP). If other string is given, it is

View File

@ -270,7 +270,7 @@ Performance
Set buffer size used to store backend response.
Default: ``16K``
Default: ``128K``
.. option:: --fastopen=<N>
@ -341,7 +341,7 @@ Timeout
is disabled for a given amount of time. Specifying 0
disables this feature.
Default: ``0``
Default: ``30s``
SSL/TLS
@ -751,6 +751,47 @@ HTTP
Strip X-Forwarded-For header field from inbound client
requests.
.. option:: --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 :option:`--forwarded-by` and
:option:`--forwarded-for` options respectively. Note that nghttpx
does not translate non-standard X-Forwarded-\* header
fields into Forwarded header field, and vice versa.
.. option:: --strip-incoming-forwarded
Strip Forwarded header field from inbound client
requests.
.. option:: --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.
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``
.. option:: --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.
Default: ``obfuscated``
.. option:: --no-via
Don't append to Via header field. If Via header field
@ -913,6 +954,22 @@ FILES
``=`` between option name and value. Don't put extra leading or
trailing spaces.
When specifying arguments including characters which have special
meaning to a shell, we usually use quotes so that shell does not
interpret them. When writing this configuration file, quotes for
this purpose must not be used. For example, specify additional
request header field, do this:
.. code-block:: text
add-request-header=foo: bar
instead of:
.. code-block:: text
add-request-header="foo: bar"
The options which do not take argument in the command-line *take*
argument in the configuration file. Specify ``yes`` as an argument
(e.g., ``http2-proxy=yes``). If other string is given, it is

View File

@ -13,6 +13,22 @@ FILES
``=`` between option name and value. Don't put extra leading or
trailing spaces.
When specifying arguments including characters which have special
meaning to a shell, we usually use quotes so that shell does not
interpret them. When writing this configuration file, quotes for
this purpose must not be used. For example, specify additional
request header field, do this:
.. code-block:: text
add-request-header=foo: bar
instead of:
.. code-block:: text
add-request-header="foo: bar"
The options which do not take argument in the command-line *take*
argument in the configuration file. Specify ``yes`` as an argument
(e.g., ``http2-proxy=yes``). If other string is given, it is

View File

@ -14,9 +14,10 @@ If nghttpx is invoked without any ``-s``, ``-p`` and ``--client``, it
operates in default mode. In this mode, nghttpx frontend listens for
HTTP/2 requests and translates them to HTTP/1 requests. Thus it works
as reverse proxy (gateway) for HTTP/2 clients to HTTP/1 web server.
HTTP/1 requests are also supported in frontend as a fallback. If
nghttpx is linked with spdylay library and frontend connection is
SSL/TLS, the frontend also supports SPDY protocol.
This is also known as "HTTP/2 router". HTTP/1 requests are also
supported in frontend as a fallback. If nghttpx is linked with
spdylay library and frontend connection is SSL/TLS, the frontend also
supports SPDY protocol.
By default, this mode's frontend connection is encrypted using
SSL/TLS. So server's private key and certificate must be supplied to
@ -30,6 +31,10 @@ available on the frontend and a HTTP/1 connection can be upgraded to
HTTP/2 using HTTP Upgrade. Starting HTTP/2 connection by sending
HTTP/2 connection preface is also supported.
By default, backend HTTP/1 connections are not encrypted. To enable
TLS on HTTP/1 backend connections, use ``--backend-http1-tls`` option.
This applies to all mode whose backend connections are HTTP/1.
The backend is supposed to be HTTP/1 Web server. For example, to make
nghttpx listen to encrypted HTTP/2 requests at port 8443, and a
backend HTTP/1 web server is configured to listen to HTTP/1 request at

View File

@ -24,8 +24,8 @@
if ENABLE_EXAMPLES
AM_CFLAGS = $(WARNCFLAGS)
AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
AM_CPPFLAGS = \
-Wall \
-I$(top_srcdir)/lib/includes \
-I$(top_builddir)/lib/includes \
-I$(top_srcdir)/src/includes \

32
genauthoritychartbl.py Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env python
import sys
def name(i):
if i < 0x21:
return \
['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ',
'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ',
'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ',
'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ',
'SPC '][i]
elif i == 0x7f:
return 'DEL '
for i in range(256):
if chr(i) in [
"-", ".", "_", "~",
"!", "$", "&", "'", "(", ")",
"*", "+", ",", ";", "=",
"%", "@", ":", "[", "]"] or\
('0' <= chr(i) and chr(i) <= '9') or \
('A' <= chr(i) and chr(i) <= 'Z') or \
('a' <= chr(i) and chr(i) <= 'z'):
sys.stdout.write('1 /* {} */, '.format(chr(i)))
elif (0x21 <= i and i < 0x7f):
sys.stdout.write('0 /* {} */, '.format(chr(i)))
elif 0x80 <= i:
sys.stdout.write('0 /* {} */, '.format(hex(i)))
else:
sys.stdout.write('0 /* {} */, '.format(name(i)))
if (i + 1)%4 == 0:
sys.stdout.write('\n')

View File

@ -17,6 +17,7 @@ HEADERS = [
"http2-settings",
"server",
"via",
"forwarded",
"x-forwarded-for",
"x-forwarded-proto",
"alt-svc",
@ -29,6 +30,7 @@ HEADERS = [
"cache-control",
"user-agent",
"date",
"content-type",
# disallowed h1 headers
'connection',
'keep-alive',

View File

@ -88,8 +88,6 @@ OPTIONS = [
"fetch-ocsp-response-file",
"ocsp-update-interval",
"no-ocsp",
"header-field-buffer",
"max-header-fields",
"include",
"tls-ticket-key-cipher",
"host-rewrite",
@ -103,7 +101,20 @@ OPTIONS = [
"conf",
"fastopen",
"tls-dyn-rec-warmup-threshold",
"tls-dyn-rec-idle-timeout"
"tls-dyn-rec-idle-timeout",
"add-forwarded",
"strip-incoming-forwarded",
"forwarded-by",
"forwarded-for",
"response-header-field-buffer",
"max-response-header-fields",
"request-header-field-buffer",
"max-request-header-fields",
"header-field-buffer",
"max-header-fields",
"no-http2-cipher-black-list",
"backend-http1-tls",
"backend-tls-session-cache-per-worker"
]
LOGVARS = [

View File

@ -185,6 +185,66 @@ func TestH1H1HostRewrite(t *testing.T) {
}
}
// TestH1H1BadHost tests that server rejects request including bad
// characters in host header field.
func TestH1H1BadHost(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()
if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H1HBadHost\r\nHost: foo\"bar\r\n\r\n"); err != nil {
t.Fatalf("Error io.WriteString() = %v", err)
}
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
if err != nil {
t.Fatalf("Error http.ReadResponse() = %v", err)
}
if got, want := resp.StatusCode, 400; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH1H1BadAuthority tests that server rejects request including
// bad characters in authority component of requset URI.
func TestH1H1BadAuthority(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()
if _, err := io.WriteString(st.conn, "GET http://foo\"bar/ HTTP/1.1\r\nTest-Case: TestH1H1HBadAuthority\r\nHost: foobar\r\n\r\n"); err != nil {
t.Fatalf("Error io.WriteString() = %v", err)
}
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
if err != nil {
t.Fatalf("Error http.ReadResponse() = %v", err)
}
if got, want := resp.StatusCode, 400; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH1H1BadScheme tests that server rejects request including
// bad characters in scheme component of requset URI.
func TestH1H1BadScheme(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()
if _, err := io.WriteString(st.conn, "GET http*://example.com/ HTTP/1.1\r\nTest-Case: TestH1H1HBadScheme\r\nHost: example.com\r\n\r\n"); err != nil {
t.Fatalf("Error io.WriteString() = %v", err)
}
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
if err != nil {
t.Fatalf("Error http.ReadResponse() = %v", err)
}
if got, want := resp.StatusCode, 400; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH1H1HTTP10 tests that server can accept HTTP/1.0 request
// without Host header field
func TestH1H1HTTP10(t *testing.T) {

View File

@ -8,6 +8,7 @@ import (
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
"syscall"
"testing"
@ -44,12 +45,15 @@ func TestH2H1AddXff(t *testing.T) {
})
defer st.Close()
_, err := st.http2(requestParam{
res, err := st.http2(requestParam{
name: "TestH2H1AddXff",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H1AddXff2 tests that server appends X-Forwarded-For header
@ -64,7 +68,7 @@ func TestH2H1AddXff2(t *testing.T) {
})
defer st.Close()
_, err := st.http2(requestParam{
res, err := st.http2(requestParam{
name: "TestH2H1AddXff2",
header: []hpack.HeaderField{
pair("x-forwarded-for", "host"),
@ -73,6 +77,9 @@ func TestH2H1AddXff2(t *testing.T) {
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H1StripXff tests that --strip-incoming-x-forwarded-for
@ -85,8 +92,8 @@ func TestH2H1StripXff(t *testing.T) {
})
defer st.Close()
_, err := st.http2(requestParam{
name: "TestH2H1StripXff1",
res, err := st.http2(requestParam{
name: "TestH2H1StripXff",
header: []hpack.HeaderField{
pair("x-forwarded-for", "host"),
},
@ -94,6 +101,9 @@ func TestH2H1StripXff(t *testing.T) {
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H1StripAddXff tests that --strip-incoming-x-forwarded-for and
@ -112,7 +122,7 @@ func TestH2H1StripAddXff(t *testing.T) {
})
defer st.Close()
_, err := st.http2(requestParam{
res, err := st.http2(requestParam{
name: "TestH2H1StripAddXff",
header: []hpack.HeaderField{
pair("x-forwarded-for", "host"),
@ -121,6 +131,177 @@ func TestH2H1StripAddXff(t *testing.T) {
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H1AddForwardedObfuscated tests that server generates
// Forwarded header field with obfuscated "by" and "for" parameters.
func TestH2H1AddForwardedObfuscated(t *testing.T) {
st := newServerTester([]string{"--add-forwarded=by,for,host,proto"}, t, func(w http.ResponseWriter, r *http.Request) {
pattern := fmt.Sprintf(`by=_[^;]+;for=_[^;]+;host="127\.0\.0\.1:%v";proto=http`, serverPort)
validFwd := regexp.MustCompile(pattern)
got := r.Header.Get("Forwarded")
if !validFwd.MatchString(got) {
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1AddForwardedObfuscated",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1AddForwardedByIP tests that server generates Forwarded header
// field with IP address in "by" parameter.
func TestH2H1AddForwardedByIP(t *testing.T) {
st := newServerTester([]string{"--add-forwarded=by,for", "--forwarded-by=ip"}, t, func(w http.ResponseWriter, r *http.Request) {
pattern := fmt.Sprintf(`by="127\.0\.0\.1:%v";for=_[^;]+`, serverPort)
validFwd := regexp.MustCompile(pattern)
if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1AddForwardedByIP",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1AddForwardedForIP tests that server generates Forwarded header
// field with IP address in "for" parameters.
func TestH2H1AddForwardedForIP(t *testing.T) {
st := newServerTester([]string{"--add-forwarded=by,for,host,proto", "--forwarded-by=_alpha", "--forwarded-for=ip"}, t, func(w http.ResponseWriter, r *http.Request) {
want := fmt.Sprintf(`by=_alpha;for=127.0.0.1;host="127.0.0.1:%v";proto=http`, serverPort)
if got := r.Header.Get("Forwarded"); got != want {
t.Errorf("Forwarded = %v; want %v", got, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1AddForwardedForIP",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1AddForwardedMerge tests that server generates Forwarded
// header field with IP address in "by" and "for" parameters. The
// generated values must be appended to the existing value.
func TestH2H1AddForwardedMerge(t *testing.T) {
st := newServerTester([]string{"--add-forwarded=proto"}, t, func(w http.ResponseWriter, r *http.Request) {
if got, want := r.Header.Get("Forwarded"), `host=foo, proto=http`; got != want {
t.Errorf("Forwarded = %v; want %v", got, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1AddForwardedMerge",
header: []hpack.HeaderField{
pair("forwarded", "host=foo"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1AddForwardedStrip tests that server generates Forwarded
// header field with IP address in "by" and "for" parameters. The
// generated values must not include the existing value.
func TestH2H1AddForwardedStrip(t *testing.T) {
st := newServerTester([]string{"--strip-incoming-forwarded", "--add-forwarded=proto"}, t, func(w http.ResponseWriter, r *http.Request) {
if got, want := r.Header.Get("Forwarded"), `proto=http`; got != want {
t.Errorf("Forwarded = %v; want %v", got, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1AddForwardedStrip",
header: []hpack.HeaderField{
pair("forwarded", "host=foo"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1StripForwarded tests that server strips incoming Forwarded
// header field.
func TestH2H1StripForwarded(t *testing.T) {
st := newServerTester([]string{"--strip-incoming-forwarded"}, t, func(w http.ResponseWriter, r *http.Request) {
if got, found := r.Header["Forwarded"]; found {
t.Errorf("Forwarded = %v; want nothing", got)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1StripForwarded",
header: []hpack.HeaderField{
pair("forwarded", "host=foo"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1AddForwardedStatic tests that server generates Forwarded
// header field with the given static obfuscated string for "by"
// parameter.
func TestH2H1AddForwardedStatic(t *testing.T) {
st := newServerTester([]string{"--add-forwarded=by,for", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
pattern := `by=_alpha;for=_[^;]+`
validFwd := regexp.MustCompile(pattern)
if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1AddForwardedStatic",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1GenerateVia tests that server generates Via header field to and
@ -329,7 +510,7 @@ func TestH2H1ChunkedRequestBody(t *testing.T) {
})
defer st.Close()
_, err := st.http2(requestParam{
res, err := st.http2(requestParam{
name: "TestH2H1ChunkedRequestBody",
method: "POST",
body: []byte("foo"),
@ -337,6 +518,9 @@ func TestH2H1ChunkedRequestBody(t *testing.T) {
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H1MultipleRequestCL tests that server rejects request with
@ -425,6 +609,46 @@ func TestH2H1InvalidMethod(t *testing.T) {
}
}
// TestH2H1BadAuthority tests that server rejects request including
// bad characters in :authority header field.
func TestH2H1BadAuthority(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1BadAuthority",
authority: `foo\bar`,
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
t.Errorf("res.errCode: %v; want %v", got, want)
}
}
// TestH2H1BadScheme tests that server rejects request including
// bad characters in :scheme header field.
func TestH2H1BadScheme(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1BadScheme",
scheme: "http*",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
t.Errorf("res.errCode: %v; want %v", got, want)
}
}
// TestH2H1AssembleCookies tests that crumbled cookies in HTTP/2
// request is assembled into 1 when forwarding to HTTP/1 backend link.
func TestH2H1AssembleCookies(t *testing.T) {
@ -1348,6 +1572,208 @@ func TestH2H2TLSXfp(t *testing.T) {
}
}
// TestH2H2AddXff tests that server generates X-Forwarded-For header
// field when forwarding request to backend.
func TestH2H2AddXff(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
xff := r.Header.Get("X-Forwarded-For")
want := "127.0.0.1"
if xff != want {
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2AddXff",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H2AddXff2 tests that server appends X-Forwarded-For header
// field to existing one when forwarding request to backend.
func TestH2H2AddXff2(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
xff := r.Header.Get("X-Forwarded-For")
want := "host, 127.0.0.1"
if xff != want {
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2AddXff2",
header: []hpack.HeaderField{
pair("x-forwarded-for", "host"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H2StripXff tests that --strip-incoming-x-forwarded-for
// option.
func TestH2H2StripXff(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
if xff, found := r.Header["X-Forwarded-For"]; found {
t.Errorf("X-Forwarded-For = %v; want nothing", xff)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2StripXff",
header: []hpack.HeaderField{
pair("x-forwarded-for", "host"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H2StripAddXff tests that --strip-incoming-x-forwarded-for and
// --add-x-forwarded-for options.
func TestH2H2StripAddXff(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-x-forwarded-for", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
xff := r.Header.Get("X-Forwarded-For")
want := "127.0.0.1"
if xff != want {
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2StripAddXff",
header: []hpack.HeaderField{
pair("x-forwarded-for", "host"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status = %v; want %v", got, want)
}
}
// TestH2H2AddForwarded tests that server generates Forwarded header
// field using static obfuscated "by" parameter.
func TestH2H2AddForwarded(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--add-forwarded=by,for,host,proto", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
pattern := fmt.Sprintf(`by=_alpha;for=_[^;]+;host="127\.0\.0\.1:%v";proto=https`, serverPort)
validFwd := regexp.MustCompile(pattern)
if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2AddForwarded",
scheme: "https",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H2AddForwardedMerge tests that server generates Forwarded
// header field using static obfuscated "by" parameter, and
// existing Forwarded header field.
func TestH2H2AddForwardedMerge(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--add-forwarded=by,host,proto", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
want := fmt.Sprintf(`host=foo, by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort)
if got := r.Header.Get("Forwarded"); got != want {
t.Errorf("Forwarded = %v; want %v", got, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2AddForwardedMerge",
scheme: "https",
header: []hpack.HeaderField{
pair("forwarded", "host=foo"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H2AddForwardedStrip tests that server generates Forwarded
// header field using static obfuscated "by" parameter, and
// existing Forwarded header field stripped.
func TestH2H2AddForwardedStrip(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-forwarded", "--add-forwarded=by,host,proto", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
want := fmt.Sprintf(`by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort)
if got := r.Header.Get("Forwarded"); got != want {
t.Errorf("Forwarded = %v; want %v", got, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2AddForwardedStrip",
scheme: "https",
header: []hpack.HeaderField{
pair("forwarded", "host=foo"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H2StripForwarded tests that server strips incoming Forwarded
// header field.
func TestH2H2StripForwarded(t *testing.T) {
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-forwarded"}, t, func(w http.ResponseWriter, r *http.Request) {
if got, found := r.Header["Forwarded"]; found {
t.Errorf("Forwarded = %v; want nothing", got)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H2StripForwarded",
scheme: "https",
header: []hpack.HeaderField{
pair("forwarded", "host=foo"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H2ReqPhaseReturn tests mruby request phase hook returns
// custom response.
func TestH2H2ReqPhaseReturn(t *testing.T) {

View File

@ -1,8 +1,8 @@
package nghttp2
import (
"golang.org/x/net/http2/hpack"
"github.com/tatsuhiro-t/spdy"
"golang.org/x/net/http2/hpack"
"net/http"
"testing"
)
@ -230,6 +230,46 @@ func TestS3H1InvalidMethod(t *testing.T) {
}
}
// TestS3H1BadHost tests that server rejects request including bad
// character in :host header field.
func TestS3H1BadHost(t *testing.T) {
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()
res, err := st.spdy(requestParam{
name: "TestS3H1BadHost",
authority: `foo\bar`,
})
if err != nil {
t.Fatalf("Error st.spdy() = %v", err)
}
if got, want := res.status, 400; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestS3H1BadScheme tests that server rejects request including bad
// character in :scheme header field.
func TestS3H1BadScheme(t *testing.T) {
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()
res, err := st.spdy(requestParam{
name: "TestS3H1BadScheme",
scheme: `http*`,
})
if err != nil {
t.Fatalf("Error st.spdy() = %v", err)
}
if got, want := res.status, 400; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestS3H1ReqPhaseSetHeader tests mruby request phase hook
// modifies request header fields.
func TestS3H1ReqPhaseSetHeader(t *testing.T) {

View File

@ -15,7 +15,7 @@ THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST))
USE_CYTHON := 1
#USE_CYTHON := 0
_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV], //g')
_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -r -e 's/(-DEV)?], //g')
_VERSION := $(subst ., ,$(_VERSION))
VER_MAJOR := $(word 1,$(_VERSION))
VER_MINOR := $(word 2,$(_VERSION))

View File

@ -752,7 +752,7 @@ typedef enum {
/**
* Indicates that END_STREAM flag must not be set even if
* NGHTTP2_DATA_FLAG_EOF is set. Usually this flag is used to send
* trailer header fields with `nghttp2_submit_request()` or
* trailer fields with `nghttp2_submit_request()` or
* `nghttp2_submit_response()`.
*/
NGHTTP2_DATA_FLAG_NO_END_STREAM = 0x02,
@ -788,13 +788,13 @@ typedef enum {
* :enum:`NGHTTP2_FLAG_END_STREAM` set, and
* :enum:`NGHTTP2_DATA_FLAG_EOF` flag is set to |*data_flags|, DATA
* frame will have END_STREAM flag set. Usually, this is expected
* behaviour and all are fine. One exception is send trailer header
* fields. You cannot send trailers after sending frame with
* END_STREAM set. To avoid this problem, one can set
* behaviour and all are fine. One exception is send trailer fields.
* You cannot send trailer fields after sending frame with END_STREAM
* set. To avoid this problem, one can set
* :enum:`NGHTTP2_DATA_FLAG_NO_END_STREAM` along with
* :enum:`NGHTTP2_DATA_FLAG_EOF` to signal the library not to set
* END_STREAM in DATA frame. Then application can use
* `nghttp2_submit_trailer()` to send trailers.
* `nghttp2_submit_trailer()` to send trailer fields.
* `nghttp2_submit_trailer()` can be called inside this callback.
*
* If the application wants to postpone DATA frames (e.g.,
@ -853,9 +853,9 @@ typedef struct {
*
* The category of HEADERS, which indicates the role of the frame. In
* HTTP/2 spec, request, response, push response and other arbitrary
* headers (e.g., trailers) are all called just HEADERS. To give the
* application the role of incoming HEADERS frame, we define several
* categories.
* headers (e.g., trailer fields) are all called just HEADERS. To
* give the application the role of incoming HEADERS frame, we define
* several categories.
*/
typedef enum {
/**
@ -1503,7 +1503,7 @@ typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session,
* NGHTTP2_HCAT_REQUEST``. If |session| is configured as server side,
* ``frame->headers.cat`` is either ``NGHTTP2_HCAT_REQUEST``
* containing request headers or ``NGHTTP2_HCAT_HEADERS`` containing
* trailer headers and never get PUSH_PROMISE in this callback.
* trailer fields and never get PUSH_PROMISE in this callback.
*
* For the client applications, ``frame->hd.type`` is either
* ``NGHTTP2_HEADERS`` or ``NGHTTP2_PUSH_PROMISE``. In case of
@ -1515,7 +1515,7 @@ typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session,
* non-final response code and finally client gets exactly one HEADERS
* frame with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS``
* containing final response headers (non-1xx status code). The
* trailer headers also has ``frame->headers.cat ==
* trailer fields also has ``frame->headers.cat ==
* NGHTTP2_HCAT_HEADERS`` which does not contain any status code.
*
* Returning :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will close
@ -3390,7 +3390,7 @@ nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
/**
* @function
*
* Submits trailer HEADERS against the stream |stream_id|.
* Submits trailer fields HEADERS against the stream |stream_id|.
*
* The |nva| is an array of name/value pair :type:`nghttp2_nv` with
* |nvlen| elements. The application is responsible not to include
@ -3409,26 +3409,26 @@ nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
* :type:`nghttp2_on_frame_send_callback` or
* :type:`nghttp2_on_frame_not_send_callback` is called.
*
* For server, trailer must be followed by response HEADERS or
* response DATA. The library does not check that response HEADERS
* has already sent and if `nghttp2_submit_trailer()` is called before
* any response HEADERS submission (usually by
* `nghttp2_submit_response()`), the content of |nva| will be sent as
* response headers, which will result in error.
* For server, trailer fields must follow response HEADERS or response
* DATA with END_STREAM flag set. The library does not enforce this
* requirement, and applications should do this for themselves. If
* `nghttp2_submit_trailer()` is called before any response HEADERS
* submission (usually by `nghttp2_submit_response()`), the content of
* |nva| will be sent as response headers, which will result in error.
*
* This function has the same effect with `nghttp2_submit_headers()`,
* with flags = :enum:`NGHTTP2_FLAG_END_HEADERS` and both pri_spec and
* stream_user_data to NULL.
*
* To submit trailer after `nghttp2_submit_response()` is called, the
* application has to specify :type:`nghttp2_data_provider` to
* `nghttp2_submit_response()`. In side
* :type:`nghttp2_data_source_read_callback`, when setting
* To submit trailer fields after `nghttp2_submit_response()` is
* called, the application has to specify
* :type:`nghttp2_data_provider` to `nghttp2_submit_response()`. In
* side :type:`nghttp2_data_source_read_callback`, when setting
* :enum:`NGHTTP2_DATA_FLAG_EOF`, also set
* :enum:`NGHTTP2_DATA_FLAG_NO_END_STREAM`. After that, the
* application can send trailer using `nghttp2_submit_trailer()`.
* `nghttp2_submit_trailer()` can be used inside
* :type:`nghttp2_data_source_read_callback`.
* application can send trailer fields using
* `nghttp2_submit_trailer()`. `nghttp2_submit_trailer()` can be used
* inside :type:`nghttp2_data_source_read_callback`.
*
* This function returns 0 if it succeeds and |stream_id| is -1.
* Otherwise, this function returns 0 if it succeeds, or one of the

View File

@ -284,9 +284,101 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
return 0;
}
/* Generated by genauthroitychartbl.py */
static char VALID_AUTHORITY_CHARS[] = {
0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, 0 /* EOT */,
0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, 0 /* BS */, 0 /* HT */,
0 /* LF */, 0 /* VT */, 0 /* FF */, 0 /* CR */, 0 /* SO */,
0 /* SI */, 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */,
0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, 0 /* CAN */,
0 /* EM */, 0 /* SUB */, 0 /* ESC */, 0 /* FS */, 0 /* GS */,
0 /* RS */, 0 /* US */, 0 /* SPC */, 1 /* ! */, 0 /* " */,
0 /* # */, 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */,
1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */, 1 /* , */,
1 /* - */, 1 /* . */, 0 /* / */, 1 /* 0 */, 1 /* 1 */,
1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */,
1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */,
0 /* < */, 1 /* = */, 0 /* > */, 0 /* ? */, 1 /* @ */,
1 /* A */, 1 /* B */, 1 /* C */, 1 /* D */, 1 /* E */,
1 /* F */, 1 /* G */, 1 /* H */, 1 /* I */, 1 /* J */,
1 /* K */, 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */,
1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, 1 /* T */,
1 /* U */, 1 /* V */, 1 /* W */, 1 /* X */, 1 /* Y */,
1 /* Z */, 1 /* [ */, 0 /* \ */, 1 /* ] */, 0 /* ^ */,
1 /* _ */, 0 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */,
1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, 1 /* h */,
1 /* i */, 1 /* j */, 1 /* k */, 1 /* l */, 1 /* m */,
1 /* n */, 1 /* o */, 1 /* p */, 1 /* q */, 1 /* r */,
1 /* s */, 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */,
1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, 0 /* | */,
0 /* } */, 1 /* ~ */, 0 /* DEL */, 0 /* 0x80 */, 0 /* 0x81 */,
0 /* 0x82 */, 0 /* 0x83 */, 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */,
0 /* 0x87 */, 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */,
0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, 0 /* 0x90 */,
0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, 0 /* 0x94 */, 0 /* 0x95 */,
0 /* 0x96 */, 0 /* 0x97 */, 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */,
0 /* 0x9b */, 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */,
0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, 0 /* 0xa4 */,
0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, 0 /* 0xa8 */, 0 /* 0xa9 */,
0 /* 0xaa */, 0 /* 0xab */, 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */,
0 /* 0xaf */, 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */,
0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, 0 /* 0xb8 */,
0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, 0 /* 0xbc */, 0 /* 0xbd */,
0 /* 0xbe */, 0 /* 0xbf */, 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */,
0 /* 0xc3 */, 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */,
0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, 0 /* 0xcc */,
0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, 0 /* 0xd0 */, 0 /* 0xd1 */,
0 /* 0xd2 */, 0 /* 0xd3 */, 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */,
0 /* 0xd7 */, 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */,
0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, 0 /* 0xe0 */,
0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */,
0 /* 0xe6 */, 0 /* 0xe7 */, 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */,
0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */,
0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, 0 /* 0xf4 */,
0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, 0 /* 0xf8 */, 0 /* 0xf9 */,
0 /* 0xfa */, 0 /* 0xfb */, 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */,
0 /* 0xff */
};
static int check_authority(const uint8_t *value, size_t len) {
const uint8_t *last;
for (last = value + len; value != last; ++value) {
if (!VALID_AUTHORITY_CHARS[*value]) {
return 0;
}
}
return 1;
}
static int check_scheme(const uint8_t *value, size_t len) {
const uint8_t *last;
if (len == 0) {
return 0;
}
if (!(('A' <= *value && *value <= 'Z') || ('a' <= *value && *value <= 'z'))) {
return 0;
}
last = value + len;
++value;
for (; value != last; ++value) {
if (!(('A' <= *value && *value <= 'Z') ||
('a' <= *value && *value <= 'z') ||
('0' <= *value && *value <= '9') || *value == '+' || *value == '-' ||
*value == '.')) {
return 0;
}
}
return 1;
}
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
nghttp2_frame *frame, nghttp2_nv *nv, int token,
int trailer) {
int rv;
/* We are strict for pseudo header field. One bad character should
lead to fail. OTOH, we should be a bit forgiving for regular
headers, since existing public internet has so much illegal
@ -313,7 +405,15 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
return NGHTTP2_ERR_IGN_HTTP_HEADER;
}
if (!nghttp2_check_header_value(nv->value, nv->valuelen)) {
if (token == NGHTTP2_TOKEN__AUTHORITY || token == NGHTTP2_TOKEN_HOST) {
rv = check_authority(nv->value, nv->valuelen);
} else if (token == NGHTTP2_TOKEN__SCHEME) {
rv = check_scheme(nv->value, nv->valuelen);
} else {
rv = nghttp2_check_header_value(nv->value, nv->valuelen);
}
if (rv == 0) {
assert(nv->namelen > 0);
if (nv->name[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER;

View File

@ -130,7 +130,7 @@ static int session_detect_idle_stream(nghttp2_session *session,
int32_t stream_id) {
/* Assume that stream object with stream_id does not exist */
if (nghttp2_session_is_my_stream_id(session, stream_id)) {
if (session->sent_stream_id < stream_id) {
if (session->last_sent_stream_id < stream_id) {
return 1;
}
return 0;
@ -1296,7 +1296,9 @@ static int session_allow_incoming_new_stream(nghttp2_session *session) {
* This function returns nonzero if session is closing.
*/
static int session_is_closing(nghttp2_session *session) {
return (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) != 0;
return (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) != 0 ||
(nghttp2_session_want_read(session) == 0 &&
nghttp2_session_want_write(session) == 0);
}
/*
@ -1327,8 +1329,8 @@ static int session_predicate_for_stream_send(nghttp2_session *session,
int nghttp2_session_check_request_allowed(nghttp2_session *session) {
return !session->server && session->next_stream_id <= INT32_MAX &&
(session->goaway_flags &
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) == 0;
(session->goaway_flags & NGHTTP2_GOAWAY_RECV) == 0 &&
!session_is_closing(session);
}
/*
@ -1350,10 +1352,11 @@ static int session_predicate_request_headers_send(nghttp2_session *session,
if (item->aux_data.headers.canceled) {
return NGHTTP2_ERR_STREAM_CLOSING;
}
/* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND) or
GOAWAY was received from peer, new request is not allowed. */
if (session->goaway_flags &
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) {
/* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND),
GOAWAY was received from peer, or session is about to close, new
request is not allowed. */
if ((session->goaway_flags & NGHTTP2_GOAWAY_RECV) ||
session_is_closing(session)) {
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
}
return 0;
@ -1912,8 +1915,8 @@ static int session_prep_frame(nghttp2_session *session,
nghttp2_bufs_len(&session->aob.framebufs)));
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
assert(session->sent_stream_id < frame->hd.stream_id);
session->sent_stream_id = frame->hd.stream_id;
assert(session->last_sent_stream_id < frame->hd.stream_id);
session->last_sent_stream_id = frame->hd.stream_id;
}
break;
@ -1943,6 +1946,13 @@ static int session_prep_frame(nghttp2_session *session,
if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
assert(session->obq_flood_counter_ > 0);
--session->obq_flood_counter_;
/* When session is about to close, don't send SETTINGS ACK.
We are required to send SETTINGS without ACK though; for
example, we have to send SETTINGS as a part of connection
preface. */
if (session_is_closing(session)) {
return NGHTTP2_ERR_SESSION_CLOSING;
}
}
rv = nghttp2_frame_pack_settings(&session->aob.framebufs,
@ -1985,9 +1995,9 @@ static int session_prep_frame(nghttp2_session *session,
return rv;
}
assert(session->sent_stream_id + 2 <=
assert(session->last_sent_stream_id + 2 <=
frame->push_promise.promised_stream_id);
session->sent_stream_id = frame->push_promise.promised_stream_id;
session->last_sent_stream_id = frame->push_promise.promised_stream_id;
break;
}
@ -3870,8 +3880,8 @@ static int update_remote_initial_window_size_func(nghttp2_map_entry *entry,
rv = nghttp2_stream_update_remote_initial_window_size(
stream, arg->new_window_size, arg->old_window_size);
if (rv != 0) {
return nghttp2_session_terminate_session(arg->session,
NGHTTP2_FLOW_CONTROL_ERROR);
return nghttp2_session_add_rst_stream(arg->session, stream->stream_id,
NGHTTP2_FLOW_CONTROL_ERROR);
}
/* If window size gets positive, push deferred DATA frame to
@ -3922,8 +3932,8 @@ static int update_local_initial_window_size_func(nghttp2_map_entry *entry,
rv = nghttp2_stream_update_local_initial_window_size(
stream, arg->new_window_size, arg->old_window_size);
if (rv != 0) {
return nghttp2_session_terminate_session(arg->session,
NGHTTP2_FLOW_CONTROL_ERROR);
return nghttp2_session_add_rst_stream(arg->session, stream->stream_id,
NGHTTP2_FLOW_CONTROL_ERROR);
}
if (!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) &&
stream->window_update_queued == 0 &&
@ -6726,6 +6736,7 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session,
session->last_proc_stream_id = 1;
} else {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
session->last_sent_stream_id = 1;
session->next_stream_id += 2;
}
return 0;

View File

@ -250,7 +250,7 @@ struct nghttp2_session {
/* The last stream ID this session initiated. For client session,
this is the last stream ID it has sent. For server session, it
is the last promised stream ID sent in PUSH_PROMISE. */
int32_t sent_stream_id;
int32_t last_sent_stream_id;
/* The largest stream ID received so far */
int32_t last_recv_stream_id;
/* The largest stream ID which has been processed in some way. This

View File

@ -55,10 +55,10 @@
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
#serial 3
#serial 4
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
@ -67,7 +67,7 @@ AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
[AS_VAR_SET(CACHEVAR,[yes])],
[AS_VAR_SET(CACHEVAR,[no])])
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
AS_VAR_IF(CACHEVAR,yes,
[m4_default([$2], :)],
[m4_default([$3], :)])
AS_VAR_POPDEF([CACHEVAR])dnl

5
releasechk Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh -e
git submodule update --init
./configure --with-mruby --with-neverbleed --enable-asio-lib
make -j3 distcheck DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-asio-lib --enable-werror"

View File

@ -101,12 +101,26 @@ template <typename Array> void append_nv(Stream *stream, const Array &nva) {
} // 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), 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) {}
: 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),
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) {}
Config::~Config() {}
@ -225,8 +239,13 @@ 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), next_session_id_(1), tstamp_cached_(ev_now(loop)),
: sv_(sv),
loop_(loop),
config_(config),
ssl_ctx_(ssl_ctx),
callbacks_(nullptr),
next_session_id_(1),
tstamp_cached_(ev_now(loop)),
cached_date_(util::http_date(tstamp_cached_)) {
nghttp2_session_callbacks_new(&callbacks_);
@ -424,8 +443,12 @@ void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents) {
} // namespace
Stream::Stream(Http2Handler *handler, int32_t stream_id)
: handler(handler), file_ent(nullptr), body_length(0), body_offset(0),
stream_id(stream_id), echo_upload(false) {
: handler(handler),
file_ent(nullptr),
body_length(0),
body_offset(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);
@ -496,8 +519,13 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
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) {
: 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);
@ -819,11 +847,28 @@ int Http2Handler::connection_made() {
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;
}
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
if (r != 0) {
return r;
}
if (config->connection_window_bits != -1) {
r = nghttp2_submit_window_update(
session_, NGHTTP2_FLAG_NONE, 0,
(1 << config->connection_window_bits) - 1 -
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE);
if (r != 0) {
return r;
}
}
ev_timer_start(sessions_->get_loop(), &settings_timerev_);
if (ssl_ && !nghttp2::ssl::check_http2_requirement(ssl_)) {
@ -864,6 +909,21 @@ int Http2Handler::verify_npn_result() {
return -1;
}
namespace {
std::string make_trailer_header_value(const Headers &trailer) {
if (trailer.empty()) {
return "";
}
auto trailer_names = trailer[0].name;
for (size_t i = 1; i < trailer.size(); ++i) {
trailer_names += ", ";
trailer_names += trailer[i].name;
}
return trailer_names;
}
} // namespace
int Http2Handler::submit_file_response(const std::string &status,
Stream *stream, time_t last_modified,
off_t file_length,
@ -888,14 +948,8 @@ int Http2Handler::submit_file_response(const std::string &status,
if (content_type) {
nva[nvlen++] = http2::make_nv_ls("content-type", *content_type);
}
auto &trailer = get_config()->trailer;
std::string trailer_names;
if (!trailer.empty()) {
trailer_names = trailer[0].name;
for (size_t i = 1; i < trailer.size(); ++i) {
trailer_names += ", ";
trailer_names += trailer[i].name;
}
auto trailer_names = make_trailer_header_value(get_config()->trailer);
if (!trailer_names.empty()) {
nva[nvlen++] = http2::make_nv_ls("trailer", trailer_names);
}
return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen,
@ -906,10 +960,19 @@ int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
const Headers &headers,
nghttp2_data_provider *data_prd) {
auto nva = std::vector<nghttp2_nv>();
nva.reserve(3 + headers.size());
nva.reserve(4 + headers.size());
nva.push_back(http2::make_nv_ls(":status", status));
nva.push_back(http2::make_nv_ll("server", NGHTTPD_SERVER));
nva.push_back(http2::make_nv_ls("date", sessions_->get_cached_date()));
std::string trailer_names;
if (data_prd) {
trailer_names = make_trailer_header_value(get_config()->trailer);
if (!trailer_names.empty()) {
nva.push_back(http2::make_nv_ls("trailer", trailer_names));
}
}
for (auto &nv : headers) {
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
}
@ -920,11 +983,21 @@ int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
nghttp2_data_provider *data_prd) {
auto nva =
make_array(http2::make_nv_ls(":status", status),
http2::make_nv_ll("server", NGHTTPD_SERVER),
http2::make_nv_ls("date", sessions_->get_cached_date()));
return nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(),
auto nva = make_array(http2::make_nv_ls(":status", status),
http2::make_nv_ll("server", NGHTTPD_SERVER),
http2::make_nv_ls("date", sessions_->get_cached_date()),
http2::make_nv_ll("", ""));
size_t nvlen = 3;
std::string trailer_names;
if (data_prd) {
trailer_names = make_trailer_header_value(get_config()->trailer);
if (!trailer_names.empty()) {
nva[nvlen++] = http2::make_nv_ls("trailer", trailer_names);
}
}
return nghttp2_submit_response(session_, stream_id, nva.data(), nvlen,
data_prd);
}

View File

@ -67,6 +67,8 @@ struct Config {
size_t num_worker;
size_t max_concurrent_streams;
ssize_t header_table_size;
int window_bits;
int connection_window_bits;
uint16_t port;
bool verbose;
bool daemon;
@ -87,9 +89,16 @@ struct FileEntry {
FileEntry(std::string path, int64_t length, int64_t mtime, int fd,
const std::string *content_type, ev_tstamp 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) {}
: 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;

View File

@ -27,9 +27,9 @@ check_PROGRAMS =
TESTS =
AM_CFLAGS = $(WARNCFLAGS)
AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
AM_CPPFLAGS = \
-DPKGDATADIR='"$(pkgdatadir)"' \
-Wall \
-I$(top_srcdir)/lib/includes \
-I$(top_builddir)/lib/includes \
-I$(top_srcdir)/lib \
@ -169,12 +169,15 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
shrpx_ssl_test.cc shrpx_ssl_test.h \
shrpx_downstream_test.cc shrpx_downstream_test.h \
shrpx_config_test.cc shrpx_config_test.h \
shrpx_http_test.cc shrpx_http_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
memchunk_test.cc memchunk_test.h \
template_test.cc template_test.h \
base64_test.cc base64_test.h
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@

View File

@ -39,15 +39,33 @@ using boost::asio::ip::tcp;
session::session(boost::asio::io_service &io_service, const std::string &host,
const std::string &service)
: impl_(std::make_shared<session_tcp_impl>(io_service, host, service)) {
: impl_(std::make_shared<session_tcp_impl>(
io_service, host, service, boost::posix_time::seconds(60))) {
impl_->start_resolve(host, service);
}
session::session(boost::asio::io_service &io_service, const std::string &host,
const std::string &service,
const boost::posix_time::time_duration &connect_timeout)
: impl_(std::make_shared<session_tcp_impl>(io_service, host, service,
connect_timeout)) {
impl_->start_resolve(host, service);
}
session::session(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx, const std::string &host,
const std::string &service)
: impl_(std::make_shared<session_tls_impl>(
io_service, tls_ctx, host, service, boost::posix_time::seconds(60))) {
impl_->start_resolve(host, service);
}
session::session(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx, const std::string &host,
const std::string &service,
const boost::posix_time::time_duration &connect_timeout)
: impl_(std::make_shared<session_tls_impl>(io_service, tls_ctx, host,
service)) {
service, connect_timeout)) {
impl_->start_resolve(host, service);
}
@ -97,10 +115,6 @@ const request *session::submit(boost::system::error_code &ec,
return impl_->submit(ec, method, uri, std::move(cb), std::move(h));
}
void session::connect_timeout(const boost::posix_time::time_duration &t) {
impl_->connect_timeout(t);
}
void session::read_timeout(const boost::posix_time::time_duration &t) {
impl_->read_timeout(t);
}

View File

@ -38,12 +38,21 @@ namespace nghttp2 {
namespace asio_http2 {
namespace client {
session_impl::session_impl(boost::asio::io_service &io_service)
: wblen_(0), io_service_(io_service), resolver_(io_service),
deadline_(io_service), connect_timeout_(boost::posix_time::seconds(60)),
read_timeout_(boost::posix_time::seconds(60)), session_(nullptr),
data_pending_(nullptr), data_pendinglen_(0), writing_(false),
inside_callback_(false), stopped_(false) {}
session_impl::session_impl(
boost::asio::io_service &io_service,
const boost::posix_time::time_duration &connect_timeout)
: wblen_(0),
io_service_(io_service),
resolver_(io_service),
deadline_(io_service),
connect_timeout_(connect_timeout),
read_timeout_(boost::posix_time::seconds(60)),
session_(nullptr),
data_pending_(nullptr),
data_pendinglen_(0),
writing_(false),
inside_callback_(false),
stopped_(false) {}
session_impl::~session_impl() {
// finish up all active stream
@ -698,9 +707,7 @@ void session_impl::stop() {
stopped_ = true;
}
void session_impl::connect_timeout(const boost::posix_time::time_duration &t) {
connect_timeout_ = t;
}
bool session_impl::stopped() const { return stopped_; }
void session_impl::read_timeout(const boost::posix_time::time_duration &t) {
read_timeout_ = t;

View File

@ -43,7 +43,8 @@ using boost::asio::ip::tcp;
class session_impl : public std::enable_shared_from_this<session_impl> {
public:
session_impl(boost::asio::io_service &io_service);
session_impl(boost::asio::io_service &io_service,
const boost::posix_time::time_duration &connect_timeout);
virtual ~session_impl();
void start_resolve(const std::string &host, const std::string &service);
@ -91,10 +92,10 @@ public:
void do_read();
void do_write();
void connect_timeout(const boost::posix_time::time_duration &t);
void read_timeout(const boost::posix_time::time_duration &t);
void stop();
bool stopped() const;
protected:
boost::array<uint8_t, 8_k> rb_;

View File

@ -28,10 +28,11 @@ namespace nghttp2 {
namespace asio_http2 {
namespace client {
session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service,
const std::string &host,
const std::string &service)
: session_impl(io_service), socket_(io_service) {}
session_tcp_impl::session_tcp_impl(
boost::asio::io_service &io_service, const std::string &host,
const std::string &service,
const boost::posix_time::time_duration &connect_timeout)
: session_impl(io_service, connect_timeout), socket_(io_service) {}
session_tcp_impl::~session_tcp_impl() {}
@ -39,6 +40,10 @@ void session_tcp_impl::start_connect(tcp::resolver::iterator endpoint_it) {
boost::asio::async_connect(socket_, endpoint_it,
[this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) {
if (stopped()) {
return;
}
if (ec) {
not_connected(ec);
return;

View File

@ -38,7 +38,8 @@ using boost::asio::ip::tcp;
class session_tcp_impl : public session_impl {
public:
session_tcp_impl(boost::asio::io_service &io_service, const std::string &host,
const std::string &service);
const std::string &service,
const boost::posix_time::time_duration &connect_timeout);
virtual ~session_tcp_impl();
virtual void start_connect(tcp::resolver::iterator endpoint_it);

View File

@ -29,11 +29,11 @@ namespace nghttp2 {
namespace asio_http2 {
namespace client {
session_tls_impl::session_tls_impl(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx,
const std::string &host,
const std::string &service)
: session_impl(io_service), socket_(io_service, tls_ctx) {
session_tls_impl::session_tls_impl(
boost::asio::io_service &io_service, boost::asio::ssl::context &tls_ctx,
const std::string &host, const std::string &service,
const boost::posix_time::time_duration &connect_timeout)
: session_impl(io_service, connect_timeout), socket_(io_service, tls_ctx) {
// this callback setting is no effect is
// ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is
// not used, which is what we want.
@ -46,6 +46,10 @@ void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) {
boost::asio::async_connect(
socket(), endpoint_it, [this](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) {
if (stopped()) {
return;
}
if (ec) {
not_connected(ec);
return;
@ -54,6 +58,10 @@ void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) {
socket_.async_handshake(
boost::asio::ssl::stream_base::client,
[this, endpoint_it](const boost::system::error_code &ec) {
if (stopped()) {
return;
}
if (ec) {
not_connected(ec);
return;

View File

@ -41,7 +41,8 @@ class session_tls_impl : public session_impl {
public:
session_tls_impl(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx, const std::string &host,
const std::string &service);
const std::string &service,
const boost::posix_time::time_duration &connect_timeout);
virtual ~session_tls_impl();
virtual void start_connect(tcp::resolver::iterator endpoint_it);

View File

@ -175,7 +175,12 @@ void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) {
});
}
void server::stop() { io_service_pool_.stop(); }
void server::stop() {
io_service_pool_.stop();
for (auto &acceptor : acceptors_) {
acceptor.close();
}
}
void server::join() { io_service_pool_.join(); }

View File

@ -69,10 +69,13 @@ public:
const boost::posix_time::time_duration &tls_handshake_timeout,
const boost::posix_time::time_duration &read_timeout,
SocketArgs &&... args)
: socket_(std::forward<SocketArgs>(args)...), mux_(mux),
: socket_(std::forward<SocketArgs>(args)...),
mux_(mux),
deadline_(socket_.get_io_service()),
tls_handshake_timeout_(tls_handshake_timeout),
read_timeout_(read_timeout), writing_(false), stopped_(false) {}
read_timeout_(read_timeout),
writing_(false),
stopped_(false) {}
/// Start the first asynchronous operation for the connection.
void start() {

View File

@ -230,8 +230,14 @@ int on_frame_not_send_callback(nghttp2_session *session,
http2_handler::http2_handler(boost::asio::io_service &io_service,
boost::asio::ip::tcp::endpoint ep,
connection_write writefun, serve_mux &mux)
: writefun_(writefun), mux_(mux), io_service_(io_service), remote_ep_(ep),
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false),
: writefun_(writefun),
mux_(mux),
io_service_(io_service),
remote_ep_(ep),
session_(nullptr),
buf_(nullptr),
buflen_(0),
inside_callback_(false),
tstamp_cached_(time(nullptr)),
formatted_date_(util::http_date(tstamp_cached_)) {}

View File

@ -38,7 +38,8 @@ namespace asio_http2 {
namespace server {
http2_impl::http2_impl()
: num_threads_(1), backlog_(-1),
: num_threads_(1),
backlog_(-1),
tls_handshake_timeout_(boost::posix_time::seconds(60)),
read_timeout_(boost::posix_time::seconds(60)) {}

View File

@ -36,8 +36,11 @@ namespace asio_http2 {
namespace server {
response_impl::response_impl()
: strm_(nullptr), generator_cb_(deferred_generator()), status_code_(200),
state_(response_state::INITIAL), pushed_(false),
: strm_(nullptr),
generator_cb_(deferred_generator()),
status_code_(200),
state_(response_state::INITIAL),
pushed_(false),
push_promise_sent_(false) {}
unsigned int response_impl::status_code() const { return status_code_; }

View File

@ -33,9 +33,8 @@ namespace nghttp2 {
namespace base64 {
template <typename InputIterator>
std::string encode(InputIterator first, InputIterator last) {
static const char CHAR_TABLE[] = {
template <typename InputIt> std::string encode(InputIt first, InputIt last) {
static constexpr char CHAR_TABLE[] = {
'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',
@ -48,39 +47,38 @@ std::string encode(InputIterator first, InputIterator last) {
return res;
}
size_t r = len % 3;
InputIterator j = last - r;
char temp[4];
res.resize((len + 2) / 3 * 4);
auto j = last - r;
auto p = std::begin(res);
while (first != j) {
int n = static_cast<unsigned char>(*first++) << 16;
n += static_cast<unsigned char>(*first++) << 8;
n += static_cast<unsigned char>(*first++);
temp[0] = CHAR_TABLE[n >> 18];
temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu];
temp[3] = CHAR_TABLE[n & 0x3fu];
res.append(temp, sizeof(temp));
uint32_t n = static_cast<uint8_t>(*first++) << 16;
n += static_cast<uint8_t>(*first++) << 8;
n += static_cast<uint8_t>(*first++);
*p++ = CHAR_TABLE[n >> 18];
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
*p++ = CHAR_TABLE[(n >> 6) & 0x3fu];
*p++ = CHAR_TABLE[n & 0x3fu];
}
if (r == 2) {
int n = static_cast<unsigned char>(*first++) << 16;
n += static_cast<unsigned char>(*first++) << 8;
temp[0] = CHAR_TABLE[n >> 18];
temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu];
temp[3] = '=';
res.append(temp, sizeof(temp));
uint32_t n = static_cast<uint8_t>(*first++) << 16;
n += static_cast<uint8_t>(*first++) << 8;
*p++ = CHAR_TABLE[n >> 18];
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
*p++ = CHAR_TABLE[(n >> 6) & 0x3fu];
*p++ = '=';
} else if (r == 1) {
int n = static_cast<unsigned char>(*first++) << 16;
temp[0] = CHAR_TABLE[n >> 18];
temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
temp[2] = '=';
temp[3] = '=';
res.append(temp, sizeof(temp));
uint32_t n = static_cast<uint8_t>(*first++) << 16;
*p++ = CHAR_TABLE[n >> 18];
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
*p++ = '=';
*p++ = '=';
}
return res;
}
template <typename InputIterator>
InputIterator getNext(InputIterator first, InputIterator last, const int *tbl) {
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;
@ -89,9 +87,8 @@ InputIterator getNext(InputIterator first, InputIterator last, const int *tbl) {
return first;
}
template <typename InputIterator>
std::string decode(InputIterator first, InputIterator last) {
static const int INDEX_TABLE[] = {
template <typename InputIt> std::string decode(InputIt first, InputIt last) {
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,
@ -107,59 +104,47 @@ std::string decode(InputIterator first, InputIterator last) {
-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};
std::string res;
InputIterator k[4];
int eq = 0;
for (; first != last;) {
for (int i = 1; i <= 4; ++i) {
k[i - 1] = getNext(first, last, INDEX_TABLE);
if (k[i - 1] == last) {
// If i == 1, input may look like this: "TWFu\n" (i.e.,
// garbage at the end)
if (i != 1) {
res.clear();
}
return res;
} else if (*k[i - 1] == '=' && eq == 0) {
eq = i;
}
first = k[i - 1] + 1;
}
if (eq) {
break;
}
int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
(INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12) +
(INDEX_TABLE[static_cast<unsigned char>(*k[2])] << 6) +
INDEX_TABLE[static_cast<unsigned char>(*k[3])];
res += n >> 16;
res += n >> 8 & 0xffu;
res += n & 0xffu;
auto len = last - first;
if (len % 4 != 0) {
return "";
}
if (eq) {
if (eq <= 2) {
res.clear();
return res;
} else {
for (int i = eq; i <= 4; ++i) {
if (*k[i - 1] != '=') {
res.clear();
std::string res;
res.resize(len / 4 * 3);
auto p = std::begin(res);
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 "";
}
if (i == 3) {
if (*first == '=' && *(first + 1) == '=' && first + 2 == last) {
*p++ = n >> 16;
res.resize(p - std::begin(res));
return res;
}
return "";
}
if (*first == '=' && first + 1 == last) {
*p++ = n >> 16;
*p++ = n >> 8 & 0xffu;
res.resize(p - std::begin(res));
return res;
}
return "";
}
if (eq == 3) {
int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
(INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12);
res += n >> 16;
} else if (eq == 4) {
int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
(INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12) +
(INDEX_TABLE[static_cast<unsigned char>(*k[2])] << 6);
res += n >> 16;
res += n >> 8 & 0xffu;
}
n += idx << (24 - i * 6);
}
*p++ = n >> 16;
*p++ = n >> 8 & 0xffu;
*p++ = n & 0xffu;
}
return res;
}

109
src/base64_test.cc Normal file
View File

@ -0,0 +1,109 @@
/*
* 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) {
{
std::string in = "/w==";
auto out = base64::decode(std::begin(in), std::end(in));
CU_ASSERT("\xff" == out);
}
{
std::string in = "//4=";
auto out = base64::decode(std::begin(in), std::end(in));
CU_ASSERT("\xff\xfe" == out);
}
{
std::string in = "//79";
auto out = base64::decode(std::begin(in), std::end(in));
CU_ASSERT("\xff\xfe\xfd" == out);
}
{
std::string in = "//79/A==";
auto out = base64::decode(std::begin(in), std::end(in));
CU_ASSERT("\xff\xfe\xfd\xfc" == out);
}
{
// 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);
}
{
// 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);
}
{
// 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);
}
{
// additional '=' at the end is bad
std::string in = "//79/A======";
auto out = base64::decode(std::begin(in), std::end(in));
CU_ASSERT("" == out);
}
}
} // namespace nghttp2

39
src/base64_test.h Normal file
View File

@ -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

View File

@ -79,14 +79,31 @@ bool recorded(const std::chrono::steady_clock::time_point &t) {
} // namespace
Config::Config()
: data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
rate(0), rate_period(1.0), conn_active_timeout(0.),
conn_inactivity_timeout(0.), no_tls_proto(PROTO_HTTP2), data_fd(-1),
port(0), default_port(0), verbose(false), timing_script(false) {}
: data_length(-1),
addrs(nullptr),
nreqs(1),
nclients(1),
nthreads(1),
max_concurrent_streams(-1),
window_bits(30),
connection_window_bits(30),
rate(0),
rate_period(1.0),
conn_active_timeout(0.),
conn_inactivity_timeout(0.),
no_tls_proto(PROTO_HTTP2),
data_fd(-1),
port(0),
default_port(0),
verbose(false),
timing_script(false) {}
Config::~Config() {
freeaddrinfo(addrs);
if (base_uri_unix) {
delete addrs;
} else {
freeaddrinfo(addrs);
}
if (data_fd != -1) {
close(data_fd);
@ -102,9 +119,18 @@ 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),
: 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() {}
Stream::Stream() : req_stat{}, status_success(-1) {}
@ -286,9 +312,18 @@ void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
} // namespace
Client::Client(uint32_t id, Worker *worker, size_t req_todo)
: cstat{}, worker(worker), ssl(nullptr), next_addr(config.addrs),
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo),
req_started(0), req_done(0), id(id), fd(-1),
: cstat{},
worker(worker),
ssl(nullptr),
next_addr(config.addrs),
current_addr(nullptr),
reqidx(0),
state(CLIENT_IDLE),
req_todo(req_todo),
req_started(0),
req_done(0),
id(id),
fd(-1),
new_connection_requested(false) {
ev_io_init(&wev, writecb, 0, EV_WRITE);
ev_io_init(&rev, readcb, 0, EV_READ);
@ -1090,11 +1125,20 @@ void Client::try_new_connection() { new_connection_requested = true; }
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)
: stats(req_todo, nclients), loop(ev_loop_new(0)), 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) {
: stats(req_todo, nclients),
loop(ev_loop_new(0)),
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()) {
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
} else {
@ -1301,6 +1345,18 @@ process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
namespace {
void resolve_host() {
if (config.base_uri_unix) {
auto res = 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;
@ -1357,6 +1413,10 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
}
} // namespace
namespace {
constexpr char UNIX_PATH_PREFIX[] = "unix:";
} // namespace
namespace {
bool parse_base_uri(std::string base_uri) {
http_parser_url u{};
@ -1636,11 +1696,16 @@ Options:
contained in other URIs, if present, are ignored.
Definition of a base URI overrides all scheme, host or
port values.
-B, --base-uri=<URI>
-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.
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.
--npn-list=<LIST>
Comma delimited list of ALPN protocol identifier sorted
in the order of preference. That means most desirable
@ -1819,13 +1884,40 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE);
}
break;
case 'B':
case 'B': {
config.base_uri = "";
config.base_uri_unix = false;
if (util::istarts_with_l(optarg, UNIX_PATH_PREFIX)) {
// UNIX domain socket path
sockaddr_un un;
auto path = optarg + str_size(UNIX_PATH_PREFIX);
auto pathlen = strlen(optarg) - str_size(UNIX_PATH_PREFIX);
if (pathlen == 0 || pathlen + 1 > sizeof(un.sun_path)) {
std::cerr << "--base-uri: invalid UNIX domain socket path: " << optarg
<< std::endl;
exit(EXIT_FAILURE);
}
config.base_uri_unix = true;
auto &unix_addr = config.unix_addr;
std::copy_n(path, pathlen + 1, unix_addr.sun_path);
unix_addr.sun_family = AF_UNIX;
break;
}
if (!parse_base_uri(optarg)) {
std::cerr << "invalid base URI: " << optarg << std::endl;
std::cerr << "--base-uri: invalid base URI: " << optarg << std::endl;
exit(EXIT_FAILURE);
}
config.base_uri = optarg;
break;
}
case 'v':
config.verbose = true;
break;
@ -2321,7 +2413,7 @@ int main(int argc, char **argv) {
std::cout << std::fixed << std::setprecision(2) << R"(
finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
<< util::utos_with_funit(bps) << R"(B/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
@ -2329,9 +2421,12 @@ requests: )" << stats.req_todo << " total, " << stats.req_started
<< 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: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
<< " bytes headers (space savings " << header_space_savings * 100
<< "%), " << stats.bytes_body << R"( bytes data
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
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)

View File

@ -34,6 +34,7 @@
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif // HAVE_NETDB_H
#include <sys/un.h>
#include <vector>
#include <string>
@ -100,6 +101,11 @@ struct Config {
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 NPN/ALPN protocol strings in the order of
// preference.
std::vector<std::string> npn_list;

View File

@ -41,7 +41,10 @@ using namespace nghttp2;
namespace h2load {
Http1Session::Http1Session(Client *client)
: stream_req_counter_(1), stream_resp_counter_(1), client_(client), htp_(),
: stream_req_counter_(1),
stream_resp_counter_(1),
client_(client),
htp_(),
complete_(false) {
http_parser_init(&htp_, HTTP_RESPONSE);
htp_.data = this;

View File

@ -348,6 +348,7 @@ void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
switch (kv.token) {
case HD_COOKIE:
case HD_CONNECTION:
case HD_FORWARDED:
case HD_HOST:
case HD_HTTP2_SETTINGS:
case HD_KEEP_ALIVE:
@ -385,6 +386,7 @@ void build_http1_headers_from_headers(DefaultMemchunks *buf,
switch (kv.token) {
case HD_CONNECTION:
case HD_COOKIE:
case HD_FORWARDED:
case HD_HOST:
case HD_HTTP2_SETTINGS:
case HD_KEEP_ALIVE:
@ -638,6 +640,15 @@ int lookup_token(const uint8_t *name, size_t namelen) {
break;
}
break;
case 9:
switch (name[8]) {
case 'd':
if (util::streq_l("forwarde", name, 8)) {
return HD_FORWARDED;
}
break;
}
break;
case 10:
switch (name[9]) {
case 'e':
@ -662,6 +673,15 @@ int lookup_token(const uint8_t *name, size_t namelen) {
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':

View File

@ -45,7 +45,9 @@ namespace nghttp2 {
struct Header {
Header(std::string name, std::string value, bool no_index = false,
int16_t token = -1)
: name(std::move(name)), value(std::move(value)), token(token),
: name(std::move(name)),
value(std::move(value)),
token(token),
no_index(no_index) {}
Header() : token(-1), no_index(false) {}
@ -151,6 +153,12 @@ nghttp2_nv make_nv_ls_nocopy(const char(&name)[N], const std::string &value) {
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};
}
// 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
@ -218,6 +226,8 @@ int parse_http_status_code(const std::string &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,
@ -231,9 +241,11 @@ enum {
HD_CACHE_CONTROL,
HD_CONNECTION,
HD_CONTENT_LENGTH,
HD_CONTENT_TYPE,
HD_COOKIE,
HD_DATE,
HD_EXPECT,
HD_FORWARDED,
HD_HOST,
HD_HTTP2_SETTINGS,
HD_IF_MODIFIED_SINCE,

View File

@ -123,15 +123,33 @@ class session_impl;
class session {
public:
// Starts HTTP/2 session by connecting to |host| and |service|
// (e.g., "80") using clear text TCP connection.
// (e.g., "80") using clear text TCP connection with connect timeout
// 60 seconds.
session(boost::asio::io_service &io_service, const std::string &host,
const std::string &service);
// Starts HTTP/2 session by connecting to |host| and |service|
// (e.g., "443") using encrypted SSL/TLS connection.
// (e.g., "80") using clear text TCP connection with given connect
// timeout.
session(boost::asio::io_service &io_service, const std::string &host,
const std::string &service,
const boost::posix_time::time_duration &connect_timeout);
// Starts HTTP/2 session by connecting to |host| and |service|
// (e.g., "443") using encrypted SSL/TLS connection with connect
// timeout 60 seconds.
session(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_context, const std::string &host,
const std::string &service);
// Starts HTTP/2 session by connecting to |host| and |service|
// (e.g., "443") using encrypted SSL/TLS connection with given
// connect timeout.
session(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_context, const std::string &host,
const std::string &service,
const boost::posix_time::time_duration &connect_timeout);
~session();
session(session &&other) noexcept;
@ -144,9 +162,6 @@ public:
// and session is terminated.
void on_error(error_cb cb) const;
// Sets connect timeout, which defaults to 60 seconds.
void connect_timeout(const boost::posix_time::time_duration &t);
// Sets read timeout, which defaults to 60 seconds.
void read_timeout(const boost::posix_time::time_duration &t);

View File

@ -32,13 +32,23 @@ namespace nghttp2 {
namespace util {
EvbufferBuffer::EvbufferBuffer()
: evbuffer_(nullptr), bucket_(nullptr), buf_(nullptr), bufmax_(0),
buflen_(0), limit_(0), writelen_(0) {}
: 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) {}
: 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) {

View File

@ -41,9 +41,19 @@
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(std::unique_ptr<Memchunk> next_chunk)
: pos(std::begin(buf)), last(pos), knext(std::move(next_chunk)),
: pos(std::begin(buf)),
last(pos),
knext(std::move(next_chunk)),
next(nullptr) {}
size_t len() const { return last - pos; }
size_t left() const { return std::end(buf) - last; }
@ -89,14 +99,16 @@ template <typename Memchunk> struct Memchunks {
Memchunks(Pool<Memchunk> *pool)
: pool(pool), head(nullptr), tail(nullptr), len(0) {}
Memchunks(const Memchunks &) = delete;
Memchunks(Memchunks &&other)
: pool(other.pool), head(other.head), tail(other.tail), len(other.len) {
Memchunks(Memchunks &&other) noexcept : pool(other.pool),
head(other.head),
tail(other.tail),
len(other.len) {
// keep other.pool
other.head = other.tail = nullptr;
other.len = 0;
}
Memchunks &operator=(const Memchunks &) = delete;
Memchunks &operator=(Memchunks &&other) {
Memchunks &operator=(Memchunks &&other) noexcept {
if (this == &other) {
return *this;
}
@ -165,6 +177,7 @@ template <typename Memchunk> struct Memchunks {
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 remove(void *dest, size_t count) {
if (!tail || count == 0) {
return 0;
@ -196,6 +209,36 @@ template <typename Memchunk> struct Memchunks {
return first - static_cast<uint8_t *>(dest);
}
size_t remove(Memchunks &dest, size_t count) {
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 drain(size_t count) {
auto ndata = count;
auto m = head;
@ -249,17 +292,24 @@ template <typename Memchunk> struct Memchunks {
// 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) {}
: memchunks(pool),
cur(nullptr),
cur_pos(nullptr),
cur_last(nullptr),
len(0),
peeking(true) {}
PeekMemchunks(const PeekMemchunks &) = delete;
PeekMemchunks(PeekMemchunks &&other)
: memchunks(std::move(other.memchunks)), cur(other.cur),
cur_pos(other.cur_pos), cur_last(other.cur_last), len(other.len),
PeekMemchunks(PeekMemchunks &&other) noexcept
: memchunks(std::move(other.memchunks)),
cur(other.cur),
cur_pos(other.cur_pos),
cur_last(other.cur_last),
len(other.len),
peeking(other.peeking) {
other.reset();
}
PeekMemchunks &operator=(const PeekMemchunks &) = delete;
PeekMemchunks &operator=(PeekMemchunks &&other) {
PeekMemchunks &operator=(PeekMemchunks &&other) noexcept {
if (this == &other) {
return *this;
}
@ -368,14 +418,6 @@ using MemchunkPool = Pool<Memchunk16K>;
using DefaultMemchunks = Memchunks<Memchunk16K>;
using DefaultPeekMemchunks = PeekMemchunks<Memchunk16K>;
#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
inline int limit_iovec(struct iovec *iov, int iovcnt, size_t max) {
if (max == 0) {
return 0;

View File

@ -94,13 +94,26 @@ constexpr auto anchors = std::array<Anchor, 5>{{
Config::Config()
: header_table_size(-1),
min_header_table_size(std::numeric_limits<uint32_t>::max()), padding(0),
max_concurrent_streams(100), peer_max_concurrent_streams(100),
weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1), timeout(0.), window_bits(-1),
connection_window_bits(-1), verbose(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) {
min_header_table_size(std::numeric_limits<uint32_t>::max()),
padding(0),
max_concurrent_streams(100),
peer_max_concurrent_streams(100),
weight(NGHTTP2_DEFAULT_WEIGHT),
multiply(1),
timeout(0.),
window_bits(-1),
connection_window_bits(-1),
verbose(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) {
nghttp2_option_new(&http2_option);
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
peer_max_concurrent_streams);
@ -133,9 +146,18 @@ std::string strip_fragment(const char *raw_uri) {
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), html_parser(nullptr),
data_prd(data_prd), stream_id(-1), status(0), level(level),
: uri(uri),
u(u),
pri_spec(pri_spec),
data_length(data_length),
data_offset(0),
response_len(0),
inflater(nullptr),
html_parser(nullptr),
data_prd(data_prd),
stream_id(-1),
status(0),
level(level),
expect_final_response(false) {
http2::init_hdidx(res_hdidx);
http2::init_hdidx(req_hdidx);
@ -467,10 +489,20 @@ void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
struct ev_loop *loop, SSL_CTX *ssl_ctx)
: 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),
: 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);
@ -1974,7 +2006,7 @@ id responseEnd requestStart process code size request path)" << std::endl;
<< ("+" + util::format_duration(request_start)) << " "
<< std::setw(8) << util::format_duration(total) << " "
<< std::setw(4) << req->status << " " << std::setw(4)
<< util::utos_with_unit(req->response_len) << " "
<< util::utos_unit(req->response_len) << " "
<< req->make_reqpath() << std::endl;
}
}

View File

@ -143,6 +143,11 @@ Options:
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
@ -202,6 +207,8 @@ int main(int argc, char **argv) {
{"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},
@ -214,7 +221,7 @@ int main(int argc, char **argv) {
{"no-content-length", no_argument, &flag, 10},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options,
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
&option_index);
char *end;
if (c == -1) {
@ -281,6 +288,26 @@ int main(int argc, char **argv) {
std::cerr << "-p: Bad option value: " << optarg << std::endl;
}
break;
case 'w':
case 'W': {
char *endptr;
errno = 0;
auto n = strtoul(optarg, &endptr, 10);
if (errno != 0 || *endptr != '\0' || n >= 31) {
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);

View File

@ -38,6 +38,9 @@
#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 "ssl.h"
@ -69,6 +72,8 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_ssl_create_lookup_tree) ||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
!CU_add_test(pSuite, "ssl_tls_hostname_match",
shrpx::test_shrpx_ssl_tls_hostname_match) ||
!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",
@ -101,14 +106,10 @@ int main(int argc, char *argv[]) {
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, "downstream_index_request_headers",
shrpx::test_downstream_index_request_headers) ||
!CU_add_test(pSuite, "downstream_index_response_headers",
shrpx::test_downstream_index_response_headers) ||
!CU_add_test(pSuite, "downstream_get_request_header",
shrpx::test_downstream_get_request_header) ||
!CU_add_test(pSuite, "downstream_get_response_header",
shrpx::test_downstream_get_response_header) ||
!CU_add_test(pSuite, "downstream_field_store_index_headers",
shrpx::test_downstream_field_store_index_headers) ||
!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",
@ -125,6 +126,8 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) ||
!CU_add_test(pSuite, "config_match_downstream_addr_group",
shrpx::test_shrpx_config_match_downstream_addr_group) ||
!CU_add_test(pSuite, "http_create_forwarded",
shrpx::test_shrpx_http_create_forwarded) ||
!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",
@ -144,10 +147,8 @@ int main(int argc, char *argv[]) {
!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_with_unit",
shrpx::test_util_utos_with_unit) ||
!CU_add_test(pSuite, "util_utos_with_funit",
shrpx::test_util_utos_with_funit) ||
!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) ||
@ -182,7 +183,13 @@ int main(int argc, char *argv[]) {
!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)) {
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();
}

File diff suppressed because it is too large Load Diff

View File

@ -45,16 +45,16 @@ void acceptcb(struct ev_loop *loop, ev_io *w, int revent) {
}
} // namespace
AcceptHandler::AcceptHandler(int fd, ConnectionHandler *h)
: conn_hnr_(h), fd_(fd) {
ev_io_init(&wev_, acceptcb, fd_, EV_READ);
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(fd_);
close(faddr_->fd);
}
void AcceptHandler::accept_connection() {
@ -63,10 +63,10 @@ void AcceptHandler::accept_connection() {
socklen_t addrlen = sizeof(sockaddr);
#ifdef HAVE_ACCEPT4
auto cfd =
accept4(fd_, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#else // !HAVE_ACCEPT4
auto cfd = accept(fd_, &sockaddr.sa, &addrlen);
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) {
@ -87,7 +87,7 @@ void AcceptHandler::accept_connection() {
case ENFILE:
LOG(WARN) << "acceptor: running out file descriptor; disable acceptor "
"temporarily";
conn_hnr_->disable_acceptor_temporary(30.);
conn_hnr_->sleep_acceptor(get_config()->conn.listener.timeout.sleep);
break;
}
@ -101,7 +101,7 @@ void AcceptHandler::accept_connection() {
util::make_socket_nodelay(cfd);
conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen);
conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen, faddr_);
}
}
@ -109,6 +109,6 @@ 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 fd_; }
int AcceptHandler::get_fd() const { return faddr_->fd; }
} // namespace shrpx

View File

@ -32,10 +32,11 @@
namespace shrpx {
class ConnectionHandler;
struct UpstreamAddr;
class AcceptHandler {
public:
AcceptHandler(int fd, ConnectionHandler *h);
AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h);
~AcceptHandler();
void accept_connection();
void enable();
@ -45,7 +46,7 @@ public:
private:
ev_io wev_;
ConnectionHandler *conn_hnr_;
int fd_;
const UpstreamAddr *faddr_;
};
} // namespace shrpx

View File

@ -27,6 +27,13 @@
#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"
@ -120,6 +127,10 @@ int ClientHandler::read_clear() {
return 0;
}
if (!ev_is_active(&conn_.rev)) {
return 0;
}
auto nread = conn_.read_clear(rb_.last, rb_.wleft());
if (nread == 0) {
@ -213,6 +224,10 @@ int ClientHandler::read_tls() {
return 0;
}
if (!ev_is_active(&conn_.rev)) {
return 0;
}
auto nread = conn_.read_tls(rb_.last, rb_.wleft());
if (nread == 0) {
@ -362,20 +377,24 @@ int ClientHandler::upstream_http1_connhd_read() {
}
ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
const char *ipaddr, const char *port)
const char *ipaddr, const char *port, int family,
const UpstreamAddr *faddr)
: conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(),
get_config()->upstream_write_timeout,
get_config()->upstream_read_timeout, get_config()->write_rate,
get_config()->write_burst, get_config()->read_rate,
get_config()->read_burst, writecb, readcb, timeoutcb, this,
get_config()->tls_dyn_rec_warmup_threshold,
get_config()->tls_dyn_rec_idle_timeout),
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),
pinned_http2sessions_(
get_config()->downstream_proto == PROTO_HTTP2
get_config()->conn.downstream.proto == PROTO_HTTP2
? make_unique<std::vector<ssize_t>>(
get_config()->downstream_addr_groups.size(), -1)
get_config()->conn.downstream.addr_groups.size(), -1)
: nullptr),
ipaddr_(ipaddr), port_(port), worker_(worker),
ipaddr_(ipaddr),
port_(port),
faddr_(faddr),
worker_(worker),
left_connhd_len_(NGHTTP2_CLIENT_MAGIC_LEN),
should_close_after_write_(false) {
@ -388,7 +407,7 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
conn_.rlimit.startw();
ev_timer_again(conn_.loop, &conn_.rt);
if (get_config()->accept_proxy_protocol) {
if (get_config()->conn.upstream.accept_proxy_protocol) {
read_ = &ClientHandler::read_clear;
write_ = &ClientHandler::noop;
on_read_ = &ClientHandler::proxy_protocol_read;
@ -396,6 +415,23 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
} else {
setup_upstream_io_callback();
}
auto &fwdconf = get_config()->http.forwarded;
if (fwdconf.params & FORWARDED_FOR) {
if (fwdconf.for_node_type == FORWARDED_NODE_OBFUSCATED) {
forwarded_for_ = "_";
forwarded_for_ += util::random_alpha_digit(worker_->get_randgen(),
SHRPX_OBFUSCATED_NODE_LENGTH);
} else if (family == AF_INET6) {
forwarded_for_ = "[";
forwarded_for_ += ipaddr_;
forwarded_for_ += ']';
} else {
// family == AF_INET or family == AF_UNIX
forwarded_for_ = ipaddr_;
}
}
}
void ClientHandler::setup_upstream_io_callback() {
@ -503,7 +539,8 @@ int ClientHandler::validate_next_proto() {
CLOG(INFO, this) << "The negotiated next protocol: " << proto;
}
if (!ssl::in_proto_list(get_config()->npn_list, next_proto, next_proto_len)) {
if (!ssl::in_proto_list(get_config()->tls.npn_list, next_proto,
next_proto_len)) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "The negotiated protocol is not supported";
}
@ -626,33 +663,34 @@ void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
std::unique_ptr<DownstreamConnection>
ClientHandler::get_downstream_connection(Downstream *downstream) {
size_t group;
auto &groups = get_config()->downstream_addr_groups;
auto catch_all = get_config()->downstream_addr_group_catch_all;
auto &downstreamconf = get_config()->conn.downstream;
auto &groups = downstreamconf.addr_groups;
auto catch_all = downstreamconf.addr_group_catch_all;
const auto &req = downstream->request();
// Fast path. If we have one group, it must be catch-all group.
// HTTP/2 and client proxy modes fall in this case.
if (groups.size() == 1) {
group = 0;
} else if (downstream->get_request_method() == HTTP_CONNECT) {
} else if (req.method == HTTP_CONNECT) {
// We don't know how to treat CONNECT request in host-path
// mapping. It most likely appears in proxy scenario. Since we
// have dealt with proxy case already, just use catch-all group.
group = catch_all;
} else {
auto &router = get_config()->router;
if (!downstream->get_request_http2_authority().empty()) {
group = match_downstream_addr_group(
router, downstream->get_request_http2_authority(),
downstream->get_request_path(), groups, catch_all);
if (!req.authority.empty()) {
group = match_downstream_addr_group(router, req.authority, req.path,
groups, catch_all);
} else {
auto h = downstream->get_request_header(http2::HD_HOST);
auto h = req.fs.header(http2::HD_HOST);
if (h) {
group = match_downstream_addr_group(router, h->value,
downstream->get_request_path(),
groups, catch_all);
group = match_downstream_addr_group(router, h->value, req.path, groups,
catch_all);
} else {
group = match_downstream_addr_group(
router, "", downstream->get_request_path(), groups, catch_all);
group = match_downstream_addr_group(router, "", req.path, groups,
catch_all);
}
}
}
@ -672,7 +710,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
auto dconn_pool = worker_->get_dconn_pool();
if (get_config()->downstream_proto == PROTO_HTTP2) {
if (downstreamconf.proto == PROTO_HTTP2) {
Http2Session *http2session;
auto &pinned = (*pinned_http2sessions_)[group];
if (pinned == -1) {
@ -684,8 +722,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
}
dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session);
} else {
dconn =
make_unique<HttpDownstreamConnection>(dconn_pool, group, conn_.loop);
dconn = make_unique<HttpDownstreamConnection>(dconn_pool, group,
conn_.loop, worker_);
}
dconn->set_client_handler(this);
return dconn;
@ -735,17 +773,6 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
"\r\n";
auto required_size = str_size(res) + input->rleft();
if (output->wleft() < required_size) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this)
<< "HTTP Upgrade failed because of insufficient buffer space: need "
<< required_size << ", available " << output->wleft();
}
return -1;
}
if (upstream->upgrade_upstream(http) != 0) {
return -1;
}
@ -757,11 +784,8 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
on_read_ = &ClientHandler::upstream_http2_connhd_read;
write_ = &ClientHandler::write_clear;
auto nread =
downstream->get_response_buf()->remove(output->last, output->wleft());
output->write(nread);
output->write(res, str_size(res));
input->remove(*output, input->rleft());
output->append(res, str_size(res));
upstream_ = std::move(upstream);
signal_write();
@ -783,28 +807,27 @@ void ClientHandler::start_immediate_shutdown() {
}
namespace {
// Construct absolute request URI from |downstream|, mainly to log
// 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.
std::string construct_absolute_request_uri(Downstream *downstream) {
auto &authority = downstream->get_request_http2_authority();
if (authority.empty()) {
return downstream->get_request_path();
std::string construct_absolute_request_uri(const Request &req) {
if (req.authority.empty()) {
return req.path;
}
std::string uri;
auto &scheme = downstream->get_request_http2_scheme();
if (scheme.empty()) {
if (req.scheme.empty()) {
// We may have to log the request which lacks scheme (e.g.,
// http/1.1 with origin form).
uri += "http://";
} else {
uri += scheme;
uri += req.scheme;
uri += "://";
}
uri += authority;
uri += downstream->get_request_path();
uri += req.authority;
uri += req.path;
return uri;
}
@ -812,34 +835,34 @@ std::string construct_absolute_request_uri(Downstream *downstream) {
void ClientHandler::write_accesslog(Downstream *downstream) {
nghttp2::ssl::TLSSessionInfo tls_info;
const auto &req = downstream->request();
const auto &resp = downstream->response();
upstream_accesslog(
get_config()->accesslog_format,
get_config()->logging.access.format,
LogSpec{
downstream, ipaddr_.c_str(),
http2::to_method_string(downstream->get_request_method()),
downstream, StringRef(ipaddr_), http2::to_method_string(req.method),
downstream->get_request_method() == HTTP_CONNECT
? downstream->get_request_http2_authority().c_str()
req.method == HTTP_CONNECT
? StringRef(req.authority)
: (get_config()->http2_proxy || get_config()->client_proxy)
? construct_absolute_request_uri(downstream).c_str()
: downstream->get_request_path().empty()
? downstream->get_request_method() == HTTP_OPTIONS
? "*"
: "-"
: downstream->get_request_path().c_str(),
? StringRef(construct_absolute_request_uri(req))
: req.path.empty()
? req.method == HTTP_OPTIONS
? StringRef::from_lit("*")
: StringRef::from_lit("-")
: StringRef(req.path),
alpn_.c_str(),
StringRef(alpn_),
nghttp2::ssl::get_tls_session_info(&tls_info, conn_.tls.ssl),
std::chrono::system_clock::now(), // time_now
downstream->get_request_start_time(), // request_start_time
std::chrono::high_resolution_clock::now(), // request_end_time
downstream->get_request_major(), downstream->get_request_minor(),
downstream->get_response_http_status(),
downstream->get_response_sent_bodylen(), port_.c_str(),
get_config()->port, get_config()->pid,
req.http_major, req.http_minor, resp.http_status,
downstream->response_sent_body_length, StringRef(port_), faddr_->port,
get_config()->pid,
});
}
@ -849,20 +872,20 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
auto highres_now = std::chrono::high_resolution_clock::now();
nghttp2::ssl::TLSSessionInfo tls_info;
upstream_accesslog(get_config()->accesslog_format,
upstream_accesslog(get_config()->logging.access.format,
LogSpec{
nullptr, ipaddr_.c_str(),
"-", // method
"-", // path,
alpn_.c_str(), nghttp2::ssl::get_tls_session_info(
&tls_info, conn_.tls.ssl),
nullptr, StringRef(ipaddr_),
StringRef::from_lit("-"), // method
StringRef::from_lit("-"), // path,
StringRef(alpn_), nghttp2::ssl::get_tls_session_info(
&tls_info, conn_.tls.ssl),
time_now,
highres_now, // request_start_time TODO is
// there a better value?
highres_now, // request_end_time
major, minor, // major, minor
status, body_bytes_sent, port_.c_str(),
get_config()->port, get_config()->pid,
status, body_bytes_sent, StringRef(port_),
faddr_->port, get_config()->pid,
});
}
@ -952,7 +975,7 @@ int ClientHandler::proxy_protocol_read() {
--end;
constexpr const char HEADER[] = "PROXY ";
constexpr char HEADER[] = "PROXY ";
if (static_cast<size_t>(end - rb_.pos) < str_size(HEADER)) {
if (LOG_ENABLED(INFO)) {
@ -1103,4 +1126,18 @@ int ClientHandler::proxy_protocol_read() {
return on_proxy_protocol_finish();
}
StringRef ClientHandler::get_forwarded_by() {
auto &fwdconf = get_config()->http.forwarded;
if (fwdconf.by_node_type == FORWARDED_NODE_OBFUSCATED) {
return StringRef(fwdconf.by_obfuscated);
}
return StringRef(faddr_->hostport);
}
const std::string &ClientHandler::get_forwarded_for() const {
return forwarded_for_;
}
} // namespace shrpx

View File

@ -53,7 +53,7 @@ struct WorkerStat;
class ClientHandler {
public:
ClientHandler(Worker *worker, int fd, SSL *ssl, const char *ipaddr,
const char *port);
const char *port, int family, const UpstreamAddr *faddr);
~ClientHandler();
int noop();
@ -134,17 +134,31 @@ public:
void setup_upstream_io_callback();
// Returns string suitable for use in "by" parameter of Forwarded
// header field.
StringRef get_forwarded_by();
// Returns string suitable for use in "for" parameter of Forwarded
// header field.
const std::string &get_forwarded_for() const;
private:
Connection conn_;
ev_timer reneg_shutdown_timer_;
std::unique_ptr<Upstream> upstream_;
std::unique_ptr<std::vector<ssize_t>> pinned_http2sessions_;
// IP address of client. If UNIX domain socket is used, this is
// "localhost".
std::string ipaddr_;
std::string port_;
// The ALPN identifier negotiated for this connection.
std::string alpn_;
// The client address used in "for" parameter of Forwarded header
// field.
std::string forwarded_for_;
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_;

View File

@ -71,6 +71,8 @@ Config *mod_config() { return config; }
void create_config() { config = new Config(); }
std::string EMPTY_STRING;
TicketKeys::~TicketKeys() {
/* Erase keys from memory */
for (auto &key : keys) {
@ -79,8 +81,10 @@ TicketKeys::~TicketKeys() {
}
DownstreamAddr::DownstreamAddr(const DownstreamAddr &other)
: addr(other.addr), host(strcopy(other.host)),
hostport(strcopy(other.hostport)), port(other.port),
: addr(other.addr),
host(other.host),
hostport(other.hostport),
port(other.port),
host_unix(other.host_unix) {}
DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
@ -89,8 +93,8 @@ DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
}
addr = other.addr;
host = strcopy(other.host);
hostport = strcopy(other.hostport);
host = other.host;
hostport = other.hostport;
port = other.port;
host_unix = other.host_unix;
@ -543,7 +547,8 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
}
if (literal_start < var_start) {
res.emplace_back(SHRPX_LOGF_LITERAL, strcopy(literal_start, var_start));
res.emplace_back(SHRPX_LOGF_LITERAL,
ImmutableString(literal_start, var_start));
}
literal_start = p;
@ -553,17 +558,18 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
continue;
}
res.emplace_back(type, strcopy(value, var_name + var_namelen));
auto &v = res.back().value;
for (size_t i = 0; v[i]; ++i) {
if (v[i] == '_') {
v[i] = '-';
auto name = std::string(value, var_name + var_namelen);
for (auto &c : name) {
if (c == '_') {
c = '-';
}
}
res.emplace_back(type, ImmutableString(name));
}
if (literal_start != eop) {
res.emplace_back(SHRPX_LOGF_LITERAL, strcopy(literal_start, eop));
res.emplace_back(SHRPX_LOGF_LITERAL, ImmutableString(literal_start, eop));
}
return res;
@ -593,6 +599,8 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
// will append '/' to all patterns, so it becomes catch-all pattern.
auto mapping = util::split_config_str_list(src, ':');
assert(!mapping.empty());
auto &addr_groups = mod_config()->conn.downstream.addr_groups;
for (const auto &raw_pattern : mapping) {
auto done = false;
std::string pattern;
@ -607,7 +615,7 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
util::inp_strlower(pattern);
pattern += http2::normalize_path(slash, raw_pattern.second);
}
for (auto &g : mod_config()->downstream_addr_groups) {
for (auto &g : addr_groups) {
if (g.pattern.get() == pattern) {
g.addrs.push_back(addr);
done = true;
@ -621,19 +629,45 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
g.addrs.push_back(addr);
mod_config()->router.add_route(g.pattern.get(), strlen(g.pattern.get()),
get_config()->downstream_addr_groups.size());
addr_groups.size());
mod_config()->downstream_addr_groups.push_back(std::move(g));
addr_groups.push_back(std::move(g));
}
}
} // namespace
namespace {
int parse_forwarded_node_type(const std::string &optarg) {
if (util::strieq(optarg, "obfuscated")) {
return FORWARDED_NODE_OBFUSCATED;
}
if (util::strieq(optarg, "ip")) {
return FORWARDED_NODE_IP;
}
if (optarg.size() < 2 || optarg[0] != '_') {
return -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 -1;
}
return FORWARDED_NODE_OBFUSCATED;
}
} // namespace
// generated by gennghttpxfun.py
enum {
SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL,
SHRPX_OPTID_ACCESSLOG_FILE,
SHRPX_OPTID_ACCESSLOG_FORMAT,
SHRPX_OPTID_ACCESSLOG_SYSLOG,
SHRPX_OPTID_ADD_FORWARDED,
SHRPX_OPTID_ADD_REQUEST_HEADER,
SHRPX_OPTID_ADD_RESPONSE_HEADER,
SHRPX_OPTID_ADD_X_FORWARDED_FOR,
@ -642,6 +676,7 @@ enum {
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_CONNECTIONS_PER_WORKER,
SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS,
@ -652,6 +687,7 @@ enum {
SHRPX_OPTID_BACKEND_READ_TIMEOUT,
SHRPX_OPTID_BACKEND_REQUEST_BUFFER,
SHRPX_OPTID_BACKEND_RESPONSE_BUFFER,
SHRPX_OPTID_BACKEND_TLS_SESSION_CACHE_PER_WORKER,
SHRPX_OPTID_BACKEND_TLS_SNI_FIELD,
SHRPX_OPTID_BACKEND_WRITE_TIMEOUT,
SHRPX_OPTID_BACKLOG,
@ -669,6 +705,8 @@ enum {
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,
@ -690,8 +728,11 @@ enum {
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_MRUBY_FILE,
SHRPX_OPTID_NO_HOST_REWRITE,
SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST,
SHRPX_OPTID_NO_LOCATION_REWRITE,
SHRPX_OPTID_NO_OCSP,
SHRPX_OPTID_NO_SERVER_PUSH,
@ -704,9 +745,12 @@ enum {
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
SHRPX_OPTID_READ_BURST,
SHRPX_OPTID_READ_RATE,
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RLIMIT_NOFILE,
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,
@ -915,11 +959,19 @@ int option_lookup_token(const char *name, size_t namelen) {
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;
}
break;
case 'e':
if (util::strieq_l("dh-param-fil", name, 12)) {
return SHRPX_OPTID_DH_PARAM_FILE;
@ -931,6 +983,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_RLIMIT_NOFILE;
}
break;
case 'r':
if (util::strieq_l("forwarded-fo", name, 12)) {
return SHRPX_OPTID_FORWARDED_FOR;
}
break;
case 't':
if (util::strieq_l("verify-clien", name, 12)) {
return SHRPX_OPTID_VERIFY_CLIENT;
@ -1022,6 +1079,9 @@ int option_lookup_token(const char *name, size_t namelen) {
}
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;
}
@ -1166,6 +1226,9 @@ int option_lookup_token(const char *name, size_t namelen) {
case 24:
switch (name[23]) {
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;
}
@ -1196,6 +1259,9 @@ int option_lookup_token(const char *name, size_t namelen) {
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;
}
break;
@ -1205,11 +1271,17 @@ int option_lookup_token(const char *name, size_t namelen) {
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("no-http2-cipher-black-lis", name, 25)) {
return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST;
}
break;
}
break;
@ -1220,6 +1292,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED;
}
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;
@ -1239,6 +1316,11 @@ int option_lookup_token(const char *name, size_t namelen) {
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;
@ -1301,6 +1383,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("backend-http2-connections-per-worke", name, 35)) {
return SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER;
}
if (util::strieq_l("backend-tls-session-cache-per-worke", name, 35)) {
return SHRPX_OPTID_BACKEND_TLS_SESSION_CACHE_PER_WORKER;
}
break;
case 's':
if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) {
@ -1349,7 +1434,7 @@ int parse_config(const char *opt, const char *optarg,
DownstreamAddr addr;
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
addr.host = strcopy(path, pat_delim);
addr.host = ImmutableString(path, pat_delim);
addr.host_unix = true;
} else {
if (split_host_port(host, sizeof(host), &port, optarg,
@ -1357,7 +1442,7 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
addr.host = strcopy(host);
addr.host = ImmutableString(host);
addr.port = port;
}
@ -1373,11 +1458,17 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_FRONTEND: {
auto &listenerconf = mod_config()->conn.listener;
UpstreamAddr addr{};
addr.fd = -1;
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
mod_config()->host = strcopy(path);
mod_config()->port = 0;
mod_config()->host_unix = true;
addr.host = ImmutableString(path);
addr.host_unix = true;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}
@ -1387,9 +1478,26 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->host = strcopy(host);
mod_config()->port = port;
mod_config()->host_unix = false;
addr.host = ImmutableString(host);
addr.port = port;
if (util::numeric_host(host, AF_INET)) {
addr.family = AF_INET;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}
if (util::numeric_host(host, AF_INET6)) {
addr.family = AF_INET6;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}
addr.family = AF_INET;
listenerconf.addrs.push_back(addr);
addr.family = AF_INET6;
listenerconf.addrs.push_back(std::move(addr));
return 0;
}
@ -1401,7 +1509,7 @@ int parse_config(const char *opt, const char *optarg,
return parse_uint(&mod_config()->num_worker, opt, optarg);
#endif // !NOTHREADS
case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS:
return parse_uint(&mod_config()->http2_max_concurrent_streams, opt, optarg);
return parse_uint(&mod_config()->http2.max_concurrent_streams, opt, optarg);
case SHRPX_OPTID_LOG_LEVEL:
if (Log::set_severity_level_by_name(optarg) == -1) {
LOG(ERROR) << opt << ": Invalid severity level: " << optarg;
@ -1426,50 +1534,56 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_ADD_X_FORWARDED_FOR:
mod_config()->add_x_forwarded_for = util::strieq(optarg, "yes");
mod_config()->http.xff.add = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR:
mod_config()->strip_incoming_x_forwarded_for = util::strieq(optarg, "yes");
mod_config()->http.xff.strip_incoming = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_NO_VIA:
mod_config()->no_via = util::strieq(optarg, "yes");
mod_config()->http.no_via = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT:
return parse_duration(&mod_config()->http2_upstream_read_timeout, opt,
return parse_duration(&mod_config()->conn.upstream.timeout.http2_read, opt,
optarg);
case SHRPX_OPTID_FRONTEND_READ_TIMEOUT:
return parse_duration(&mod_config()->upstream_read_timeout, opt, optarg);
return parse_duration(&mod_config()->conn.upstream.timeout.read, opt,
optarg);
case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT:
return parse_duration(&mod_config()->upstream_write_timeout, opt, optarg);
return parse_duration(&mod_config()->conn.upstream.timeout.write, opt,
optarg);
case SHRPX_OPTID_BACKEND_READ_TIMEOUT:
return parse_duration(&mod_config()->downstream_read_timeout, opt, optarg);
return parse_duration(&mod_config()->conn.downstream.timeout.read, opt,
optarg);
case SHRPX_OPTID_BACKEND_WRITE_TIMEOUT:
return parse_duration(&mod_config()->downstream_write_timeout, opt, optarg);
return parse_duration(&mod_config()->conn.downstream.timeout.write, opt,
optarg);
case SHRPX_OPTID_STREAM_READ_TIMEOUT:
return parse_duration(&mod_config()->stream_read_timeout, opt, optarg);
return parse_duration(&mod_config()->http2.timeout.stream_read, opt,
optarg);
case SHRPX_OPTID_STREAM_WRITE_TIMEOUT:
return parse_duration(&mod_config()->stream_write_timeout, opt, optarg);
return parse_duration(&mod_config()->http2.timeout.stream_write, opt,
optarg);
case SHRPX_OPTID_ACCESSLOG_FILE:
mod_config()->accesslog_file = strcopy(optarg);
mod_config()->logging.access.file = strcopy(optarg);
return 0;
case SHRPX_OPTID_ACCESSLOG_SYSLOG:
mod_config()->accesslog_syslog = util::strieq(optarg, "yes");
mod_config()->logging.access.syslog = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_ACCESSLOG_FORMAT:
mod_config()->accesslog_format = parse_log_format(optarg);
mod_config()->logging.access.format = parse_log_format(optarg);
return 0;
case SHRPX_OPTID_ERRORLOG_FILE:
mod_config()->errorlog_file = strcopy(optarg);
mod_config()->logging.error.file = strcopy(optarg);
return 0;
case SHRPX_OPTID_ERRORLOG_SYSLOG:
mod_config()->errorlog_syslog = util::strieq(optarg, "yes");
mod_config()->logging.error.syslog = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_FASTOPEN: {
@ -1483,21 +1597,21 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->fastopen = n;
mod_config()->conn.listener.fastopen = n;
return 0;
}
case SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT:
return parse_duration(&mod_config()->downstream_idle_read_timeout, opt,
return parse_duration(&mod_config()->conn.downstream.timeout.idle_read, opt,
optarg);
case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS:
case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS: {
size_t *resp;
if (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS) {
resp = &mod_config()->http2_upstream_window_bits;
resp = &mod_config()->http2.upstream.window_bits;
} else {
resp = &mod_config()->http2_downstream_window_bits;
resp = &mod_config()->http2.downstream.window_bits;
}
errno = 0;
@ -1523,9 +1637,9 @@ int parse_config(const char *opt, const char *optarg,
size_t *resp;
if (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS) {
resp = &mod_config()->http2_upstream_connection_window_bits;
resp = &mod_config()->http2.upstream.connection_window_bits;
} else {
resp = &mod_config()->http2_downstream_connection_window_bits;
resp = &mod_config()->http2.downstream.connection_window_bits;
}
errno = 0;
@ -1547,15 +1661,15 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_FRONTEND_NO_TLS:
mod_config()->upstream_no_tls = util::strieq(optarg, "yes");
mod_config()->conn.upstream.no_tls = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_NO_TLS:
mod_config()->downstream_no_tls = util::strieq(optarg, "yes");
mod_config()->conn.downstream.no_tls = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD:
mod_config()->backend_tls_sni_name = strcopy(optarg);
mod_config()->tls.backend_sni_name = optarg;
return 0;
case SHRPX_OPTID_PID_FILE:
@ -1576,7 +1690,7 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_PRIVATE_KEY_FILE:
mod_config()->private_key_file = strcopy(optarg);
mod_config()->tls.private_key_file = strcopy(optarg);
return 0;
case SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE: {
@ -1585,16 +1699,16 @@ int parse_config(const char *opt, const char *optarg,
LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg;
return -1;
}
mod_config()->private_key_passwd = strcopy(passwd);
mod_config()->tls.private_key_passwd = strcopy(passwd);
return 0;
}
case SHRPX_OPTID_CERTIFICATE_FILE:
mod_config()->cert_file = strcopy(optarg);
mod_config()->tls.cert_file = strcopy(optarg);
return 0;
case SHRPX_OPTID_DH_PARAM_FILE:
mod_config()->dh_param_file = strcopy(optarg);
mod_config()->tls.dh_param_file = strcopy(optarg);
return 0;
case SHRPX_OPTID_SUBCERT: {
@ -1603,7 +1717,7 @@ int parse_config(const char *opt, const char *optarg,
if (sp) {
std::string keyfile(optarg, sp);
// TODO Do we need private key for subcert?
mod_config()->subcerts.emplace_back(keyfile, sp + 1);
mod_config()->tls.subcerts.emplace_back(keyfile, sp + 1);
}
return 0;
@ -1614,7 +1728,7 @@ int parse_config(const char *opt, const char *optarg,
LOG(ERROR) << opt << ": Unknown syslog facility: " << optarg;
return -1;
}
mod_config()->syslog_facility = facility;
mod_config()->logging.syslog_facility = facility;
return 0;
}
@ -1630,12 +1744,12 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->backlog = n;
mod_config()->conn.listener.backlog = n;
return 0;
}
case SHRPX_OPTID_CIPHERS:
mod_config()->ciphers = strcopy(optarg);
mod_config()->tls.ciphers = strcopy(optarg);
return 0;
case SHRPX_OPTID_CLIENT:
@ -1643,22 +1757,26 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_INSECURE:
mod_config()->insecure = util::strieq(optarg, "yes");
mod_config()->tls.insecure = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_CACERT:
mod_config()->cacert = strcopy(optarg);
mod_config()->tls.cacert = strcopy(optarg);
return 0;
case SHRPX_OPTID_BACKEND_IPV4:
mod_config()->backend_ipv4 = util::strieq(optarg, "yes");
mod_config()->conn.downstream.ipv4 = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_IPV6:
mod_config()->backend_ipv6 = util::strieq(optarg, "yes");
mod_config()->conn.downstream.ipv6 = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_HTTP_PROXY_URI: {
auto &proxy = mod_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, strlen(optarg), 0, &u);
@ -1669,19 +1787,17 @@ int parse_config(const char *opt, const char *optarg,
// Surprisingly, u.field_set & UF_USERINFO is nonzero even if
// userinfo component is empty string.
if (!val.empty()) {
val = util::percent_decode(std::begin(val), std::end(val));
mod_config()->downstream_http_proxy_userinfo = strcopy(val);
proxy.userinfo = util::percent_decode(std::begin(val), std::end(val));
}
}
if (u.field_set & UF_HOST) {
http2::copy_url_component(val, &u, UF_HOST, optarg);
mod_config()->downstream_http_proxy_host = strcopy(val);
http2::copy_url_component(proxy.host, &u, UF_HOST, optarg);
} else {
LOG(ERROR) << opt << ": no hostname specified";
return -1;
}
if (u.field_set & UF_PORT) {
mod_config()->downstream_http_proxy_port = u.port;
proxy.port = u.port;
} else {
LOG(ERROR) << opt << ": no port specified";
return -1;
@ -1694,63 +1810,70 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_READ_RATE:
return parse_uint_with_unit(&mod_config()->read_rate, opt, optarg);
return parse_uint_with_unit(
&mod_config()->conn.upstream.ratelimit.read.rate, opt, optarg);
case SHRPX_OPTID_READ_BURST:
return parse_uint_with_unit(&mod_config()->read_burst, opt, optarg);
return parse_uint_with_unit(
&mod_config()->conn.upstream.ratelimit.read.burst, opt, optarg);
case SHRPX_OPTID_WRITE_RATE:
return parse_uint_with_unit(&mod_config()->write_rate, opt, optarg);
return parse_uint_with_unit(
&mod_config()->conn.upstream.ratelimit.write.rate, opt, optarg);
case SHRPX_OPTID_WRITE_BURST:
return parse_uint_with_unit(&mod_config()->write_burst, opt, optarg);
return parse_uint_with_unit(
&mod_config()->conn.upstream.ratelimit.write.burst, opt, optarg);
case SHRPX_OPTID_WORKER_READ_RATE:
LOG(WARN) << opt << ": not implemented yet";
return parse_uint_with_unit(&mod_config()->worker_read_rate, opt, optarg);
return 0;
case SHRPX_OPTID_WORKER_READ_BURST:
LOG(WARN) << opt << ": not implemented yet";
return parse_uint_with_unit(&mod_config()->worker_read_burst, opt, optarg);
return 0;
case SHRPX_OPTID_WORKER_WRITE_RATE:
LOG(WARN) << opt << ": not implemented yet";
return parse_uint_with_unit(&mod_config()->worker_write_rate, opt, optarg);
return 0;
case SHRPX_OPTID_WORKER_WRITE_BURST:
LOG(WARN) << opt << ": not implemented yet";
return parse_uint_with_unit(&mod_config()->worker_write_burst, opt, optarg);
return 0;
case SHRPX_OPTID_NPN_LIST:
mod_config()->npn_list = util::parse_config_str_list(optarg);
mod_config()->tls.npn_list = util::parse_config_str_list(optarg);
return 0;
case SHRPX_OPTID_TLS_PROTO_LIST:
mod_config()->tls_proto_list = util::parse_config_str_list(optarg);
mod_config()->tls.tls_proto_list = util::parse_config_str_list(optarg);
return 0;
case SHRPX_OPTID_VERIFY_CLIENT:
mod_config()->verify_client = util::strieq(optarg, "yes");
mod_config()->tls.client_verify.enabled = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_VERIFY_CLIENT_CACERT:
mod_config()->verify_client_cacert = strcopy(optarg);
mod_config()->tls.client_verify.cacert = strcopy(optarg);
return 0;
case SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE:
mod_config()->client_private_key_file = strcopy(optarg);
mod_config()->tls.client.private_key_file = strcopy(optarg);
return 0;
case SHRPX_OPTID_CLIENT_CERT_FILE:
mod_config()->client_cert_file = strcopy(optarg);
mod_config()->tls.client.cert_file = strcopy(optarg);
return 0;
case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER:
mod_config()->http2_upstream_dump_request_header_file = strcopy(optarg);
mod_config()->http2.upstream.debug.dump.request_header_file =
strcopy(optarg);
return 0;
case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER:
mod_config()->http2_upstream_dump_response_header_file = strcopy(optarg);
mod_config()->http2.upstream.debug.dump.response_header_file =
strcopy(optarg);
return 0;
case SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING:
mod_config()->http2_no_cookie_crumbling = util::strieq(optarg, "yes");
mod_config()->http2.no_cookie_crumbling = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_FRONTEND_FRAME_DEBUG:
mod_config()->upstream_frame_debug = util::strieq(optarg, "yes");
mod_config()->http2.upstream.debug.frame_debug =
util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_PADDING:
@ -1797,7 +1920,7 @@ int parse_config(const char *opt, const char *optarg,
}
}
mod_config()->altsvcs.push_back(std::move(altsvc));
mod_config()->http.altsvcs.push_back(std::move(altsvc));
return 0;
}
@ -1809,16 +1932,17 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {
mod_config()->add_request_headers.push_back(std::move(p));
mod_config()->http.add_request_headers.push_back(std::move(p));
} else {
mod_config()->add_response_headers.push_back(std::move(p));
mod_config()->http.add_response_headers.push_back(std::move(p));
}
return 0;
}
case SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS:
return parse_uint(&mod_config()->worker_frontend_connections, opt, optarg);
return parse_uint(&mod_config()->conn.upstream.worker_connections, opt,
optarg);
case SHRPX_OPTID_NO_LOCATION_REWRITE:
mod_config()->no_location_rewrite = util::strieq(optarg, "yes");
mod_config()->http.no_location_rewrite = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_NO_HOST_REWRITE:
@ -1841,17 +1965,18 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->downstream_connections_per_host = n;
mod_config()->conn.downstream.connections_per_host = n;
return 0;
}
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND:
return parse_uint(&mod_config()->downstream_connections_per_frontend, opt,
optarg);
return parse_uint(&mod_config()->conn.downstream.connections_per_frontend,
opt, optarg);
case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT:
return parse_duration(&mod_config()->listener_disable_timeout, opt, optarg);
return parse_duration(&mod_config()->conn.listener.timeout.sleep, opt,
optarg);
case SHRPX_OPTID_TLS_TICKET_KEY_FILE:
mod_config()->tls_ticket_key_files.push_back(optarg);
mod_config()->tls.ticket.files.push_back(optarg);
return 0;
case SHRPX_OPTID_RLIMIT_NOFILE: {
int n;
@ -1884,36 +2009,50 @@ int parse_config(const char *opt, const char *optarg,
}
if (optid == SHRPX_OPTID_BACKEND_REQUEST_BUFFER) {
mod_config()->downstream_request_buffer_size = n;
mod_config()->conn.downstream.request_buffer_size = n;
} else {
mod_config()->downstream_response_buffer_size = n;
mod_config()->conn.downstream.response_buffer_size = n;
}
return 0;
}
case SHRPX_OPTID_NO_SERVER_PUSH:
mod_config()->no_server_push = util::strieq(optarg, "yes");
mod_config()->http2.no_server_push = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER:
return parse_uint(&mod_config()->http2_downstream_connections_per_worker,
return parse_uint(&mod_config()->http2.downstream.connections_per_worker,
opt, optarg);
case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE:
mod_config()->fetch_ocsp_response_file = strcopy(optarg);
mod_config()->tls.ocsp.fetch_ocsp_response_file = strcopy(optarg);
return 0;
case SHRPX_OPTID_OCSP_UPDATE_INTERVAL:
return parse_duration(&mod_config()->ocsp_update_interval, opt, optarg);
return parse_duration(&mod_config()->tls.ocsp.update_interval, opt, optarg);
case SHRPX_OPTID_NO_OCSP:
mod_config()->no_ocsp = util::strieq(optarg, "yes");
mod_config()->tls.ocsp.disabled = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_HEADER_FIELD_BUFFER:
return parse_uint_with_unit(&mod_config()->header_field_buffer, opt,
optarg);
LOG(WARN) << opt
<< ": deprecated. Use request-header-field-buffer instead.";
// fall through
case SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER:
return parse_uint_with_unit(&mod_config()->http.request_header_field_buffer,
opt, optarg);
case SHRPX_OPTID_MAX_HEADER_FIELDS:
return parse_uint(&mod_config()->max_header_fields, opt, optarg);
LOG(WARN) << opt << ": deprecated. Use max-request-header-fields instead.";
// fall through
case SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS:
return parse_uint(&mod_config()->http.max_request_header_fields, opt,
optarg);
case SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER:
return parse_uint_with_unit(
&mod_config()->http.response_header_field_buffer, opt, optarg);
case SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS:
return parse_uint(&mod_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";
@ -1932,19 +2071,19 @@ int parse_config(const char *opt, const char *optarg,
}
case SHRPX_OPTID_TLS_TICKET_KEY_CIPHER:
if (util::strieq(optarg, "aes-128-cbc")) {
mod_config()->tls_ticket_key_cipher = EVP_aes_128_cbc();
mod_config()->tls.ticket.cipher = EVP_aes_128_cbc();
} else if (util::strieq(optarg, "aes-256-cbc")) {
mod_config()->tls_ticket_key_cipher = EVP_aes_256_cbc();
mod_config()->tls.ticket.cipher = EVP_aes_256_cbc();
} else {
LOG(ERROR) << opt
<< ": unsupported cipher for ticket encryption: " << optarg;
return -1;
}
mod_config()->tls_ticket_key_cipher_given = true;
mod_config()->tls.ticket.cipher_given = true;
return 0;
case SHRPX_OPTID_HOST_REWRITE:
mod_config()->no_host_rewrite = !util::strieq(optarg, "yes");
mod_config()->http.no_host_rewrite = !util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: {
@ -1953,8 +2092,9 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->session_cache_memcached_host = strcopy(host);
mod_config()->session_cache_memcached_port = port;
auto &memcachedconf = mod_config()->tls.session_cache.memcached;
memcachedconf.host = strcopy(host);
memcachedconf.port = port;
return 0;
}
@ -1964,13 +2104,14 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->tls_ticket_key_memcached_host = strcopy(host);
mod_config()->tls_ticket_key_memcached_port = port;
auto &memcachedconf = mod_config()->tls.ticket.memcached;
memcachedconf.host = strcopy(host);
memcachedconf.port = port;
return 0;
}
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL:
return parse_duration(&mod_config()->tls_ticket_key_memcached_interval, opt,
return parse_duration(&mod_config()->tls.ticket.memcached.interval, opt,
optarg);
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY: {
int n;
@ -1983,11 +2124,11 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->tls_ticket_key_memcached_max_retry = n;
mod_config()->tls.ticket.memcached.max_retry = n;
return 0;
}
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
return parse_uint(&mod_config()->tls_ticket_key_memcached_max_fail, opt,
return parse_uint(&mod_config()->tls.ticket.memcached.max_fail, opt,
optarg);
case SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD: {
size_t n;
@ -1995,13 +2136,13 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
mod_config()->tls_dyn_rec_warmup_threshold = n;
mod_config()->tls.dyn_rec.warmup_threshold = n;
return 0;
}
case SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT:
return parse_duration(&mod_config()->tls_dyn_rec_idle_timeout, opt, optarg);
return parse_duration(&mod_config()->tls.dyn_rec.idle_timeout, opt, optarg);
case SHRPX_OPTID_MRUBY_FILE:
#ifdef HAVE_MRUBY
@ -2012,9 +2153,82 @@ int parse_config(const char *opt, const char *optarg,
#endif // !HAVE_MRUBY
return 0;
case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL:
mod_config()->accept_proxy_protocol = util::strieq(optarg, "yes");
mod_config()->conn.upstream.accept_proxy_protocol =
util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_ADD_FORWARDED: {
auto &fwdconf = mod_config()->http.forwarded;
fwdconf.params = FORWARDED_NONE;
for (const auto &param : util::parse_config_str_list(optarg)) {
if (util::strieq(param, "by")) {
fwdconf.params |= FORWARDED_BY;
continue;
}
if (util::strieq(param, "for")) {
fwdconf.params |= FORWARDED_FOR;
continue;
}
if (util::strieq(param, "host")) {
fwdconf.params |= FORWARDED_HOST;
continue;
}
if (util::strieq(param, "proto")) {
fwdconf.params |= FORWARDED_PROTO;
continue;
}
LOG(ERROR) << opt << ": unknown parameter " << optarg;
return -1;
}
return 0;
}
case SHRPX_OPTID_STRIP_INCOMING_FORWARDED:
mod_config()->http.forwarded.strip_incoming = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_FORWARDED_BY:
case SHRPX_OPTID_FORWARDED_FOR: {
auto type = parse_forwarded_node_type(optarg);
if (type == -1 ||
(optid == SHRPX_OPTID_FORWARDED_FOR && optarg[0] == '_')) {
LOG(ERROR) << opt << ": unknown node type or illegal obfuscated string "
<< optarg;
return -1;
}
auto &fwdconf = mod_config()->http.forwarded;
switch (optid) {
case SHRPX_OPTID_FORWARDED_BY:
fwdconf.by_node_type = static_cast<shrpx_forwarded_node_type>(type);
if (optarg[0] == '_') {
fwdconf.by_obfuscated = optarg;
} else {
fwdconf.by_obfuscated = "";
}
break;
case SHRPX_OPTID_FORWARDED_FOR:
fwdconf.for_node_type = static_cast<shrpx_forwarded_node_type>(type);
break;
}
return 0;
}
case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST:
mod_config()->tls.no_http2_cipher_black_list = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_HTTP1_TLS:
mod_config()->conn.downstream.http1_tls = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_TLS_SESSION_CACHE_PER_WORKER:
return parse_uint(&mod_config()->tls.downstream_session_cache_per_worker,
opt, optarg);
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";

View File

@ -191,6 +191,26 @@ constexpr char SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD[] =
"tls-dyn-rec-warmup-threshold";
constexpr char SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT[] =
"tls-dyn-rec-idle-timeout";
constexpr char SHRPX_OPT_ADD_FORWARDED[] = "add-forwarded";
constexpr char SHRPX_OPT_STRIP_INCOMING_FORWARDED[] =
"strip-incoming-forwarded";
constexpr static char SHRPX_OPT_FORWARDED_BY[] = "forwarded-by";
constexpr char SHRPX_OPT_FORWARDED_FOR[] = "forwarded-for";
constexpr char SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER[] =
"request-header-field-buffer";
constexpr char SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS[] =
"max-request-header-fields";
constexpr char SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER[] =
"response-header-field-buffer";
constexpr char SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS[] =
"max-response-header-fields";
constexpr char SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST[] =
"no-http2-cipher-black-list";
constexpr char SHRPX_OPT_BACKEND_HTTP1_TLS[] = "backend-http1-tls";
constexpr char SHRPX_OPT_BACKEND_TLS_SESSION_CACHE_PER_WORKER[] =
"backend-tls-session-cache-per-worker";
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
union sockaddr_union {
sockaddr_storage storage;
@ -207,6 +227,23 @@ struct Address {
enum shrpx_proto { PROTO_HTTP2, PROTO_HTTP };
enum shrpx_forwarded_param {
FORWARDED_NONE = 0,
FORWARDED_BY = 0x1,
FORWARDED_FOR = 0x2,
FORWARDED_HOST = 0x4,
FORWARDED_PROTO = 0x8,
};
enum shrpx_forwarded_node_type {
FORWARDED_NODE_OBFUSCATED,
FORWARDED_NODE_IP,
};
// Used inside function if it has to return const reference to empty
// string without defining empty string each time.
extern std::string EMPTY_STRING;
struct AltSvc {
AltSvc() : port(0) {}
@ -215,6 +252,24 @@ struct AltSvc {
uint16_t port;
};
struct UpstreamAddr {
// The frontend address (e.g., FQDN, hostname, IP address). If
// |host_unix| is true, this is UNIX domain socket path.
ImmutableString 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".
ImmutableString 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;
// true if |host| contains UNIX domain socket path.
bool host_unix;
int fd;
};
struct DownstreamAddr {
DownstreamAddr() : addr{}, port(0), host_unix(false) {}
DownstreamAddr(const DownstreamAddr &other);
@ -225,8 +280,8 @@ struct DownstreamAddr {
Address addr;
// backend address. If |host_unix| is true, this is UNIX domain
// socket path.
std::unique_ptr<char[]> host;
std::unique_ptr<char[]> hostport;
ImmutableString host;
ImmutableString hostport;
// backend port. 0 if |host_unix| is true.
uint16_t port;
// true if |host| contains UNIX domain socket path.
@ -263,168 +318,265 @@ struct TicketKeys {
std::vector<TicketKey> keys;
};
struct Config {
struct HttpProxy {
Address addr;
// host in http proxy URI
std::string host;
// userinfo in http proxy URI, not percent-encoded form
std::string userinfo;
// port in http proxy URI
uint16_t port;
};
struct TLSConfig {
// RFC 5077 Session ticket related configurations
struct {
struct {
Address addr;
uint16_t port;
std::unique_ptr<char[]> host;
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;
} memcached;
std::vector<std::string> 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;
std::unique_ptr<char[]> host;
} memcached;
} session_cache;
// Dynamic record sizing configurations
struct {
size_t warmup_threshold;
ev_tstamp idle_timeout;
} dyn_rec;
// OCSP realted configurations
struct {
ev_tstamp update_interval;
std::unique_ptr<char[]> fetch_ocsp_response_file;
bool disabled;
} ocsp;
// Client verification configurations
struct {
// Path to file containing CA certificate solely used for client
// certificate validation
std::unique_ptr<char[]> cacert;
bool enabled;
} client_verify;
// Client private key and certificate used in backend connections.
struct {
std::unique_ptr<char[]> private_key_file;
std::unique_ptr<char[]> cert_file;
} client;
// The list of (private key file, certificate file) pair
std::vector<std::pair<std::string, std::string>> subcerts;
std::vector<AltSvc> altsvcs;
std::vector<std::pair<std::string, std::string>> add_request_headers;
std::vector<std::pair<std::string, std::string>> add_response_headers;
std::vector<unsigned char> alpn_prefs;
std::vector<LogFragment> accesslog_format;
std::vector<DownstreamAddrGroup> downstream_addr_groups;
std::vector<std::string> tls_ticket_key_files;
// list of supported NPN/ALPN protocol strings in the order of
// preference.
std::vector<std::string> npn_list;
// list of supported SSL/TLS protocol strings.
std::vector<std::string> tls_proto_list;
// binary form of http proxy host and port
Address downstream_http_proxy_addr;
Address session_cache_memcached_addr;
Address tls_ticket_key_memcached_addr;
Router router;
std::chrono::seconds tls_session_timeout;
ev_tstamp http2_upstream_read_timeout;
ev_tstamp upstream_read_timeout;
ev_tstamp upstream_write_timeout;
ev_tstamp downstream_read_timeout;
ev_tstamp downstream_write_timeout;
ev_tstamp stream_read_timeout;
ev_tstamp stream_write_timeout;
ev_tstamp downstream_idle_read_timeout;
ev_tstamp listener_disable_timeout;
ev_tstamp ocsp_update_interval;
ev_tstamp tls_ticket_key_memcached_interval;
// address of frontend connection. This could be a path to UNIX
// domain socket. In this case, |host_unix| must be true.
std::unique_ptr<char[]> host;
size_t downstream_session_cache_per_worker;
// Bit mask to disable SSL/TLS protocol versions. This will be
// passed to SSL_CTX_set_options().
long int tls_proto_mask;
std::string backend_sni_name;
std::chrono::seconds session_timeout;
std::unique_ptr<char[]> private_key_file;
std::unique_ptr<char[]> private_key_passwd;
std::unique_ptr<char[]> cert_file;
std::unique_ptr<char[]> dh_param_file;
std::unique_ptr<char[]> backend_tls_sni_name;
std::unique_ptr<char[]> pid_file;
std::unique_ptr<char[]> conf_path;
std::unique_ptr<char[]> ciphers;
std::unique_ptr<char[]> cacert;
// userinfo in http proxy URI, not percent-encoded form
std::unique_ptr<char[]> downstream_http_proxy_userinfo;
// host in http proxy URI
std::unique_ptr<char[]> downstream_http_proxy_host;
std::unique_ptr<char[]> http2_upstream_dump_request_header_file;
std::unique_ptr<char[]> http2_upstream_dump_response_header_file;
// // Rate limit configuration per connection
// ev_token_bucket_cfg *rate_limit_cfg;
// // Rate limit configuration per worker (thread)
// ev_token_bucket_cfg *worker_rate_limit_cfg;
// Path to file containing CA certificate solely used for client
// certificate validation
std::unique_ptr<char[]> verify_client_cacert;
std::unique_ptr<char[]> client_private_key_file;
std::unique_ptr<char[]> client_cert_file;
std::unique_ptr<char[]> accesslog_file;
std::unique_ptr<char[]> errorlog_file;
std::unique_ptr<char[]> fetch_ocsp_response_file;
bool insecure;
bool no_http2_cipher_black_list;
};
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.
std::string 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.
shrpx_forwarded_node_type by_node_type;
// type of value recorded in "for" parameter of Forwarded header
// field.
shrpx_forwarded_node_type for_node_type;
bool strip_incoming;
} forwarded;
struct {
bool add;
bool strip_incoming;
} xff;
std::vector<AltSvc> altsvcs;
std::vector<std::pair<std::string, std::string>> add_request_headers;
std::vector<std::pair<std::string, std::string>> add_response_headers;
StringRef server_name;
size_t request_header_field_buffer;
size_t max_request_header_fields;
size_t response_header_field_buffer;
size_t max_response_header_fields;
bool no_via;
bool no_location_rewrite;
bool no_host_rewrite;
};
struct Http2Config {
struct {
struct {
struct {
std::unique_ptr<char[]> request_header_file;
std::unique_ptr<char[]> response_header_file;
FILE *request_header;
FILE *response_header;
} dump;
bool frame_debug;
} debug;
nghttp2_option *option;
nghttp2_session_callbacks *callbacks;
size_t window_bits;
size_t connection_window_bits;
} upstream;
struct {
nghttp2_option *option;
nghttp2_session_callbacks *callbacks;
size_t window_bits;
size_t connection_window_bits;
size_t connections_per_worker;
} downstream;
struct {
ev_tstamp stream_read;
ev_tstamp stream_write;
} timeout;
size_t max_concurrent_streams;
bool no_cookie_crumbling;
bool no_server_push;
};
struct LoggingConfig {
struct {
std::vector<LogFragment> format;
std::unique_ptr<char[]> file;
// Send accesslog to syslog, ignoring accesslog_file.
bool syslog;
} access;
struct {
std::unique_ptr<char[]> file;
// Send errorlog to syslog, ignoring errorlog_file.
bool syslog;
} error;
int syslog_facility;
};
struct RateLimitConfig {
size_t rate;
size_t burst;
};
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;
struct {
struct {
ev_tstamp http2_read;
ev_tstamp read;
ev_tstamp write;
} timeout;
struct {
RateLimitConfig read;
RateLimitConfig write;
} ratelimit;
size_t worker_connections;
bool no_tls;
bool accept_proxy_protocol;
} upstream;
struct {
struct {
ev_tstamp read;
ev_tstamp write;
ev_tstamp idle_read;
} timeout;
std::vector<DownstreamAddrGroup> 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;
// downstream protocol; this will be determined by given options.
shrpx_proto proto;
bool no_tls;
bool http1_tls;
// true if IPv4 only; ipv4 and ipv6 are mutually exclusive; and
// (ipv4 && ipv6) must be false.
bool ipv4;
// true if IPv6 only
bool ipv6;
} downstream;
};
struct Config {
Router router;
HttpProxy downstream_http_proxy;
HttpConfig http;
Http2Config http2;
TLSConfig tls;
LoggingConfig logging;
ConnectionConfig conn;
std::unique_ptr<char[]> pid_file;
std::unique_ptr<char[]> conf_path;
std::unique_ptr<char[]> user;
std::unique_ptr<char[]> session_cache_memcached_host;
std::unique_ptr<char[]> tls_ticket_key_memcached_host;
std::unique_ptr<char[]> mruby_file;
FILE *http2_upstream_dump_request_header;
FILE *http2_upstream_dump_response_header;
nghttp2_session_callbacks *http2_upstream_callbacks;
nghttp2_session_callbacks *http2_downstream_callbacks;
nghttp2_option *http2_option;
nghttp2_option *http2_client_option;
const EVP_CIPHER *tls_ticket_key_cipher;
const char *server_name;
char **original_argv;
char **argv;
char *cwd;
size_t num_worker;
size_t http2_max_concurrent_streams;
size_t http2_upstream_window_bits;
size_t http2_downstream_window_bits;
size_t http2_upstream_connection_window_bits;
size_t http2_downstream_connection_window_bits;
size_t http2_downstream_connections_per_worker;
size_t downstream_connections_per_host;
size_t downstream_connections_per_frontend;
size_t read_rate;
size_t read_burst;
size_t write_rate;
size_t write_burst;
size_t worker_read_rate;
size_t worker_read_burst;
size_t worker_write_rate;
size_t worker_write_burst;
size_t padding;
size_t worker_frontend_connections;
size_t rlimit_nofile;
size_t downstream_request_buffer_size;
size_t downstream_response_buffer_size;
size_t header_field_buffer;
size_t max_header_fields;
// The index of catch-all group in downstream_addr_groups.
size_t downstream_addr_group_catch_all;
// Maximum number of retries when getting TLS ticket key from
// mamcached, due to network error.
size_t tls_ticket_key_memcached_max_retry;
// Maximum number of consecutive error from memcached, when this
// limit reached, TLS ticket is disabled.
size_t tls_ticket_key_memcached_max_fail;
// Bit mask to disable SSL/TLS protocol versions. This will be
// passed to SSL_CTX_set_options().
long int tls_proto_mask;
// downstream protocol; this will be determined by given options.
shrpx_proto downstream_proto;
int syslog_facility;
int backlog;
int argc;
int fastopen;
uid_t uid;
gid_t gid;
pid_t pid;
// frontend listening port. 0 if frontend listens on UNIX domain
// socket, in this case |host_unix| must be true.
uint16_t port;
// port in http proxy URI
uint16_t downstream_http_proxy_port;
uint16_t session_cache_memcached_port;
uint16_t tls_ticket_key_memcached_port;
bool verbose;
bool daemon;
bool verify_client;
bool http2_proxy;
bool http2_bridge;
bool client_proxy;
bool add_x_forwarded_for;
bool strip_incoming_x_forwarded_for;
bool no_via;
bool upstream_no_tls;
bool downstream_no_tls;
// Send accesslog to syslog, ignoring accesslog_file.
bool accesslog_syslog;
// Send errorlog to syslog, ignoring errorlog_file.
bool errorlog_syslog;
bool client;
// true if --client or --client-proxy are enabled.
bool client_mode;
bool insecure;
bool backend_ipv4;
bool backend_ipv6;
bool http2_no_cookie_crumbling;
bool upstream_frame_debug;
bool no_location_rewrite;
bool no_host_rewrite;
bool no_server_push;
// true if host contains UNIX domain socket path
bool host_unix;
bool no_ocsp;
// true if --tls-ticket-key-cipher is used
bool tls_ticket_key_cipher_given;
bool accept_proxy_protocol;
size_t tls_dyn_rec_warmup_threshold;
ev_tstamp tls_dyn_rec_idle_timeout;
};
const Config *get_config();

View File

@ -76,82 +76,82 @@ void test_shrpx_config_parse_log_format(void) {
CU_ASSERT(SHRPX_LOGF_REMOTE_ADDR == res[0].type);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[1].type);
CU_ASSERT(0 == strcmp(" - $remote_user [", res[1].value.get()));
CU_ASSERT(" - $remote_user [" == res[1].value);
CU_ASSERT(SHRPX_LOGF_TIME_LOCAL == res[2].type);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[3].type);
CU_ASSERT(0 == strcmp("] \"", res[3].value.get()));
CU_ASSERT("] \"" == res[3].value);
CU_ASSERT(SHRPX_LOGF_REQUEST == res[4].type);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[5].type);
CU_ASSERT(0 == strcmp("\" ", res[5].value.get()));
CU_ASSERT("\" " == res[5].value);
CU_ASSERT(SHRPX_LOGF_STATUS == res[6].type);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[7].type);
CU_ASSERT(0 == strcmp(" ", res[7].value.get()));
CU_ASSERT(" " == res[7].value);
CU_ASSERT(SHRPX_LOGF_BODY_BYTES_SENT == res[8].type);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[9].type);
CU_ASSERT(0 == strcmp(" \"", res[9].value.get()));
CU_ASSERT(" \"" == res[9].value);
CU_ASSERT(SHRPX_LOGF_HTTP == res[10].type);
CU_ASSERT(0 == strcmp("referer", res[10].value.get()));
CU_ASSERT("referer" == res[10].value);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[11].type);
CU_ASSERT(0 == strcmp("\" ", res[11].value.get()));
CU_ASSERT("\" " == res[11].value);
CU_ASSERT(SHRPX_LOGF_AUTHORITY == res[12].type);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[13].type);
CU_ASSERT(0 == strcmp(" \"", res[13].value.get()));
CU_ASSERT(" \"" == res[13].value);
CU_ASSERT(SHRPX_LOGF_HTTP == res[14].type);
CU_ASSERT(0 == strcmp("user-agent", res[14].value.get()));
CU_ASSERT("user-agent" == res[14].value);
CU_ASSERT(SHRPX_LOGF_LITERAL == res[15].type);
CU_ASSERT(0 == strcmp("\"", res[15].value.get()));
CU_ASSERT("\"" == res[15].value);
res = parse_log_format("$");
CU_ASSERT(1 == res.size());
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
CU_ASSERT(0 == strcmp("$", res[0].value.get()));
CU_ASSERT("$" == res[0].value);
res = parse_log_format("${");
CU_ASSERT(1 == res.size());
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
CU_ASSERT(0 == strcmp("${", res[0].value.get()));
CU_ASSERT("${" == res[0].value);
res = parse_log_format("${a");
CU_ASSERT(1 == res.size());
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
CU_ASSERT(0 == strcmp("${a", res[0].value.get()));
CU_ASSERT("${a" == res[0].value);
res = parse_log_format("${a ");
CU_ASSERT(1 == res.size());
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
CU_ASSERT(0 == strcmp("${a ", res[0].value.get()));
CU_ASSERT("${a " == res[0].value);
res = parse_log_format("$$remote_addr");
CU_ASSERT(2 == res.size());
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
CU_ASSERT(0 == strcmp("$", res[0].value.get()));
CU_ASSERT("$" == res[0].value);
CU_ASSERT(SHRPX_LOGF_REMOTE_ADDR == res[1].type);
CU_ASSERT(nullptr == res[1].value.get());
CU_ASSERT("" == res[1].value);
}
void test_shrpx_config_read_tls_ticket_key_file(void) {

View File

@ -42,15 +42,21 @@ using namespace nghttp2;
namespace shrpx {
Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
MemchunkPool *mcpool, ev_tstamp write_timeout,
ev_tstamp read_timeout, size_t write_rate,
size_t write_burst, size_t read_rate, size_t read_burst,
IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data,
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)
: tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool)},
wlimit(loop, &wev, write_rate, write_burst),
rlimit(loop, &rev, read_rate, read_burst, this), writecb(writecb),
readcb(readcb), timeoutcb(timeoutcb), loop(loop), data(data), fd(fd),
wlimit(loop, &wev, write_limit.rate, write_limit.burst),
rlimit(loop, &rev, read_limit.rate, read_limit.burst, this),
writecb(writecb),
readcb(readcb),
timeoutcb(timeoutcb),
loop(loop),
data(data),
fd(fd),
tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold),
tls_dyn_rec_idle_timeout(tls_dyn_rec_idle_timeout) {
@ -482,10 +488,17 @@ int Connection::check_http2_requirement() {
!util::check_h2_is_selected(next_proto, next_proto_len)) {
return 0;
}
if (!nghttp2::ssl::check_http2_requirement(tls.ssl)) {
if (!nghttp2::ssl::check_http2_tls_version(tls.ssl)) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "TLSv1.2 and/or black listed cipher suite was negotiated. "
"HTTP/2 must not be used.";
LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be used.";
}
return -1;
}
if (!get_config()->tls.no_http2_cipher_black_list &&
nghttp2::ssl::check_http2_cipher_black_list(tls.ssl)) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "The negotiated cipher suite is in HTTP/2 cipher suite "
"black list. HTTP/2 must not be used.";
}
return -1;
}

View File

@ -73,10 +73,10 @@ 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, size_t write_rate,
size_t write_burst, size_t read_rate, size_t read_burst,
IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data,
size_t tls_dyn_rec_warmup_threshold,
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);
~Connection();

View File

@ -102,10 +102,17 @@ void thread_join_async_cb(struct ev_loop *loop, ev_async *w, int revent) {
}
} // namespace
namespace {
std::random_device rd;
} // namespace
ConnectionHandler::ConnectionHandler(struct ev_loop *loop)
: single_worker_(nullptr), loop_(loop),
: gen_(rd()),
single_worker_(nullptr),
loop_(loop),
tls_ticket_key_memcached_get_retry_count_(0),
tls_ticket_key_memcached_fail_count_(0), worker_round_robin_cnt_(0),
tls_ticket_key_memcached_fail_count_(0),
worker_round_robin_cnt_(0),
graceful_shutdown_(false) {
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
disable_acceptor_timer_.data = this;
@ -290,19 +297,20 @@ void ConnectionHandler::graceful_shutdown_worker() {
#endif // NOTHREADS
}
int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen,
const UpstreamAddr *faddr) {
if (LOG_ENABLED(INFO)) {
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
}
if (get_config()->num_worker == 1) {
auto &upstreamconf = get_config()->conn.upstream;
if (single_worker_->get_worker_stat()->num_connections >=
get_config()->worker_frontend_connections) {
upstreamconf.worker_connections) {
if (LOG_ENABLED(INFO)) {
LLOG(INFO, this) << "Too many connections >="
<< get_config()->worker_frontend_connections;
<< upstreamconf.worker_connections;
}
close(fd);
@ -310,7 +318,7 @@ int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
}
auto client =
ssl::accept_connection(single_worker_.get(), fd, addr, addrlen);
ssl::accept_connection(single_worker_.get(), fd, addr, addrlen, faddr);
if (!client) {
LLOG(ERROR, this) << "ClientHandler creation failed";
@ -331,6 +339,7 @@ int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
wev.client_fd = fd;
memcpy(&wev.client_addr, addr, addrlen);
wev.client_addrlen = addrlen;
wev.faddr = faddr;
workers_[idx]->send(wev);
@ -345,43 +354,23 @@ Worker *ConnectionHandler::get_single_worker() const {
return single_worker_.get();
}
void ConnectionHandler::set_acceptor(std::unique_ptr<AcceptHandler> h) {
acceptor_ = std::move(h);
}
AcceptHandler *ConnectionHandler::get_acceptor() const {
return acceptor_.get();
}
void ConnectionHandler::set_acceptor6(std::unique_ptr<AcceptHandler> h) {
acceptor6_ = std::move(h);
}
AcceptHandler *ConnectionHandler::get_acceptor6() const {
return acceptor6_.get();
void ConnectionHandler::add_acceptor(std::unique_ptr<AcceptHandler> h) {
acceptors_.push_back(std::move(h));
}
void ConnectionHandler::enable_acceptor() {
if (acceptor_) {
acceptor_->enable();
}
if (acceptor6_) {
acceptor6_->enable();
for (auto &a : acceptors_) {
a->enable();
}
}
void ConnectionHandler::disable_acceptor() {
if (acceptor_) {
acceptor_->disable();
}
if (acceptor6_) {
acceptor6_->disable();
for (auto &a : acceptors_) {
a->disable();
}
}
void ConnectionHandler::disable_acceptor_temporary(ev_tstamp t) {
void ConnectionHandler::sleep_acceptor(ev_tstamp t) {
if (t == 0. || ev_is_active(&disable_acceptor_timer_)) {
return;
}
@ -393,11 +382,8 @@ void ConnectionHandler::disable_acceptor_temporary(ev_tstamp t) {
}
void ConnectionHandler::accept_pending_connection() {
if (acceptor_) {
acceptor_->accept_connection();
}
if (acceptor6_) {
acceptor6_->accept_connection();
for (auto &a : acceptors_) {
a->accept_connection();
}
}
@ -446,7 +432,7 @@ int ConnectionHandler::start_ocsp_update(const char *cert_file) {
assert(!ev_is_active(&ocsp_.chldev));
char *const argv[] = {
const_cast<char *>(get_config()->fetch_ocsp_response_file.get()),
const_cast<char *>(get_config()->tls.ocsp.fetch_ocsp_response_file.get()),
const_cast<char *>(cert_file), nullptr};
char *const envp[] = {nullptr};
@ -630,7 +616,7 @@ void ConnectionHandler::proceed_next_cert_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()->ocsp_update_interval, 0.);
ev_timer_set(&ocsp_timer_, get_config()->tls.ocsp.update_interval, 0.);
ev_timer_start(loop_, &ocsp_timer_);
return;
}
@ -667,13 +653,9 @@ ConnectionHandler::get_tls_ticket_key_memcached_dispatcher() const {
return tls_ticket_key_memcached_dispatcher_.get();
}
namespace {
std::random_device rd;
} // namespace
void ConnectionHandler::on_tls_ticket_key_network_error(ev_timer *w) {
if (++tls_ticket_key_memcached_get_retry_count_ >=
get_config()->tls_ticket_key_memcached_max_retry) {
get_config()->tls.ticket.memcached.max_retry) {
LOG(WARN) << "Memcached: tls ticket get retry all failed "
<< tls_ticket_key_memcached_get_retry_count_ << " times.";
@ -683,7 +665,7 @@ void ConnectionHandler::on_tls_ticket_key_network_error(ev_timer *w) {
auto dist = std::uniform_int_distribution<int>(
1, std::min(60, 1 << tls_ticket_key_memcached_get_retry_count_));
auto t = dist(rd);
auto t = dist(gen_);
LOG(WARN)
<< "Memcached: tls ticket get failed due to network error, retrying in "
@ -697,7 +679,7 @@ 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_key_memcached_max_fail) {
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;
@ -742,7 +724,7 @@ void ConnectionHandler::on_tls_ticket_key_get_success(
void ConnectionHandler::schedule_next_tls_ticket_key_memcached_get(
ev_timer *w) {
ev_timer_set(w, get_config()->tls_ticket_key_memcached_interval, 0.);
ev_timer_set(w, get_config()->tls.ticket.memcached.interval, 0.);
ev_timer_start(loop_, w);
}

View File

@ -34,6 +34,7 @@
#include <memory>
#include <vector>
#include <random>
#ifndef NOTHREADS
#include <future>
#endif // NOTHREADS
@ -57,6 +58,7 @@ class Worker;
struct WorkerStat;
struct TicketKeys;
class MemcachedDispatcher;
struct UpstreamAddr;
struct OCSPUpdateContext {
// ocsp response buffer
@ -78,7 +80,8 @@ class ConnectionHandler {
public:
ConnectionHandler(struct ev_loop *loop);
~ConnectionHandler();
int handle_connection(int fd, sockaddr *addr, int addrlen);
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.
@ -91,13 +94,10 @@ public:
const std::shared_ptr<TicketKeys> &get_ticket_keys() const;
struct ev_loop *get_loop() const;
Worker *get_single_worker() const;
void set_acceptor(std::unique_ptr<AcceptHandler> h);
AcceptHandler *get_acceptor() const;
void set_acceptor6(std::unique_ptr<AcceptHandler> h);
AcceptHandler *get_acceptor6() const;
void add_acceptor(std::unique_ptr<AcceptHandler> h);
void enable_acceptor();
void disable_acceptor();
void disable_acceptor_temporary(ev_tstamp t);
void sleep_acceptor(ev_tstamp t);
void accept_pending_connection();
void graceful_shutdown_worker();
void set_graceful_shutdown(bool f);
@ -139,6 +139,7 @@ private:
// Stores all SSL_CTX objects.
std::vector<SSL_CTX *> all_ssl_ctx_;
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.
@ -152,10 +153,7 @@ private:
// Worker object.
std::shared_ptr<TicketKeys> ticket_keys_;
struct ev_loop *loop_;
// acceptor for IPv4 address or UNIX domain socket.
std::unique_ptr<AcceptHandler> acceptor_;
// acceptor for IPv6 address
std::unique_ptr<AcceptHandler> acceptor6_;
std::vector<std::unique_ptr<AcceptHandler>> acceptors_;
#ifdef HAVE_NEVERBLEED
std::unique_ptr<neverbleed_t> nb_;
#endif // HAVE_NEVERBLEED

File diff suppressed because it is too large Load Diff

View File

@ -49,17 +49,174 @@ class Upstream;
class DownstreamConnection;
struct BlockedLink;
class FieldStore {
public:
FieldStore(size_t headers_initial_capacity)
: content_length(-1),
buffer_size_(0),
header_key_prev_(false),
trailer_key_prev_(false) {
http2::init_hdidx(hdidx_);
headers_.reserve(headers_initial_capacity);
}
const Headers &headers() const { return headers_; }
const Headers &trailers() const { return trailers_; }
Headers &headers() { return headers_; }
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.
// This function must be called after headers are indexed
const Headers::value_type *header(int16_t token) const;
Headers::value_type *header(int16_t token);
// Returns pointer to the header field with the name |name|. If no
// such header is found, returns nullptr.
const Headers::value_type *header(const StringRef &name) const;
void add_header(std::string name, std::string value);
void add_header(std::string name, std::string value, int16_t token);
void add_header(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen, bool no_index, int16_t token);
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_; }
// Lower the header field names and indexes header fields. If there
// is any invalid headers (e.g., multiple Content-Length having
// different values), returns -1.
int index_headers();
// Empties headers.
void clear_headers();
void add_trailer(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen, bool no_index, int16_t token);
void add_trailer(std::string name, std::string value);
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_; }
// content-length, -1 if it is unknown.
int64_t content_length;
private:
Headers headers_;
// trailer fields. For HTTP/1.1, trailer fields are only included
// with chunked encoding. For HTTP/2, there is no such limit.
Headers trailers_;
http2::HeaderIndex hdidx_;
// 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_;
};
struct Request {
Request()
: fs(16),
recv_body_length(0),
unconsumed_body_length(0),
method(-1),
http_major(1),
http_minor(1),
upgrade_request(false),
http2_upgrade_seen(false),
connection_close(false),
http2_expect_body(false),
no_authority(false) {}
void consume(size_t len) {
assert(unconsumed_body_length >= len);
unconsumed_body_length -= len;
}
FieldStore fs;
// Request scheme. For HTTP/2, this is :scheme header field value.
// For HTTP/1.1, this is deduced from URI or connection.
std::string 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.
std::string 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.
std::string 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;
// 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;
};
struct Response {
Response()
: fs(32),
recv_body_length(0),
unconsumed_body_length(0),
http_status(0),
http_major(1),
http_minor(1),
connection_close(false) {}
void consume(size_t len) {
assert(unconsumed_body_length >= len);
unconsumed_body_length -= len;
}
FieldStore fs;
// 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;
};
class Downstream {
public:
Downstream(Upstream *upstream, MemchunkPool *mcpool, int32_t stream_id,
int32_t priority);
Downstream(Upstream *upstream, MemchunkPool *mcpool, int32_t stream_id);
~Downstream();
void reset_upstream(Upstream *upstream);
Upstream *get_upstream() const;
void set_stream_id(int32_t stream_id);
int32_t get_stream_id() const;
void set_priority(int32_t pri);
int32_t get_priority() const;
void set_assoc_stream_id(int32_t stream_id);
int32_t get_assoc_stream_id() const;
void pause_read(IOCtrlReason reason);
int resume_read(IOCtrlReason reason, size_t consumed);
void force_resume_read();
@ -79,9 +236,6 @@ public:
// Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded.
// This should not depend on inspect_http1_response().
void check_upgrade_fulfilled();
// Returns true if the request is upgrade. Upgrade to HTTP/2 is
// excluded. For HTTP/2 Upgrade, check get_http2_upgrade_request().
bool get_upgrade_request() const;
// Returns true if the upgrade is succeded as a result of the call
// check_upgrade_fulfilled(). HTTP/2 Upgrade is excluded.
bool get_upgraded() const;
@ -94,99 +248,32 @@ public:
bool get_http2_upgrade_request() const;
// Returns the value of HTTP2-Settings request header field.
const std::string &get_http2_settings() const;
// downstream request API
const Headers &get_request_headers() const;
Headers &get_request_headers();
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);
void assemble_request_cookie();
const std::string &get_assembled_request_cookie() const;
// Lower the request header field names and indexes request headers.
// If there is any invalid headers (e.g., multiple Content-Length
// having different values), returns -1.
int index_request_headers();
// Returns pointer to the request header 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.
// This function must be called after headers are indexed
const Headers::value_type *get_request_header(int16_t token) const;
// Returns pointer to the request header with the name |name|. If
// no such header is found, returns nullptr.
const Headers::value_type *get_request_header(const std::string &name) const;
void add_request_header(std::string name, std::string value);
void set_last_request_header_value(const char *data, size_t len);
// Assembles request cookies. The opposite operation against
// crumble_request_cookie().
std::string assemble_request_cookie() const;
void add_request_header(std::string name, std::string value, int16_t token);
void add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index,
int16_t token);
bool get_request_header_key_prev() const;
void append_last_request_header_key(const char *data, size_t len);
void append_last_request_header_value(const char *data, size_t len);
// Empties request headers.
void clear_request_headers();
size_t get_request_headers_sum() const;
const Headers &get_request_trailers() const;
void add_request_trailer(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index,
int16_t token);
void add_request_trailer(std::string name, std::string value);
void set_last_request_trailer_value(const char *data, size_t len);
bool get_request_trailer_key_prev() const;
void append_last_request_trailer_key(const char *data, size_t len);
void append_last_request_trailer_value(const char *data, size_t len);
void set_request_method(int method);
int get_request_method() const;
void set_request_path(std::string path);
void add_request_headers_sum(size_t amount);
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;
void append_request_path(const char *data, size_t len);
// Returns request path. For HTTP/1.1, this is request-target. For
// HTTP/2, this is :path header field value. For CONNECT request,
// this is empty.
const std::string &get_request_path() const;
// Returns HTTP/2 :scheme header field value.
const std::string &get_request_http2_scheme() const;
void set_request_http2_scheme(std::string scheme);
// Returns :authority 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.
const std::string &get_request_http2_authority() const;
void set_request_http2_authority(std::string authority);
void append_request_http2_authority(const char *data, size_t len);
void set_request_major(int major);
void set_request_minor(int minor);
int get_request_major() const;
int get_request_minor() const;
int push_request_headers();
bool get_chunked_request() const;
void set_chunked_request(bool f);
bool get_request_connection_close() const;
void set_request_connection_close(bool f);
bool get_request_http2_expect_body() const;
void set_request_http2_expect_body(bool f);
int push_upload_data_chunk(const uint8_t *data, size_t datalen);
int end_upload_data();
size_t get_request_datalen() const;
void dec_request_datalen(size_t len);
void reset_request_datalen();
// Validates that received request body length and content-length
// matches.
bool validate_request_bodylen() const;
int64_t get_request_content_length() const;
void set_request_content_length(int64_t len);
bool request_pseudo_header_allowed(int16_t token) const;
bool validate_request_recv_body_length() const;
void set_request_downstream_host(std::string host);
bool expect_response_body() const;
enum {
@ -212,71 +299,24 @@ public:
bool get_request_pending() const;
// Returns true if request is ready to be submitted to downstream.
bool request_submission_ready() const;
// downstream response API
const Headers &get_response_headers() const;
Headers &get_response_headers();
// Lower the response header field names and indexes response
// headers. If there are invalid headers (e.g., multiple
// Content-Length with different values), returns -1.
int index_response_headers();
// Returns pointer to the response header 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.
// This function must be called after response headers are indexed.
const Headers::value_type *get_response_header(int16_t token) const;
Headers::value_type *get_response_header(int16_t token);
const Response &response() const { return resp_; }
Response &response() { return resp_; }
// Rewrites the location response header field.
void rewrite_location_response_header(const std::string &upstream_scheme);
void add_response_header(std::string name, std::string value);
void set_last_response_header_value(const char *data, size_t len);
void add_response_header(std::string name, std::string value, int16_t token);
void add_response_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index,
int16_t token);
bool get_response_header_key_prev() const;
void append_last_response_header_key(const char *data, size_t len);
void append_last_response_header_value(const char *data, size_t len);
// Empties response headers.
void clear_response_headers();
size_t get_response_headers_sum() const;
const Headers &get_response_trailers() const;
void add_response_trailer(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int16_t token);
void add_response_trailer(std::string name, std::string value);
void set_last_response_trailer_value(const char *data, size_t len);
bool get_response_trailer_key_prev() const;
void append_last_response_trailer_key(const char *data, size_t len);
void append_last_response_trailer_value(const char *data, size_t len);
unsigned int get_response_http_status() const;
void set_response_http_status(unsigned int status);
void set_response_major(int major);
void set_response_minor(int minor);
int get_response_major() const;
int get_response_minor() const;
int get_response_version() const;
bool get_chunked_response() const;
void set_chunked_response(bool f);
bool get_response_connection_close() const;
void set_response_connection_close(bool f);
void set_response_state(int state);
int get_response_state() const;
DefaultMemchunks *get_response_buf();
bool response_buf_full();
void add_response_bodylen(size_t amount);
int64_t get_response_bodylen() const;
void add_response_sent_bodylen(size_t amount);
int64_t get_response_sent_bodylen() const;
int64_t get_response_content_length() const;
void set_response_content_length(int64_t len);
// Validates that received response body length and content-length
// matches.
bool validate_response_bodylen() const;
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.
@ -288,22 +328,11 @@ public:
bool get_non_final_response() const;
void set_expect_final_response(bool f);
bool get_expect_final_response() const;
void add_response_datalen(size_t len);
void dec_response_datalen(size_t len);
size_t get_response_datalen() const;
void reset_response_datalen();
bool response_pseudo_header_allowed(int16_t token) const;
// Call this method when there is incoming data in downstream
// connection.
int on_read();
// Change the priority of downstream
int change_priority(int32_t pri);
bool get_rst_stream_after_end_stream() const;
void set_rst_stream_after_end_stream(bool f);
// 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();
@ -361,25 +390,19 @@ public:
Downstream *dlnext, *dlprev;
private:
Headers request_headers_;
Headers response_headers_;
// the length of response body sent to upstream client
int64_t response_sent_body_length;
// trailer part. For HTTP/1.1, trailer part is only included with
// chunked encoding. For HTTP/2, there is no such limit.
Headers request_trailers_;
Headers response_trailers_;
private:
Request req_;
Response resp_;
std::chrono::high_resolution_clock::time_point request_start_time_;
std::string request_path_;
std::string request_http2_scheme_;
std::string request_http2_authority_;
// host we requested to downstream. This is used to rewrite
// location header field to decide the location should be rewritten
// or not.
std::string request_downstream_host_;
std::string assembled_request_cookie_;
DefaultMemchunks request_buf_;
DefaultMemchunks response_buf_;
@ -390,76 +413,36 @@ private:
ev_timer downstream_rtimer_;
ev_timer downstream_wtimer_;
// the length of request body received so far
int64_t request_bodylen_;
// the length of response body received so far
int64_t response_bodylen_;
// the length of response body sent to upstream client
int64_t response_sent_bodylen_;
// content-length of request body, -1 if it is unknown.
int64_t request_content_length_;
// content-length of response body, -1 if it is unknown.
int64_t response_content_length_;
Upstream *upstream_;
std::unique_ptr<DownstreamConnection> dconn_;
// only used by HTTP/2 or SPDY upstream
BlockedLink *blocked_link_;
size_t request_headers_sum_;
size_t response_headers_sum_;
// The number of bytes not consumed by the application yet.
size_t request_datalen_;
size_t response_datalen_;
// How many times we tried in backend connection
size_t num_retry_;
// The stream ID in frontend connection
int32_t stream_id_;
int32_t priority_;
// The associated stream ID in frontend connection if this is pushed
// stream.
int32_t assoc_stream_id_;
// stream ID in backend connection
int32_t downstream_stream_id_;
// RST_STREAM error_code from downstream HTTP2 connection
uint32_t response_rst_stream_error_code_;
int request_method_;
// request state
int request_state_;
int request_major_;
int request_minor_;
// response state
int response_state_;
unsigned int response_http_status_;
int response_major_;
int response_minor_;
// only used by HTTP/2 or SPDY upstream
int dispatch_state_;
http2::HeaderIndex request_hdidx_;
http2::HeaderIndex response_hdidx_;
// true if the request contains upgrade token (HTTP Upgrade or
// CONNECT)
bool upgrade_request_;
// true if the connection is upgraded (HTTP Upgrade or CONNECT)
// true if the connection is upgraded (HTTP Upgrade or CONNECT),
// excluding upgrade to HTTP/2.
bool upgraded_;
bool http2_upgrade_seen_;
// true if backend request uses chunked transfer-encoding
bool chunked_request_;
bool request_connection_close_;
bool request_header_key_prev_;
bool request_trailer_key_prev_;
bool request_http2_expect_body_;
// true if response to client uses chunked transfer-encoding
bool chunked_response_;
bool response_connection_close_;
bool response_header_key_prev_;
bool response_trailer_key_prev_;
// 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;

View File

@ -56,7 +56,6 @@ public:
virtual int on_timeout() { return 0; }
virtual void on_upstream_change(Upstream *uptream) = 0;
virtual int on_priority_change(int32_t pri) = 0;
virtual size_t get_group() const = 0;
// true if this object is poolable.

View File

@ -79,7 +79,7 @@ DownstreamQueue::make_host_key(const std::string &host) const {
const std::string &
DownstreamQueue::make_host_key(Downstream *downstream) const {
return make_host_key(downstream->get_request_http2_authority());
return make_host_key(downstream->request().authority);
}
void DownstreamQueue::mark_active(Downstream *downstream) {

View File

@ -32,17 +32,17 @@
namespace shrpx {
void test_downstream_index_request_headers(void) {
Downstream d(nullptr, nullptr, 0, 0);
d.add_request_header("1", "0");
d.add_request_header("2", "1");
d.add_request_header("Charlie", "2");
d.add_request_header("Alpha", "3");
d.add_request_header("Delta", "4");
d.add_request_header("BravO", "5");
d.add_request_header(":method", "6");
d.add_request_header(":authority", "7");
d.index_request_headers();
void test_downstream_field_store_index_headers(void) {
FieldStore fs(0);
fs.add_header("1", "0");
fs.add_header("2", "1");
fs.add_header("Charlie", "2");
fs.add_header("Alpha", "3");
fs.add_header("Delta", "4");
fs.add_header("BravO", "5");
fs.add_header(":method", "6");
fs.add_header(":authority", "7");
fs.index_headers();
auto ans = Headers{{"1", "0"},
{"2", "1"},
@ -52,62 +52,36 @@ void test_downstream_index_request_headers(void) {
{"bravo", "5"},
{":method", "6"},
{":authority", "7"}};
CU_ASSERT(ans == d.get_request_headers());
CU_ASSERT(ans == fs.headers());
}
void test_downstream_index_response_headers(void) {
Downstream d(nullptr, nullptr, 0, 0);
d.add_response_header("Charlie", "0");
d.add_response_header("Alpha", "1");
d.add_response_header("Delta", "2");
d.add_response_header("BravO", "3");
d.index_response_headers();
auto ans =
Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}};
CU_ASSERT(ans == d.get_response_headers());
}
void test_downstream_get_request_header(void) {
Downstream d(nullptr, nullptr, 0, 0);
d.add_request_header("alpha", "0");
d.add_request_header(":authority", "1");
d.add_request_header("content-length", "2");
d.index_request_headers();
void test_downstream_field_store_header(void) {
FieldStore fs(0);
fs.add_header("alpha", "0");
fs.add_header(":authority", "1");
fs.add_header("content-length", "2");
fs.index_headers();
// By token
CU_ASSERT(Header(":authority", "1") ==
*d.get_request_header(http2::HD__AUTHORITY));
CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
CU_ASSERT(Header(":authority", "1") == *fs.header(http2::HD__AUTHORITY));
CU_ASSERT(nullptr == fs.header(http2::HD__METHOD));
// By name
CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
CU_ASSERT(nullptr == d.get_request_header("bravo"));
}
void test_downstream_get_response_header(void) {
Downstream d(nullptr, nullptr, 0, 0);
d.add_response_header("alpha", "0");
d.add_response_header(":status", "1");
d.add_response_header("content-length", "2");
d.index_response_headers();
// By token
CU_ASSERT(Header(":status", "1") ==
*d.get_response_header(http2::HD__STATUS));
CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
CU_ASSERT(Header("alpha", "0") == *fs.header("alpha"));
CU_ASSERT(nullptr == fs.header("bravo"));
}
void test_downstream_crumble_request_cookie(void) {
Downstream d(nullptr, nullptr, 0, 0);
d.add_request_header(":method", "get");
d.add_request_header(":path", "/");
Downstream d(nullptr, nullptr, 0);
auto &req = d.request();
req.fs.add_header(":method", "get");
req.fs.add_header(":path", "/");
auto val = "alpha; bravo; ; ;; charlie;;";
d.add_request_header(
req.fs.add_header(
reinterpret_cast<const uint8_t *>("cookie"), sizeof("cookie") - 1,
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
d.add_request_header("cookie", ";delta");
d.add_request_header("cookie", "echo");
req.fs.add_header("cookie", ";delta");
req.fs.add_header("cookie", "echo");
std::vector<nghttp2_nv> nva;
d.crumble_request_cookie(nva);
@ -138,39 +112,28 @@ void test_downstream_crumble_request_cookie(void) {
}
void test_downstream_assemble_request_cookie(void) {
Downstream d(nullptr, nullptr, 0, 0);
d.add_request_header(":method", "get");
d.add_request_header(":path", "/");
d.add_request_header("cookie", "alpha");
d.add_request_header("cookie", "bravo;");
d.add_request_header("cookie", "charlie; ");
d.add_request_header("cookie", "delta;;");
d.assemble_request_cookie();
CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie());
Downstream d(nullptr, nullptr, 0);
auto &req = d.request();
req.fs.add_header(":method", "get");
req.fs.add_header(":path", "/");
req.fs.add_header("cookie", "alpha");
req.fs.add_header("cookie", "bravo;");
req.fs.add_header("cookie", "charlie; ");
req.fs.add_header("cookie", "delta;;");
CU_ASSERT("alpha; bravo; charlie; delta" == d.assemble_request_cookie());
}
void test_downstream_rewrite_location_response_header(void) {
{
Downstream d(nullptr, nullptr, 0, 0);
d.set_request_downstream_host("localhost:3000");
d.add_request_header("host", "localhost");
d.add_response_header("location", "http://localhost:3000/");
d.index_request_headers();
d.index_response_headers();
d.rewrite_location_response_header("https");
auto location = d.get_response_header(http2::HD_LOCATION);
CU_ASSERT("https://localhost/" == (*location).value);
}
{
Downstream d(nullptr, nullptr, 0, 0);
d.set_request_downstream_host("localhost");
d.set_request_http2_authority("localhost");
d.add_response_header("location", "http://localhost:3000/");
d.index_response_headers();
d.rewrite_location_response_header("https");
auto location = d.get_response_header(http2::HD_LOCATION);
CU_ASSERT("https://localhost/" == (*location).value);
}
Downstream d(nullptr, nullptr, 0);
auto &req = d.request();
auto &resp = d.response();
d.set_request_downstream_host("localhost2");
req.authority = "localhost:8443";
resp.fs.add_header("location", "http://localhost2:3000/");
resp.fs.index_headers();
d.rewrite_location_response_header("https");
auto location = resp.fs.header(http2::HD_LOCATION);
CU_ASSERT("https://localhost:8443/" == (*location).value);
}
} // namespace shrpx

View File

@ -31,10 +31,8 @@
namespace shrpx {
void test_downstream_index_request_headers(void);
void test_downstream_index_response_headers(void);
void test_downstream_get_request_header(void);
void test_downstream_get_response_header(void);
void test_downstream_field_store_index_headers(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);

View File

@ -44,9 +44,8 @@ std::string create_error_html(unsigned int status_code) {
res += "</title><body><h1>";
res += status;
res += "</h1><footer>";
res += get_config()->server_name;
res += " at port ";
res += util::utos(get_config()->port);
const auto &server_name = get_config()->http.server_name;
res.append(server_name.c_str(), server_name.size());
res += "</footer></body></html>";
return res;
}
@ -62,6 +61,61 @@ std::string create_via_header_value(int major, int minor) {
return hdrs;
}
std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for,
const std::string &host,
const std::string &proto) {
std::string res;
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') {
res += "by=";
res += node_by;
res += ";";
} else {
res += "by=\"";
res += node_by;
res += "\";";
}
}
if ((params & FORWARDED_FOR) && !node_for.empty()) {
// We only quote IPv6 literal address only, which starts with '['.
if (node_for[0] == '[') {
res += "for=\"";
res += node_for;
res += "\";";
} else {
res += "for=";
res += node_for;
res += ";";
}
}
if ((params & FORWARDED_HOST) && !host.empty()) {
// Just be quoted to skip checking characters.
res += "host=\"";
res += host;
res += "\";";
}
if ((params & FORWARDED_PROTO) && !proto.empty()) {
// Scheme production rule only allow characters which are all in
// token.
res += "proto=";
res += proto;
res += ";";
}
if (res.empty()) {
return res;
}
res.erase(res.size() - 1);
return res;
}
std::string colorizeHeaders(const char *hdrs) {
std::string nhdrs;
const char *p = strchr(hdrs, '\n');

View File

@ -39,6 +39,13 @@ std::string create_error_html(unsigned int status_code);
std::string create_via_header_value(int major, int minor);
// 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.
std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for,
const std::string &host, const std::string &proto);
// Adds ANSI color codes to HTTP headers |hdrs|.
std::string colorizeHeaders(const char *hdrs);

View File

@ -46,8 +46,11 @@ namespace shrpx {
Http2DownstreamConnection::Http2DownstreamConnection(
DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
: DownstreamConnection(dconn_pool), dlnext(nullptr), dlprev(nullptr),
http2session_(http2session), sd_(nullptr) {}
: DownstreamConnection(dconn_pool),
dlnext(nullptr),
dlprev(nullptr),
http2session_(http2session),
sd_(nullptr) {}
Http2DownstreamConnection::~Http2DownstreamConnection() {
if (LOG_ENABLED(INFO)) {
@ -71,10 +74,12 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
downstream_->get_downstream_stream_id() != -1) {
submit_rst_stream(downstream_, error_code);
http2session_->consume(downstream_->get_downstream_stream_id(),
downstream_->get_response_datalen());
auto &resp = downstream_->response();
downstream_->reset_response_datalen();
http2session_->consume(downstream_->get_downstream_stream_id(),
resp.unconsumed_body_length);
resp.unconsumed_body_length = 0;
http2session_->signal_write();
}
@ -105,15 +110,18 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
}
auto &resp = downstream_->response();
if (submit_rst_stream(downstream) == 0) {
http2session_->signal_write();
}
if (downstream_->get_downstream_stream_id() != -1) {
http2session_->consume(downstream_->get_downstream_stream_id(),
downstream_->get_response_datalen());
resp.unconsumed_body_length);
downstream_->reset_response_datalen();
resp.unconsumed_body_length = 0;
http2session_->signal_write();
}
@ -165,6 +173,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
// 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 = input->remove(buf, length);
auto input_empty = input->rleft() == 0;
@ -190,13 +199,13 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
// 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.
(!downstream->get_upgrade_request() ||
(!req.upgrade_request ||
(downstream->get_response_state() == Downstream::HEADER_COMPLETE &&
!downstream->get_upgraded()))) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
auto &trailers = downstream->get_request_trailers();
const auto &trailers = req.fs.trailers();
if (!trailers.empty()) {
std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size());
@ -248,107 +257,150 @@ int Http2DownstreamConnection::push_request_headers() {
downstream_->set_request_pending(false);
auto method = downstream_->get_request_method();
auto no_host_rewrite = get_config()->no_host_rewrite ||
get_config()->http2_proxy ||
get_config()->client_proxy || method == HTTP_CONNECT;
const auto &req = downstream_->request();
auto &httpconf = get_config()->http;
auto &http2conf = get_config()->http2;
auto no_host_rewrite =
httpconf.no_host_rewrite || get_config()->http2_proxy ||
get_config()->client_proxy || req.method == HTTP_CONNECT;
// http2session_ has already in CONNECTED state, so we can get
// addr_idx here.
auto addr_idx = http2session_->get_addr_idx();
auto group = http2session_->get_group();
auto downstream_hostport = get_config()
->downstream_addr_groups[group]
.addrs[addr_idx]
.hostport.get();
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.
const char *authority = downstream_hostport;
auto &req_authority = downstream_->get_request_http2_authority();
if (no_host_rewrite && !req_authority.empty()) {
authority = req_authority.c_str();
auto authority = StringRef(downstream_hostport);
if (no_host_rewrite && !req.authority.empty()) {
authority = StringRef(req.authority);
}
downstream_->set_request_downstream_host(authority);
auto nheader = downstream_->get_request_headers().size();
downstream_->set_request_downstream_host(authority.str());
size_t num_cookies = 0;
if (!get_config()->http2_no_cookie_crumbling) {
if (!http2conf.no_cookie_crumbling) {
num_cookies = downstream_->count_crumble_request_cookie();
}
// 8 means:
// 9 means:
// 1. :method
// 2. :scheme
// 3. :path
// 4. :authority
// 4. :authority (or host)
// 5. via (optional)
// 6. x-forwarded-for (optional)
// 7. x-forwarded-proto (optional)
// 8. te (optional)
// 9. forwarded (optional)
auto nva = std::vector<nghttp2_nv>();
nva.reserve(nheader + 8 + num_cookies +
get_config()->add_request_headers.size());
nva.reserve(req.fs.headers().size() + 9 + num_cookies +
httpconf.add_request_headers.size());
nva.push_back(
http2::make_nv_lc_nocopy(":method", http2::to_method_string(method)));
http2::make_nv_lc_nocopy(":method", http2::to_method_string(req.method)));
auto &scheme = downstream_->get_request_http2_scheme();
if (req.method != HTTP_CONNECT) {
assert(!req.scheme.empty());
nva.push_back(http2::make_nv_lc_nocopy(":authority", authority));
nva.push_back(http2::make_nv_ls_nocopy(":scheme", req.scheme));
if (method != HTTP_CONNECT) {
assert(!scheme.empty());
nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
auto &path = downstream_->get_request_path();
if (method == HTTP_OPTIONS && path.empty()) {
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", path));
nva.push_back(http2::make_nv_ls_nocopy(":path", req.path));
}
if (!req.no_authority) {
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));
}
http2::copy_headers_to_nva_nocopy(nva, downstream_->get_request_headers());
http2::copy_headers_to_nva_nocopy(nva, req.fs.headers());
bool chunked_encoding = false;
auto transfer_encoding =
downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING);
if (transfer_encoding &&
util::strieq_l("chunked", (*transfer_encoding).value)) {
chunked_encoding = true;
}
if (!get_config()->http2_no_cookie_crumbling) {
if (!http2conf.no_cookie_crumbling) {
downstream_->crumble_request_cookie(nva);
}
auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler();
std::string forwarded_value;
auto &fwdconf = httpconf.forwarded;
auto fwd =
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
if (fwdconf.params) {
auto params = fwdconf.params;
if (get_config()->http2_proxy || get_config()->client_proxy ||
req.method == HTTP_CONNECT) {
params &= ~FORWARDED_PROTO;
}
auto value = http::create_forwarded(params, handler->get_forwarded_by(),
handler->get_forwarded_for(),
req.authority, req.scheme);
if (fwd || !value.empty()) {
if (fwd) {
forwarded_value = fwd->value;
if (!value.empty()) {
forwarded_value += ", ";
}
}
forwarded_value += value;
nva.push_back(http2::make_nv_ls("forwarded", forwarded_value));
}
} else if (fwd) {
nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value));
forwarded_value = fwd->value;
}
auto &xffconf = httpconf.xff;
auto xff = xffconf.strip_incoming ? nullptr
: req.fs.header(http2::HD_X_FORWARDED_FOR);
std::string xff_value;
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
if (get_config()->add_x_forwarded_for) {
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
if (xffconf.add) {
if (xff) {
xff_value = (*xff).value;
xff_value += ", ";
}
xff_value +=
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
xff_value += upstream->get_client_handler()->get_ipaddr();
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
} else if (xff) {
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value));
}
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
downstream_->get_request_method() != HTTP_CONNECT) {
req.method != HTTP_CONNECT) {
// We use same protocol with :scheme header field
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", scheme));
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme));
}
std::string via_value;
auto via = downstream_->get_request_header(http2::HD_VIA);
if (get_config()->no_via) {
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));
}
@ -357,12 +409,11 @@ int Http2DownstreamConnection::push_request_headers() {
via_value = (*via).value;
via_value += ", ";
}
via_value += http::create_via_header_value(
downstream_->get_request_major(), downstream_->get_request_minor());
via_value += http::create_via_header_value(req.http_major, req.http_minor);
nva.push_back(http2::make_nv_ls("via", via_value));
}
auto te = downstream_->get_request_header(http2::HD_TE);
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.
@ -370,7 +421,7 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(http2::make_nv_ll("te", "trailers"));
}
for (auto &p : get_config()->add_request_headers) {
for (auto &p : httpconf.add_request_headers) {
nva.push_back(http2::make_nv_nocopy(p.first, p.second));
}
@ -382,21 +433,18 @@ int Http2DownstreamConnection::push_request_headers() {
DCLOG(INFO, this) << "HTTP request headers\n" << ss.str();
}
auto content_length =
downstream_->get_request_header(http2::HD_CONTENT_LENGTH);
auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH);
// TODO check content-length: 0 case
if (downstream_->get_request_method() == HTTP_CONNECT || chunked_encoding ||
content_length || downstream_->get_request_http2_expect_body()) {
if (req.method == HTTP_CONNECT || chunked_encoding || content_length ||
req.http2_expect_body) {
// Request-body is expected.
nghttp2_data_provider data_prd;
data_prd.source.ptr = this;
data_prd.read_callback = http2_data_read_callback;
rv = http2session_->submit_request(this, downstream_->get_priority(),
nva.data(), nva.size(), &data_prd);
rv = http2session_->submit_request(this, nva.data(), nva.size(), &data_prd);
} else {
rv = http2session_->submit_request(this, downstream_->get_priority(),
nva.data(), nva.size(), nullptr);
rv = http2session_->submit_request(this, nva.data(), nva.size(), nullptr);
}
if (rv != 0) {
DCLOG(FATAL, this) << "nghttp2_submit_request() failed";
@ -456,8 +504,6 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
}
if (consumed > 0) {
assert(downstream_->get_response_datalen() >= consumed);
rv = http2session_->consume(downstream_->get_downstream_stream_id(),
consumed);
@ -465,7 +511,9 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
return -1;
}
downstream_->dec_response_datalen(consumed);
auto &resp = downstream_->response();
resp.unconsumed_body_length -= consumed;
http2session_->signal_write();
}
@ -498,24 +546,6 @@ StreamData *Http2DownstreamConnection::detach_stream_data() {
return nullptr;
}
int Http2DownstreamConnection::on_priority_change(int32_t pri) {
int rv;
if (downstream_->get_priority() == pri) {
return 0;
}
downstream_->set_priority(pri);
if (http2session_->get_state() != Http2Session::CONNECTED) {
return 0;
}
rv = http2session_->submit_priority(this, pri);
if (rv != 0) {
DLOG(FATAL, this) << "nghttp2_submit_priority() failed";
return -1;
}
http2session_->signal_write();
return 0;
}
int Http2DownstreamConnection::on_timeout() {
if (!downstream_) {
return 0;

View File

@ -60,7 +60,6 @@ public:
virtual int on_timeout();
virtual void on_upstream_change(Upstream *upstream) {}
virtual int on_priority_change(int32_t pri);
virtual size_t get_group() const;
// This object is not poolable because we dont' have facility to

View File

@ -57,6 +57,10 @@ const ev_tstamp CONNCHK_TIMEOUT = 5.;
const 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);
@ -146,17 +150,26 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
ConnectBlocker *connect_blocker, Worker *worker,
size_t group, size_t idx)
: conn_(loop, -1, nullptr, worker->get_mcpool(),
get_config()->downstream_write_timeout,
get_config()->downstream_read_timeout, 0, 0, 0, 0, writecb, readcb,
timeoutcb, this, get_config()->tls_dyn_rec_warmup_threshold,
get_config()->tls_dyn_rec_idle_timeout),
worker_(worker), connect_blocker_(connect_blocker), ssl_ctx_(ssl_ctx),
session_(nullptr), data_pending_(nullptr), data_pendinglen_(0),
addr_idx_(0), group_(group), index_(idx), state_(DISCONNECTED),
connection_check_state_(CONNECTION_CHECK_NONE), flow_control_(false) {
get_config()->conn.downstream.timeout.write,
get_config()->conn.downstream.timeout.read, {}, {}, writecb, readcb,
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout),
wb_(worker->get_mcpool()),
worker_(worker),
connect_blocker_(connect_blocker),
ssl_ctx_(ssl_ctx),
addr_(nullptr),
session_(nullptr),
group_(group),
index_(idx),
state_(DISCONNECTED),
connection_check_state_(CONNECTION_CHECK_NONE),
flow_control_(false) {
read_ = write_ = &Http2Session::noop;
on_read_ = on_write_ = &Http2Session::noop;
on_read_ = &Http2Session::read_noop;
on_write_ = &Http2Session::write_noop;
// We will resuse this many times, so use repeat timeout value. The
// timeout value is set later.
@ -180,7 +193,6 @@ int Http2Session::disconnect(bool hard) {
nghttp2_session_del(session_);
session_ = nullptr;
rb_.reset();
wb_.reset();
conn_.rlimit.stopw();
@ -190,11 +202,13 @@ int Http2Session::disconnect(bool hard) {
ev_timer_stop(conn_.loop, &connchk_timer_);
read_ = write_ = &Http2Session::noop;
on_read_ = on_write_ = &Http2Session::noop;
on_read_ = &Http2Session::read_noop;
on_write_ = &Http2Session::write_noop;
conn_.disconnect();
addr_idx_ = 0;
addr_ = nullptr;
if (proxy_htp_) {
proxy_htp_.reset();
@ -237,16 +251,10 @@ int Http2Session::disconnect(bool hard) {
return 0;
}
int Http2Session::check_cert() {
return ssl::check_cert(
conn_.tls.ssl,
&get_config()->downstream_addr_groups[group_].addrs[addr_idx_]);
}
int Http2Session::initiate_connection() {
int rv = 0;
auto &addrs = get_config()->downstream_addr_groups[group_].addrs;
auto &addrs = get_config()->conn.downstream.addr_groups[group_].addrs;
if (state_ == DISCONNECTED) {
if (connect_blocker_->blocked()) {
@ -258,40 +266,36 @@ int Http2Session::initiate_connection() {
}
auto &next_downstream = worker_->get_dgrp(group_)->next;
addr_idx_ = next_downstream;
addr_ = &addrs[next_downstream];
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Using downstream address idx=" << next_downstream
<< " out of " << addrs.size();
}
if (++next_downstream >= addrs.size()) {
next_downstream = 0;
}
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Using downstream address idx=" << addr_idx_
<< " out of " << addrs.size();
}
}
auto &downstream_addr = addrs[addr_idx_];
if (get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) {
const auto &proxy = get_config()->downstream_http_proxy;
if (!proxy.host.empty() && state_ == DISCONNECTED) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Connecting to the proxy "
<< get_config()->downstream_http_proxy_host.get() << ":"
<< get_config()->downstream_http_proxy_port;
SSLOG(INFO, this) << "Connecting to the proxy " << proxy.host << ":"
<< proxy.port;
}
conn_.fd = util::create_nonblock_socket(
get_config()->downstream_http_proxy_addr.su.storage.ss_family);
conn_.fd = util::create_nonblock_socket(proxy.addr.su.storage.ss_family);
if (conn_.fd == -1) {
connect_blocker_->on_failure();
return -1;
}
rv = connect(conn_.fd, &get_config()->downstream_http_proxy_addr.su.sa,
get_config()->downstream_http_proxy_addr.len);
rv = connect(conn_.fd, &proxy.addr.su.sa, proxy.addr.len);
if (rv != 0 && errno != EINPROGRESS) {
SSLOG(ERROR, this) << "Failed to connect to the proxy "
<< get_config()->downstream_http_proxy_host.get()
<< ":" << get_config()->downstream_http_proxy_port;
SSLOG(ERROR, this) << "Failed to connect to the proxy " << proxy.host
<< ":" << proxy.port;
connect_blocker_->on_failure();
return -1;
}
@ -334,26 +338,23 @@ int Http2Session::initiate_connection() {
conn_.set_ssl(ssl);
}
const char *sni_name = nullptr;
if (get_config()->backend_tls_sni_name) {
sni_name = get_config()->backend_tls_sni_name.get();
} else {
sni_name = downstream_addr.host.get();
}
auto sni_name = !get_config()->tls.backend_sni_name.empty()
? StringRef(get_config()->tls.backend_sni_name)
: StringRef(addr_->host);
if (sni_name && !util::numeric_host(sni_name)) {
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);
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
}
// If state_ == PROXY_CONNECTED, we has connected to the proxy
// using conn_.fd and tunnel has been established.
if (state_ == DISCONNECTED) {
assert(conn_.fd == -1);
conn_.fd = util::create_nonblock_socket(
downstream_addr.addr.su.storage.ss_family);
conn_.fd =
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
if (conn_.fd == -1) {
connect_blocker_->on_failure();
return -1;
@ -361,8 +362,8 @@ int Http2Session::initiate_connection() {
rv = connect(conn_.fd,
// TODO maybe not thread-safe?
const_cast<sockaddr *>(&downstream_addr.addr.su.sa),
downstream_addr.addr.len);
const_cast<sockaddr *>(&addr_->addr.su.sa),
addr_->addr.len);
if (rv != 0 && errno != EINPROGRESS) {
connect_blocker_->on_failure();
return -1;
@ -378,17 +379,16 @@ int Http2Session::initiate_connection() {
// Without TLS and proxy.
assert(conn_.fd == -1);
conn_.fd = util::create_nonblock_socket(
downstream_addr.addr.su.storage.ss_family);
conn_.fd =
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
if (conn_.fd == -1) {
connect_blocker_->on_failure();
return -1;
}
rv = connect(conn_.fd,
const_cast<sockaddr *>(&downstream_addr.addr.su.sa),
downstream_addr.addr.len);
rv = connect(conn_.fd, const_cast<sockaddr *>(&addr_->addr.su.sa),
addr_->addr.len);
if (rv != 0 && errno != EINPROGRESS) {
connect_blocker_->on_failure();
return -1;
@ -468,29 +468,17 @@ http_parser_settings htp_hooks = {
};
} // namespace
int Http2Session::downstream_read_proxy() {
if (rb_.rleft() == 0) {
return 0;
}
size_t nread =
int Http2Session::downstream_read_proxy(const uint8_t *data, size_t datalen) {
auto nread =
http_parser_execute(proxy_htp_.get(), &htp_hooks,
reinterpret_cast<const char *>(rb_.pos), rb_.rleft());
rb_.drain(nread);
reinterpret_cast<const char *>(data), datalen);
(void)nread;
auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get());
if (htperr == HPE_PAUSED) {
switch (state_) {
case Http2Session::PROXY_CONNECTED:
// we need to increment nread by 1 since http_parser_execute()
// returns 1 less value we expect. This means taht
// rb_.pos[nread] points to \x0a (LF), which is last byte of
// empty line to terminate headers. We want to eat that byte
// here.
rb_.drain(1);
// Initiate SSL/TLS handshake through established tunnel.
if (initiate_connection() != 0) {
return -1;
@ -514,36 +502,29 @@ int Http2Session::downstream_connect_proxy() {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Connected to the proxy";
}
auto &downstream_addr =
get_config()->downstream_addr_groups[group_].addrs[addr_idx_];
std::string req = "CONNECT ";
req += downstream_addr.hostport.get();
if (downstream_addr.port == 80 || downstream_addr.port == 443) {
req.append(addr_->hostport.c_str(), addr_->hostport.size());
if (addr_->port == 80 || addr_->port == 443) {
req += ':';
req += util::utos(downstream_addr.port);
req += util::utos(addr_->port);
}
req += " HTTP/1.1\r\nHost: ";
req += downstream_addr.host.get();
req += addr_->host;
req += "\r\n";
if (get_config()->downstream_http_proxy_userinfo) {
const auto &proxy = get_config()->downstream_http_proxy;
if (!proxy.userinfo.empty()) {
req += "Proxy-Authorization: Basic ";
size_t len = strlen(get_config()->downstream_http_proxy_userinfo.get());
req += base64::encode(get_config()->downstream_http_proxy_userinfo.get(),
get_config()->downstream_http_proxy_userinfo.get() +
len);
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;
}
auto nwrite = wb_.write(req.c_str(), req.size());
if (nwrite != req.size()) {
SSLOG(WARN, this) << "HTTP proxy request is too large";
return -1;
}
on_write_ = &Http2Session::noop;
wb_.append(req);
on_write_ = &Http2Session::write_noop;
signal_write();
return 0;
@ -567,7 +548,7 @@ void Http2Session::remove_stream_data(StreamData *sd) {
delete sd;
}
int Http2Session::submit_request(Http2DownstreamConnection *dconn, int32_t pri,
int Http2Session::submit_request(Http2DownstreamConnection *dconn,
const nghttp2_nv *nva, size_t nvlen,
const nghttp2_data_provider *data_prd) {
assert(state_ == CONNECTED);
@ -605,30 +586,6 @@ int Http2Session::submit_rst_stream(int32_t stream_id, uint32_t error_code) {
return 0;
}
int Http2Session::submit_priority(Http2DownstreamConnection *dconn,
int32_t pri) {
assert(state_ == CONNECTED);
if (!dconn) {
return 0;
}
int rv;
// TODO Disabled temporarily
// rv = nghttp2_submit_priority(session_, NGHTTP2_FLAG_NONE,
// dconn->get_downstream()->
// get_downstream_stream_id(), pri);
rv = 0;
if (rv < NGHTTP2_ERR_FATAL) {
SSLOG(FATAL, this) << "nghttp2_submit_priority() failed: "
<< nghttp2_strerror(rv);
return -1;
}
return 0;
}
nghttp2_session *Http2Session::get_session() const { return session_; }
bool Http2Session::get_flow_control() const { return flow_control_; }
@ -752,22 +709,43 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
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() + namelen + valuelen >
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() + namelen + valuelen
<< ", 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;
}
if (trailer) {
// just store header fields for trailer part
downstream->add_response_trailer(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
resp.fs.add_trailer(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
return 0;
}
auto token = http2::lookup_token(name, namelen);
downstream->add_response_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
resp.fs.add_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
case NGHTTP2_PUSH_PROMISE: {
@ -783,10 +761,25 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
assert(promised_downstream);
auto &promised_req = promised_downstream->request();
// We use request header limit for PUSH_PROMISE
if (promised_req.fs.buffer_size() + namelen + valuelen >
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() + namelen + valuelen
<< ", num=" << promised_req.fs.num_fields() + 1;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
auto token = http2::lookup_token(name, namelen);
promised_downstream->add_request_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX,
token);
promised_req.fs.add_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
}
@ -855,18 +848,20 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
int rv;
auto upstream = downstream->get_upstream();
const auto &req = downstream->request();
auto &resp = downstream->response();
auto &nva = downstream->get_response_headers();
auto &nva = resp.fs.headers();
downstream->set_expect_final_response(false);
auto status = downstream->get_response_header(http2::HD__STATUS);
auto status = resp.fs.header(http2::HD__STATUS);
// libnghttp2 guarantees this exists and can be parsed
auto status_code = http2::parse_http_status_code(status->value);
downstream->set_response_http_status(status_code);
downstream->set_response_major(2);
downstream->set_response_minor(0);
resp.http_status = status_code;
resp.http_major = 2;
resp.http_minor = 0;
if (LOG_ENABLED(INFO)) {
std::stringstream ss;
@ -902,7 +897,7 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
downstream->check_upgrade_fulfilled();
if (downstream->get_upgraded()) {
downstream->set_response_connection_close(true);
resp.connection_close = true;
// On upgrade sucess, both ends can send data
if (upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0) != 0) {
// If resume_read fails, just drop connection. Not ideal.
@ -915,29 +910,24 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
<< "HTTP upgrade success. stream_id=" << frame->hd.stream_id;
}
} else {
auto content_length =
downstream->get_response_header(http2::HD_CONTENT_LENGTH);
auto content_length = resp.fs.header(http2::HD_CONTENT_LENGTH);
if (content_length) {
// libnghttp2 guarantees this can be parsed
auto len = util::parse_uint(content_length->value);
downstream->set_response_content_length(len);
resp.fs.content_length = util::parse_uint(content_length->value);
}
if (downstream->get_response_content_length() == -1 &&
downstream->expect_response_body()) {
if (resp.fs.content_length == -1 && downstream->expect_response_body()) {
// Here we have response body but Content-Length is not known in
// advance.
if (downstream->get_request_major() <= 0 ||
(downstream->get_request_major() == 1 &&
downstream->get_request_minor() == 0)) {
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.
downstream->set_response_connection_close(true);
resp.connection_close = true;
} else {
// Otherwise, use chunked encoding to keep upstream connection
// open. In HTTP2, we are supporsed not to receive
// transfer-encoding.
downstream->add_response_header("transfer-encoding", "chunked",
http2::HD_TRANSFER_ENCODING);
resp.fs.add_header("transfer-encoding", "chunked",
http2::HD_TRANSFER_ENCODING);
downstream->set_chunked_response(true);
}
}
@ -1177,7 +1167,10 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
downstream->reset_downstream_rtimer();
downstream->add_response_bodylen(len);
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);
@ -1191,8 +1184,6 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
downstream->set_response_state(Downstream::MSG_RESET);
}
downstream->add_response_datalen(len);
call_downstream_readcb(http2session, downstream);
return 0;
}
@ -1340,9 +1331,10 @@ int Http2Session::connection_made() {
}
}
rv = nghttp2_session_client_new2(&session_,
get_config()->http2_downstream_callbacks,
this, get_config()->http2_client_option);
auto &http2conf = get_config()->http2;
rv = nghttp2_session_client_new2(&session_, http2conf.downstream.callbacks,
this, http2conf.downstream.option);
if (rv != 0) {
return -1;
@ -1353,12 +1345,12 @@ int Http2Session::connection_made() {
std::array<nghttp2_settings_entry, 3> entry;
size_t nentry = 2;
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = get_config()->http2_max_concurrent_streams;
entry[0].value = http2conf.max_concurrent_streams;
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
entry[1].value = (1 << get_config()->http2_downstream_window_bits) - 1;
entry[1].value = (1 << http2conf.downstream.window_bits) - 1;
if (get_config()->no_server_push || get_config()->http2_proxy ||
if (http2conf.no_server_push || get_config()->http2_proxy ||
get_config()->client_proxy) {
entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
entry[nentry].value = 0;
@ -1371,17 +1363,17 @@ int Http2Session::connection_made() {
return -1;
}
if (get_config()->http2_downstream_connection_window_bits > 16) {
int32_t delta =
(1 << get_config()->http2_downstream_connection_window_bits) - 1 -
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
auto connection_window_bits = http2conf.downstream.connection_window_bits;
if (connection_window_bits > 16) {
int32_t delta = (1 << connection_window_bits) - 1 -
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0, delta);
if (rv != 0) {
return -1;
}
}
auto must_terminate = !get_config()->downstream_no_tls &&
auto must_terminate = !get_config()->conn.downstream.no_tls &&
!nghttp2::ssl::check_http2_requirement(conn_.tls.ssl);
if (must_terminate) {
@ -1411,25 +1403,20 @@ int Http2Session::connection_made() {
int Http2Session::do_read() { return read_(*this); }
int Http2Session::do_write() { return write_(*this); }
int Http2Session::on_read() { return on_read_(*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() {
ssize_t rv = 0;
int Http2Session::downstream_read(const uint8_t *data, size_t datalen) {
ssize_t rv;
if (rb_.rleft() > 0) {
rv = nghttp2_session_mem_recv(
session_, reinterpret_cast<const uint8_t *>(rb_.pos), rb_.rleft());
if (rv < 0) {
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
// nghttp2_session_mem_recv() should consume all input data in
// case of success.
rb_.reset();
rv = nghttp2_session_mem_recv(session_, data, datalen);
if (rv < 0) {
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
if (nghttp2_session_want_read(session_) == 0 &&
@ -1445,19 +1432,6 @@ int Http2Session::downstream_read() {
}
int Http2Session::downstream_write() {
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);
@ -1470,11 +1444,10 @@ int Http2Session::downstream_write() {
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;
return 0;
wb_.append(data, datalen);
if (wb_.rleft() >= MAX_BUFFER_SIZE) {
break;
}
}
@ -1634,6 +1607,10 @@ int Http2Session::get_connection_check_state() const {
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() {
if (!util::check_socket_connected(conn_.fd)) {
return -1;
@ -1672,17 +1649,10 @@ int Http2Session::connected() {
int Http2Session::read_clear() {
ev_timer_again(conn_.loop, &conn_.rt);
for (;;) {
// we should process buffered data first before we read EOF.
if (rb_.rleft() && on_read() != 0) {
return -1;
}
if (rb_.rleft()) {
return 0;
}
rb_.reset();
std::array<uint8_t, 16_k> buf;
auto nread = conn_.read_clear(rb_.last, rb_.wleft());
for (;;) {
auto nread = conn_.read_clear(buf.data(), buf.size());
if (nread == 0) {
return 0;
@ -1692,16 +1662,21 @@ int Http2Session::read_clear() {
return nread;
}
rb_.write(nread);
if (on_read(buf.data(), nread) != 0) {
return -1;
}
}
}
int Http2Session::write_clear() {
ev_timer_again(conn_.loop, &conn_.rt);
std::array<struct iovec, MAX_WR_IOVCNT> iov;
for (;;) {
if (wb_.rleft() > 0) {
auto nwrite = conn_.write_clear(wb_.pos, wb_.rleft());
auto iovcnt = wb_.riovec(iov.data(), iov.size());
auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
if (nwrite == 0) {
return 0;
@ -1715,7 +1690,6 @@ int Http2Session::write_clear() {
continue;
}
wb_.reset();
if (on_write() != 0) {
return -1;
}
@ -1749,8 +1723,8 @@ int Http2Session::tls_handshake() {
SSLOG(INFO, this) << "SSL/TLS handshake completed";
}
if (!get_config()->downstream_no_tls && !get_config()->insecure &&
check_cert() != 0) {
if (!get_config()->tls.insecure &&
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
return -1;
}
@ -1768,19 +1742,12 @@ int Http2Session::tls_handshake() {
int Http2Session::read_tls() {
ev_timer_again(conn_.loop, &conn_.rt);
std::array<uint8_t, 16_k> buf;
ERR_clear_error();
for (;;) {
// we should process buffered data first before we read EOF.
if (rb_.rleft() && on_read() != 0) {
return -1;
}
if (rb_.rleft()) {
return 0;
}
rb_.reset();
auto nread = conn_.read_tls(rb_.last, rb_.wleft());
auto nread = conn_.read_tls(buf.data(), buf.size());
if (nread == 0) {
return 0;
@ -1790,7 +1757,9 @@ int Http2Session::read_tls() {
return nread;
}
rb_.write(nread);
if (on_read(buf.data(), nread) != 0) {
return -1;
}
}
}
@ -1799,9 +1768,13 @@ int Http2Session::write_tls() {
ERR_clear_error();
struct iovec iov;
for (;;) {
if (wb_.rleft() > 0) {
auto nwrite = conn_.write_tls(wb_.pos, wb_.rleft());
auto iovcnt = wb_.riovec(&iov, 1);
assert(iovcnt == 1);
auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
if (nwrite == 0) {
return 0;
@ -1815,7 +1788,7 @@ int Http2Session::write_tls() {
continue;
}
wb_.reset();
if (on_write() != 0) {
return -1;
}
@ -1843,7 +1816,7 @@ bool Http2Session::should_hard_fail() const {
}
}
size_t Http2Session::get_addr_idx() const { return addr_idx_; }
const DownstreamAddr *Http2Session::get_addr() const { return addr_; }
size_t Http2Session::get_group() const { return group_; }
@ -1892,14 +1865,15 @@ int Http2Session::handle_downstream_push_promise(Downstream *downstream,
int Http2Session::handle_downstream_push_promise_complete(
Downstream *downstream, Downstream *promised_downstream) {
auto authority =
promised_downstream->get_request_header(http2::HD__AUTHORITY);
auto path = promised_downstream->get_request_header(http2::HD__PATH);
auto method = promised_downstream->get_request_header(http2::HD__METHOD);
auto scheme = promised_downstream->get_request_header(http2::HD__SCHEME);
auto &promised_req = promised_downstream->request();
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_downstream->get_request_header(http2::HD_HOST);
authority = promised_req.fs.header(http2::HD_HOST);
}
auto method_token = http2::lookup_method_token(method->value);
@ -1914,17 +1888,16 @@ int Http2Session::handle_downstream_push_promise_complete(
// 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?
promised_downstream->set_request_http2_authority(
http2::value_to_str(authority));
promised_downstream->set_request_method(method_token);
promised_req.authority = http2::value_to_str(authority);
promised_req.method = method_token;
// libnghttp2 ensures that we don't have CONNECT method in
// PUSH_PROMISE, and guarantees that :scheme exists.
promised_downstream->set_request_http2_scheme(http2::value_to_str(scheme));
promised_req.scheme = http2::value_to_str(scheme);
// For server-wide OPTIONS request, path is empty.
if (method_token != HTTP_OPTIONS || path->value != "*") {
promised_downstream->set_request_path(http2::rewrite_clean_path(
std::begin(path->value), std::end(path->value)));
promised_req.path = http2::rewrite_clean_path(std::begin(path->value),
std::end(path->value));
}
promised_downstream->inspect_http2_request();

View File

@ -62,8 +62,6 @@ public:
size_t idx);
~Http2Session();
int check_cert();
// If hard is true, all pending requests are abandoned and
// associated ClientHandlers will be deleted.
int disconnect(bool hard = false);
@ -74,14 +72,11 @@ public:
void remove_stream_data(StreamData *sd);
int submit_request(Http2DownstreamConnection *dconn, int32_t pri,
const nghttp2_nv *nva, size_t nvlen,
const nghttp2_data_provider *data_prd);
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 submit_priority(Http2DownstreamConnection *dconn, int32_t pri);
int terminate_session(uint32_t error_code);
nghttp2_session *get_session() const;
@ -95,7 +90,7 @@ public:
int do_read();
int do_write();
int on_read();
int on_read(const uint8_t *data, size_t datalen);
int on_write();
int connected();
@ -105,13 +100,15 @@ public:
int read_tls();
int write_tls();
int downstream_read_proxy();
int downstream_read_proxy(const uint8_t *data, size_t datalen);
int downstream_connect_proxy();
int downstream_read();
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();
@ -150,7 +147,7 @@ public:
void submit_pending_requests();
size_t get_addr_idx() const;
const DownstreamAddr *get_addr() const;
size_t get_group() const;
@ -188,10 +185,10 @@ public:
};
using ReadBuf = Buffer<8_k>;
using WriteBuf = Buffer<32768>;
private:
Connection conn_;
DefaultMemchunks wb_;
ev_timer settings_timer_;
// This timer has 2 purpose: when it first timeout, set
// connection_check_state_ = CONNECTION_CHECK_REQUIRED. After
@ -201,18 +198,17 @@ private:
DList<Http2DownstreamConnection> dconns_;
DList<StreamData> streams_;
std::function<int(Http2Session &)> read_, write_;
std::function<int(Http2Session &)> on_read_, on_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<http_parser> proxy_htp_;
Worker *worker_;
ConnectBlocker *connect_blocker_;
// NULL if no TLS is configured
SSL_CTX *ssl_ctx_;
// Address of remote endpoint
const DownstreamAddr *addr_;
nghttp2_session *session_;
const uint8_t *data_pending_;
size_t data_pendinglen_;
// index of get_config()->downstream_addrs this object uses
size_t addr_idx_;
size_t group_;
// index inside group, this is used to pin frontend to certain
// HTTP/2 backend for better throughput.
@ -220,8 +216,6 @@ private:
int state_;
int connection_check_state_;
bool flow_control_;
WriteBuf wb_;
ReadBuf rb_;
};
nghttp2_session_callbacks *create_http2_downstream_callbacks();

File diff suppressed because it is too large Load Diff

View File

@ -118,42 +118,21 @@ public:
int on_request_headers(Downstream *downstream, const nghttp2_frame *frame);
using WriteBuffer = Buffer<32_k>;
DefaultMemchunks *get_response_buf();
WriteBuffer *get_response_buf();
void set_pending_data_downstream(Downstream *downstream, size_t n,
size_t padlen);
// Changes stream priority of |downstream|, which is assumed to be a
// pushed stream.
int adjust_pushed_stream_priority(Downstream *downstream);
private:
WriteBuffer wb_;
DefaultMemchunks wb_;
std::unique_ptr<HttpsUpstream> pre_upstream_;
DownstreamQueue downstream_queue_;
ev_timer settings_timer_;
ev_timer shutdown_timer_;
ev_prepare prep_;
// A response buffer used to belong to Downstream object. This is
// moved here when response is partially written to wb_ in
// send_data_callback, but before writing them all, Downstream
// object was destroyed. On destruction of Downstream,
// pending_data_downstream_ becomes nullptr.
DefaultMemchunks pending_response_buf_;
// Downstream object whose DATA frame payload is partillay written
// to wb_ in send_data_callback. This field exists to keep track of
// its lifetime. When it is destroyed, its response buffer is
// transferred to pending_response_buf_, and this field becomes
// nullptr.
Downstream *pending_data_downstream_;
ClientHandler *handler_;
nghttp2_session *session_;
const uint8_t *data_pending_;
// The length of lending data to be written into wb_. If
// data_pending_ is not nullptr, data_pending_ points to the data to
// write. Otherwise, pending_data_downstream_->get_response_buf()
// if pending_data_downstream_ is not nullptr, or
// pending_response_buf_ holds data to write.
size_t data_pendinglen_;
size_t padding_pendinglen_;
bool flow_control_;
bool shutdown_handled_;
};

View File

@ -35,6 +35,7 @@
#include "shrpx_downstream_connection_pool.h"
#include "shrpx_worker.h"
#include "shrpx_http2_session.h"
#include "shrpx_ssl.h"
#include "http2.h"
#include "util.h"
@ -54,9 +55,10 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
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
downstream->set_response_connection_close(true);
resp.connection_close = true;
if (upstream->downstream_error(dconn, Downstream::EVENT_TIMEOUT) != 0) {
delete handler;
@ -99,7 +101,7 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
auto downstream = dconn->get_downstream();
auto upstream = downstream->get_upstream();
auto handler = upstream->get_client_handler();
if (dconn->on_connect() != 0) {
if (dconn->connected() != 0) {
if (upstream->on_downstream_abort_request(downstream, 503) != 0) {
delete handler;
}
@ -110,22 +112,39 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
} // namespace
HttpDownstreamConnection::HttpDownstreamConnection(
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop)
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop,
Worker *worker)
: DownstreamConnection(dconn_pool),
conn_(loop, -1, nullptr, nullptr, get_config()->downstream_write_timeout,
get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb,
readcb, timeoutcb, this, get_config()->tls_dyn_rec_warmup_threshold,
get_config()->tls_dyn_rec_idle_timeout),
ioctrl_(&conn_.rlimit), response_htp_{0}, group_(group), addr_idx_(0),
connected_(false) {}
conn_(loop, -1, nullptr, worker->get_mcpool(),
get_config()->conn.downstream.timeout.write,
get_config()->conn.downstream.timeout.read, {}, {}, connectcb,
readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout),
do_read_(&HttpDownstreamConnection::noop),
do_write_(&HttpDownstreamConnection::noop),
worker_(worker),
ssl_ctx_(worker->get_cl_ssl_ctx()),
addr_(nullptr),
ioctrl_(&conn_.rlimit),
response_htp_{0},
group_(group) {}
HttpDownstreamConnection::~HttpDownstreamConnection() {}
HttpDownstreamConnection::~HttpDownstreamConnection() {
if (conn_.tls.ssl) {
auto session = SSL_get1_session(conn_.tls.ssl);
if (session) {
worker_->cache_downstream_tls_session(addr_, session);
}
}
}
int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
}
auto &downstreamconf = get_config()->conn.downstream;
if (conn_.fd == -1) {
auto connect_blocker = client_handler_->get_connect_blocker();
@ -137,13 +156,21 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
return -1;
}
auto worker = client_handler_->get_worker();
auto &next_downstream = worker->get_dgrp(group_)->next;
if (ssl_ctx_) {
auto ssl = ssl::create_ssl(ssl_ctx_);
if (!ssl) {
return -1;
}
conn_.set_ssl(ssl);
}
auto &next_downstream = worker_->get_dgrp(group_)->next;
auto end = next_downstream;
auto &addrs = get_config()->downstream_addr_groups[group_].addrs;
auto &addrs = downstreamconf.addr_groups[group_].addrs;
for (;;) {
auto &addr = addrs[next_downstream];
auto i = next_downstream;
if (++next_downstream >= addrs.size()) {
next_downstream = 0;
}
@ -181,7 +208,24 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
DCLOG(INFO, this) << "Connecting to downstream server";
}
addr_idx_ = i;
addr_ = &addr;
if (ssl_ctx_) {
auto sni_name = !get_config()->tls.backend_sni_name.empty()
? StringRef(get_config()->tls.backend_sni_name)
: StringRef(addr_->host);
if (!util::numeric_host(sni_name.c_str())) {
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
}
auto session = worker_->reuse_downstream_tls_session(addr_);
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);
@ -195,7 +239,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
ev_timer_again(conn_.loop, &conn_.wt);
} else {
// we may set read timer cb to idle_timeoutcb. Reset again.
conn_.rt.repeat = get_config()->downstream_read_timeout;
conn_.rt.repeat = downstreamconf.timeout.read;
ev_set_cb(&conn_.rt, timeoutcb);
ev_timer_again(conn_.loop, &conn_.rt);
ev_set_cb(&conn_.rev, readcb);
@ -210,89 +254,82 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
}
int HttpDownstreamConnection::push_request_headers() {
auto downstream_hostport = get_config()
->downstream_addr_groups[group_]
.addrs[addr_idx_]
.hostport.get();
auto method = downstream_->get_request_method();
auto connect_method = method == HTTP_CONNECT;
const auto &downstream_hostport = addr_->hostport;
const auto &req = downstream_->request();
auto connect_method = req.method == HTTP_CONNECT;
auto &httpconf = get_config()->http;
// For HTTP/1.0 request, there is no authority in request. In that
// case, we use backend server's host nonetheless.
const char *authority = downstream_hostport;
auto &req_authority = downstream_->get_request_http2_authority();
auto no_host_rewrite = get_config()->no_host_rewrite ||
auto authority = StringRef(downstream_hostport);
auto no_host_rewrite = httpconf.no_host_rewrite ||
get_config()->http2_proxy ||
get_config()->client_proxy || connect_method;
if (no_host_rewrite && !req_authority.empty()) {
authority = req_authority.c_str();
if (no_host_rewrite && !req.authority.empty()) {
authority = StringRef(req.authority);
}
auto authoritylen = strlen(authority);
downstream_->set_request_downstream_host(authority);
downstream_->assemble_request_cookie();
downstream_->set_request_downstream_host(authority.str());
auto buf = downstream_->get_request_buf();
// Assume that method and request path do not contain \r\n.
auto meth = http2::to_method_string(method);
auto meth = http2::to_method_string(req.method);
buf->append(meth, strlen(meth));
buf->append(" ");
auto &scheme = downstream_->get_request_http2_scheme();
auto &path = downstream_->get_request_path();
if (connect_method) {
buf->append(authority, authoritylen);
buf->append(authority);
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
// Construct absolute-form request target because we are going to
// send a request to a HTTP/1 proxy.
assert(!scheme.empty());
buf->append(scheme);
assert(!req.scheme.empty());
buf->append(req.scheme);
buf->append("://");
buf->append(authority, authoritylen);
buf->append(path);
} else if (method == HTTP_OPTIONS && path.empty()) {
buf->append(authority);
buf->append(req.path);
} else if (req.method == HTTP_OPTIONS && req.path.empty()) {
// Server-wide OPTIONS
buf->append("*");
} else {
buf->append(path);
buf->append(req.path);
}
buf->append(" HTTP/1.1\r\nHost: ");
buf->append(authority, authoritylen);
buf->append(authority);
buf->append("\r\n");
http2::build_http1_headers_from_headers(buf,
downstream_->get_request_headers());
http2::build_http1_headers_from_headers(buf, req.fs.headers());
if (!downstream_->get_assembled_request_cookie().empty()) {
auto cookie = downstream_->assemble_request_cookie();
if (!cookie.empty()) {
buf->append("Cookie: ");
buf->append(downstream_->get_assembled_request_cookie());
buf->append(cookie);
buf->append("\r\n");
}
if (!connect_method && downstream_->get_request_http2_expect_body() &&
!downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) {
if (!connect_method && req.http2_expect_body &&
!req.fs.header(http2::HD_CONTENT_LENGTH)) {
downstream_->set_chunked_request(true);
buf->append("Transfer-Encoding: chunked\r\n");
}
if (downstream_->get_request_connection_close()) {
if (req.connection_close) {
buf->append("Connection: close\r\n");
}
if (!connect_method && downstream_->get_upgrade_request()) {
auto connection = downstream_->get_request_header(http2::HD_CONNECTION);
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 = downstream_->get_request_header(http2::HD_UPGRADE);
auto upgrade = req.fs.header(http2::HD_UPGRADE);
if (upgrade) {
buf->append("Upgrade: ");
buf->append((*upgrade).value);
@ -300,16 +337,57 @@ int HttpDownstreamConnection::push_request_headers() {
}
}
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
if (get_config()->add_x_forwarded_for) {
auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler();
auto &fwdconf = httpconf.forwarded;
auto fwd =
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
if (fwdconf.params) {
auto params = fwdconf.params;
if (get_config()->http2_proxy || get_config()->client_proxy ||
connect_method) {
params &= ~FORWARDED_PROTO;
}
auto value = http::create_forwarded(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 &xffconf = httpconf.xff;
auto xff = xffconf.strip_incoming ? nullptr
: req.fs.header(http2::HD_X_FORWARDED_FOR);
if (xffconf.add) {
buf->append("X-Forwarded-For: ");
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
if (xff) {
buf->append((*xff).value);
buf->append(", ");
}
buf->append(client_handler_->get_ipaddr());
buf->append("\r\n");
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
} else if (xff) {
buf->append("X-Forwarded-For: ");
buf->append((*xff).value);
buf->append("\r\n");
@ -317,12 +395,12 @@ int HttpDownstreamConnection::push_request_headers() {
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!connect_method) {
buf->append("X-Forwarded-Proto: ");
assert(!scheme.empty());
buf->append(scheme);
assert(!req.scheme.empty());
buf->append(req.scheme);
buf->append("\r\n");
}
auto via = downstream_->get_request_header(http2::HD_VIA);
if (get_config()->no_via) {
auto via = req.fs.header(http2::HD_VIA);
if (httpconf.no_via) {
if (via) {
buf->append("Via: ");
buf->append((*via).value);
@ -334,12 +412,11 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append((*via).value);
buf->append(", ");
}
buf->append(http::create_via_header_value(
downstream_->get_request_major(), downstream_->get_request_minor()));
buf->append(http::create_via_header_value(req.http_major, req.http_minor));
buf->append("\r\n");
}
for (auto &p : get_config()->add_request_headers) {
for (auto &p : httpconf.add_request_headers) {
buf->append(p.first);
buf->append(": ");
buf->append(p.second);
@ -372,7 +449,7 @@ int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
if (chunked) {
auto chunk_size_hex = util::utox(datalen);
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
output->append(chunk_size_hex);
output->append("\r\n");
}
@ -392,8 +469,10 @@ int HttpDownstreamConnection::end_upload_data() {
return 0;
}
const auto &req = downstream_->request();
auto output = downstream_->get_request_buf();
auto &trailers = downstream_->get_request_trailers();
const auto &trailers = req.fs.trailers();
if (trailers.empty()) {
output->append("0\r\n\r\n");
} else {
@ -442,7 +521,7 @@ void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
ev_set_cb(&conn_.rev, idle_readcb);
ioctrl_.force_resume_read();
conn_.rt.repeat = get_config()->downstream_idle_read_timeout;
conn_.rt.repeat = get_config()->conn.downstream.timeout.idle_read;
ev_set_cb(&conn_.rt, idle_timeoutcb);
ev_timer_again(conn_.loop, &conn_.rt);
@ -457,7 +536,7 @@ void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
size_t consumed) {
if (downstream_->get_response_buf()->rleft() <=
get_config()->downstream_request_buffer_size / 2) {
get_config()->conn.downstream.request_buffer_size / 2) {
ioctrl_.resume_read(reason);
}
@ -484,13 +563,15 @@ namespace {
int htp_hdrs_completecb(http_parser *htp) {
auto downstream = static_cast<Downstream *>(htp->data);
auto upstream = downstream->get_upstream();
const auto &req = downstream->request();
auto &resp = downstream->response();
int rv;
downstream->set_response_http_status(htp->status_code);
downstream->set_response_major(htp->http_major);
downstream->set_response_minor(htp->http_minor);
resp.http_status = htp->status_code;
resp.http_major = htp->http_major;
resp.http_minor = htp->http_minor;
if (downstream->index_response_headers() != 0) {
if (resp.fs.index_headers() != 0) {
downstream->set_response_state(Downstream::MSG_BAD_HEADER);
return -1;
}
@ -502,7 +583,7 @@ int htp_hdrs_completecb(http_parser *htp) {
if (downstream->get_non_final_response()) {
// Reset content-length because we reuse same Downstream for the
// next response.
downstream->set_response_content_length(-1);
resp.fs.content_length = -1;
// For non-final response code, we just call
// on_downstream_header_complete() without changing response
// state.
@ -516,13 +597,13 @@ int htp_hdrs_completecb(http_parser *htp) {
return 1;
}
downstream->set_response_connection_close(!http_should_keep_alive(htp));
resp.connection_close = !http_should_keep_alive(htp);
downstream->set_response_state(Downstream::HEADER_COMPLETE);
downstream->inspect_http1_response();
if (downstream->get_upgraded()) {
// content-length must be ignored for upgraded connection.
downstream->set_response_content_length(-1);
downstream->set_response_connection_close(true);
resp.fs.content_length = -1;
resp.connection_close = true;
// transfer-encoding not applied to upgraded connection
downstream->set_chunked_response(false);
}
@ -542,7 +623,7 @@ int htp_hdrs_completecb(http_parser *htp) {
}
}
unsigned int status = downstream->get_response_http_status();
auto status = resp.http_status;
// 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
@ -551,29 +632,78 @@ int htp_hdrs_completecb(http_parser *htp) {
// TODO It seems that the cases other than HEAD are handled by
// http-parser. Need test.
return downstream->get_request_method() == HTTP_HEAD ||
(100 <= status && status <= 199) || status == 204 ||
status == 304
return req.method == HTTP_HEAD || (100 <= status && status <= 199) ||
status == 204 || status == 304
? 1
: 0;
}
} // 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(http_parser *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() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) {
downstream->append_last_response_header_key(data, len);
if (resp.fs.header_key_prev()) {
resp.fs.append_last_header_key(data, len);
} else {
downstream->add_response_header(std::string(data, len), "");
if (ensure_max_header_fields(downstream, httpconf) != 0) {
return -1;
}
resp.fs.add_header(std::string(data, len), "");
}
} else {
// trailer part
if (downstream->get_response_trailer_key_prev()) {
downstream->append_last_response_trailer_key(data, len);
if (resp.fs.trailer_key_prev()) {
resp.fs.append_last_trailer_key(data, len);
} else {
downstream->add_response_trailer(std::string(data, len), "");
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.add_trailer(std::string(data, len), "");
}
}
return 0;
@ -583,18 +713,17 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
namespace {
int htp_hdr_valcb(http_parser *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() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) {
downstream->set_last_response_header_value(data, len);
} else {
downstream->append_last_response_header_value(data, len);
}
resp.fs.append_last_header_value(data, len);
} else {
if (downstream->get_response_trailer_key_prev()) {
downstream->set_last_response_trailer_value(data, len);
} else {
downstream->append_last_response_trailer_value(data, len);
}
resp.fs.append_last_trailer_value(data, len);
}
return 0;
}
@ -603,8 +732,9 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
namespace {
int htp_bodycb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data);
auto &resp = downstream->response();
downstream->add_response_bodylen(len);
resp.recv_body_length += len;
return downstream->get_upstream()->on_downstream_body(
downstream, reinterpret_cast<const uint8_t *>(data), len, true);
@ -652,44 +782,13 @@ http_parser_settings htp_hooks = {
};
} // namespace
int HttpDownstreamConnection::on_read() {
if (!connected_) {
return 0;
}
int HttpDownstreamConnection::read_clear() {
ev_timer_again(conn_.loop, &conn_.rt);
std::array<uint8_t, 8_k> buf;
std::array<uint8_t, 16_k> buf;
int rv;
if (downstream_->get_upgraded()) {
// For upgraded connection, just pass data to the upstream.
for (;;) {
auto nread = conn_.read_clear(buf.data(), buf.size());
if (nread == 0) {
return 0;
}
if (nread < 0) {
return nread;
}
rv = downstream_->get_upstream()->on_downstream_body(
downstream_, buf.data(), nread, true);
if (rv != 0) {
return rv;
}
if (downstream_->response_buf_full()) {
downstream_->pause_read(SHRPX_NO_BUFFER);
return 0;
}
}
}
for (;;) {
auto nread = conn_.read_clear(buf.data(), buf.size());
if (nread == 0) {
return 0;
}
@ -698,59 +797,18 @@ int HttpDownstreamConnection::on_read() {
return nread;
}
auto nproc =
http_parser_execute(&response_htp_, &htp_hooks,
reinterpret_cast<char *>(buf.data()), nread);
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
if (htperr != HPE_OK) {
// Handling early return (in other words, response was hijacked
// by mruby scripting).
if (downstream_->get_response_state() == Downstream::MSG_COMPLETE) {
return SHRPX_ERR_DCONN_CANCELED;
}
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP parser failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
}
return -1;
rv = process_input(buf.data(), nread);
if (rv != 0) {
return rv;
}
if (downstream_->response_buf_full()) {
downstream_->pause_read(SHRPX_NO_BUFFER);
if (!ev_is_active(&conn_.rev)) {
return 0;
}
if (downstream_->get_upgraded()) {
if (nproc < static_cast<size_t>(nread)) {
// Data from buf.data() + nproc are for upgraded protocol.
rv = downstream_->get_upstream()->on_downstream_body(
downstream_, buf.data() + nproc, nread - nproc, true);
if (rv != 0) {
return rv;
}
if (downstream_->response_buf_full()) {
downstream_->pause_read(SHRPX_NO_BUFFER);
return 0;
}
}
// call on_read(), so that we can process data left in buffer as
// upgrade.
return on_read();
}
}
}
int HttpDownstreamConnection::on_write() {
if (!connected_) {
return 0;
}
int HttpDownstreamConnection::write_clear() {
ev_timer_again(conn_.loop, &conn_.rt);
auto upstream = downstream_->get_upstream();
@ -778,14 +836,181 @@ int HttpDownstreamConnection::on_write() {
ev_timer_stop(conn_.loop, &conn_.wt);
if (input->rleft() == 0) {
auto &req = downstream_->request();
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
downstream_->get_request_datalen());
req.unconsumed_body_length);
}
return 0;
}
int HttpDownstreamConnection::on_connect() {
int HttpDownstreamConnection::tls_handshake() {
ERR_clear_error();
ev_timer_again(conn_.loop, &conn_.rt);
auto rv = conn_.tls_handshake();
if (rv == SHRPX_ERR_INPROGRESS) {
return 0;
}
if (rv < 0) {
return rv;
}
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "SSL/TLS handshake completed";
}
if (!get_config()->tls.insecure &&
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
return -1;
}
do_read_ = &HttpDownstreamConnection::read_tls;
do_write_ = &HttpDownstreamConnection::write_tls;
// TODO Check negotiated ALPN
return on_write();
}
int HttpDownstreamConnection::read_tls() {
ERR_clear_error();
ev_timer_again(conn_.loop, &conn_.rt);
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) {
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() {
ERR_clear_error();
ev_timer_again(conn_.loop, &conn_.rt);
auto upstream = downstream_->get_upstream();
auto input = downstream_->get_request_buf();
struct iovec iov;
while (input->rleft() > 0) {
auto iovcnt = input->riovec(&iov, 1);
assert(iovcnt == 1);
auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
if (nwrite == 0) {
return 0;
}
if (nwrite < 0) {
return nwrite;
}
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 nproc =
http_parser_execute(&response_htp_, &htp_hooks,
reinterpret_cast<const char *>(data), datalen);
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
if (htperr != HPE_OK) {
// Handling early return (in other words, response was hijacked by
// mruby scripting).
if (downstream_->get_response_state() == Downstream::MSG_COMPLETE) {
return SHRPX_ERR_DCONN_CANCELED;
}
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP parser failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
}
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 = client_handler_->get_connect_blocker();
if (!util::check_socket_connected(conn_.fd)) {
@ -800,18 +1025,33 @@ int HttpDownstreamConnection::on_connect() {
return -1;
}
connected_ = true;
if (LOG_ENABLED(INFO)) {
DLOG(INFO, this) << "Connected to downstream host";
}
connect_blocker->on_success();
conn_.rlimit.startw();
ev_timer_again(conn_.loop, &conn_.rt);
ev_set_cb(&conn_.wev, writecb);
if (conn_.tls.ssl) {
do_read_ = &HttpDownstreamConnection::tls_handshake;
do_write_ = &HttpDownstreamConnection::tls_handshake;
return 0;
}
do_read_ = &HttpDownstreamConnection::read_clear;
do_write_ = &HttpDownstreamConnection::write_clear;
return 0;
}
int HttpDownstreamConnection::on_read() { return do_read_(*this); }
int HttpDownstreamConnection::on_write() { return do_write_(*this); }
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
void HttpDownstreamConnection::signal_write() {
@ -820,4 +1060,6 @@ void HttpDownstreamConnection::signal_write() {
size_t HttpDownstreamConnection::get_group() const { return group_; }
int HttpDownstreamConnection::noop() { return 0; }
} // namespace shrpx

View File

@ -36,11 +36,12 @@
namespace shrpx {
class DownstreamConnectionPool;
class Worker;
class HttpDownstreamConnection : public DownstreamConnection {
public:
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, size_t group,
struct ev_loop *loop);
struct ev_loop *loop, Worker *worker);
virtual ~HttpDownstreamConnection();
virtual int attach_downstream(Downstream *downstream);
virtual void detach_downstream(Downstream *downstream);
@ -57,22 +58,34 @@ public:
virtual int on_write();
virtual void on_upstream_change(Upstream *upstream);
virtual int on_priority_change(int32_t pri) { return 0; }
virtual size_t get_group() const;
virtual bool poolable() const { return true; }
int on_connect();
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 noop();
private:
Connection conn_;
std::function<int(HttpDownstreamConnection &)> do_read_, do_write_;
Worker *worker_;
// nullptr if TLS is not used.
SSL_CTX *ssl_ctx_;
// Address of remote endpoint
const DownstreamAddr *addr_;
IOControl ioctrl_;
http_parser response_htp_;
size_t group_;
// index of get_config()->downstream_addrs this object is using
size_t addr_idx_;
bool connected_;
};
} // namespace shrpx

64
src/shrpx_http_test.cc Normal file
View File

@ -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.
*/
#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"
namespace shrpx {
void test_shrpx_http_create_forwarded(void) {
CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";"
"proto=https" ==
http::create_forwarded(
FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO,
"example.com:3000", "[::1]", "www.example.com", "https"));
CU_ASSERT("for=192.168.0.1" == http::create_forwarded(FORWARDED_FOR, "alpha",
"192.168.0.1", "bravo",
"charlie"));
CU_ASSERT("by=_hidden;for=\"[::1]\"" ==
http::create_forwarded(FORWARDED_BY | FORWARDED_FOR, "_hidden",
"[::1]", "", ""));
CU_ASSERT("by=\"[::1]\";for=_hidden" ==
http::create_forwarded(FORWARDED_BY | FORWARDED_FOR, "[::1]",
"_hidden", "", ""));
CU_ASSERT("" == http::create_forwarded(FORWARDED_BY | FORWARDED_FOR |
FORWARDED_HOST | FORWARDED_PROTO,
"", "", "", ""));
}
} // namespace shrpx

38
src/shrpx_http_test.h Normal file
View File

@ -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_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);
} // namespace shrpx
#endif // SHRPX_HTTP_TEST_H

View File

@ -49,7 +49,8 @@ using namespace nghttp2;
namespace shrpx {
HttpsUpstream::HttpsUpstream(ClientHandler *handler)
: handler_(handler), current_header_length_(0),
: handler_(handler),
current_header_length_(0),
ioctrl_(handler->get_rlimit()) {
http_parser_init(&htp_, HTTP_REQUEST);
htp_.data = this;
@ -71,9 +72,7 @@ int htp_msg_begin(http_parser *htp) {
auto handler = upstream->get_client_handler();
// TODO specify 0 as priority for now
auto downstream =
make_unique<Downstream>(upstream, handler->get_mcpool(), 0, 0);
auto downstream = make_unique<Downstream>(upstream, handler->get_mcpool(), 0);
upstream->attach_downstream(std::move(downstream));
@ -85,25 +84,28 @@ namespace {
int htp_uricb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream();
auto &req = downstream->request();
// We happen to have the same value for method token.
downstream->set_request_method(htp->method);
req.method = htp->method;
if (downstream->get_request_headers_sum() + len >
get_config()->header_field_buffer) {
if (req.fs.buffer_size() + len >
get_config()->http.request_header_field_buffer) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large URI size="
<< downstream->get_request_headers_sum() + len;
<< req.fs.buffer_size() + len;
}
assert(downstream->get_request_state() == Downstream::INITIAL);
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
return -1;
}
downstream->add_request_headers_sum(len);
if (downstream->get_request_method() == HTTP_CONNECT) {
downstream->append_request_http2_authority(data, len);
req.fs.add_extra_buffer_size(len);
if (req.method == HTTP_CONNECT) {
req.authority.append(data, len);
} else {
downstream->append_request_path(data, len);
req.path.append(data, len);
}
return 0;
@ -114,11 +116,13 @@ namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream();
if (downstream->get_request_headers_sum() + len >
get_config()->header_field_buffer) {
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="
<< downstream->get_request_headers_sum() + len;
<< req.fs.buffer_size() + len;
}
if (downstream->get_request_state() == Downstream::INITIAL) {
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
@ -126,36 +130,33 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
return -1;
}
if (downstream->get_request_state() == Downstream::INITIAL) {
if (downstream->get_request_header_key_prev()) {
downstream->append_last_request_header_key(data, len);
if (req.fs.header_key_prev()) {
req.fs.append_last_header_key(data, len);
} else {
if (downstream->get_request_headers().size() >=
get_config()->max_header_fields) {
if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too many header field num="
<< downstream->get_request_headers().size() + 1;
ULOG(INFO, upstream)
<< "Too many header field num=" << req.fs.num_fields() + 1;
}
downstream->set_request_state(
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
return -1;
}
downstream->add_request_header(std::string(data, len), "");
req.fs.add_header(std::string(data, len), "");
}
} else {
// trailer part
if (downstream->get_request_trailer_key_prev()) {
downstream->append_last_request_trailer_key(data, len);
if (req.fs.trailer_key_prev()) {
req.fs.append_last_trailer_key(data, len);
} else {
if (downstream->get_request_headers().size() +
downstream->get_request_trailers().size() >=
get_config()->max_header_fields) {
if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too many header field num="
<< downstream->get_request_headers().size() + 1;
ULOG(INFO, upstream)
<< "Too many header field num=" << req.fs.num_fields() + 1;
}
return -1;
}
downstream->add_request_trailer(std::string(data, len), "");
req.fs.add_trailer(std::string(data, len), "");
}
}
return 0;
@ -166,11 +167,13 @@ namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream();
if (downstream->get_request_headers_sum() + len >
get_config()->header_field_buffer) {
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="
<< downstream->get_request_headers_sum() + len;
<< req.fs.buffer_size() + len;
}
if (downstream->get_request_state() == Downstream::INITIAL) {
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
@ -178,30 +181,23 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
return -1;
}
if (downstream->get_request_state() == Downstream::INITIAL) {
if (downstream->get_request_header_key_prev()) {
downstream->set_last_request_header_value(data, len);
} else {
downstream->append_last_request_header_value(data, len);
}
req.fs.append_last_header_value(data, len);
} else {
if (downstream->get_request_trailer_key_prev()) {
downstream->set_last_request_trailer_value(data, len);
} else {
downstream->append_last_request_trailer_value(data, len);
}
req.fs.append_last_trailer_value(data, len);
}
return 0;
}
} // namespace
namespace {
void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
void rewrite_request_host_path_from_uri(Request &req, const char *uri,
http_parser_url &u) {
assert(u.field_set & (1 << UF_HOST));
auto &authority = req.authority;
authority.clear();
// As per https://tools.ietf.org/html/rfc7230#section-5.4, we
// rewrite host header field with authority component.
std::string authority;
http2::copy_url_component(authority, &u, UF_HOST, uri);
// TODO properly check IPv6 numeric address
if (authority.find(':') != std::string::npos) {
@ -212,23 +208,20 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
authority += ':';
authority += util::utos(u.port);
}
downstream->set_request_http2_authority(authority);
std::string scheme;
http2::copy_url_component(scheme, &u, UF_SCHEMA, uri);
downstream->set_request_http2_scheme(std::move(scheme));
http2::copy_url_component(req.scheme, &u, UF_SCHEMA, uri);
std::string path;
if (u.field_set & (1 << UF_PATH)) {
http2::copy_url_component(path, &u, UF_PATH, uri);
} else if (downstream->get_request_method() == HTTP_OPTIONS) {
} 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
downstream->set_request_path("");
req.path = "";
// we ignore query component here
return;
} else {
@ -240,10 +233,9 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
path.append(uri + fdata.off, fdata.len);
}
if (get_config()->http2_proxy || get_config()->client_proxy) {
downstream->set_request_path(std::move(path));
req.path = std::move(path);
} else {
downstream->set_request_path(
http2::rewrite_clean_path(std::begin(path), std::end(path)));
req.path = http2::rewrite_clean_path(std::begin(path), std::end(path));
}
}
} // namespace
@ -256,46 +248,57 @@ int htp_hdrs_completecb(http_parser *htp) {
ULOG(INFO, upstream) << "HTTP request headers completed";
}
auto downstream = upstream->get_downstream();
auto &req = downstream->request();
downstream->set_request_major(htp->http_major);
downstream->set_request_minor(htp->http_minor);
req.http_major = htp->http_major;
req.http_minor = htp->http_minor;
downstream->set_request_connection_close(!http_should_keep_alive(htp));
req.connection_close = !http_should_keep_alive(htp);
auto method = downstream->get_request_method();
auto method = req.method;
if (LOG_ENABLED(INFO)) {
std::stringstream ss;
ss << http2::to_method_string(method) << " "
<< (method == HTTP_CONNECT ? downstream->get_request_http2_authority()
: downstream->get_request_path()) << " "
<< "HTTP/" << downstream->get_request_major() << "."
<< downstream->get_request_minor() << "\n";
const auto &headers = downstream->get_request_headers();
for (size_t i = 0; i < headers.size(); ++i) {
ss << TTY_HTTP_HD << headers[i].name << TTY_RST << ": "
<< headers[i].value << "\n";
<< (method == HTTP_CONNECT ? req.authority : req.path) << " "
<< "HTTP/" << req.http_major << "." << req.http_minor << "\n";
for (const auto &kv : req.fs.headers()) {
ss << TTY_HTTP_HD << kv.name << TTY_RST << ": " << kv.value << "\n";
}
ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
}
if (downstream->index_request_headers() != 0) {
if (req.fs.index_headers() != 0) {
return -1;
}
if (downstream->get_request_major() == 1 &&
downstream->get_request_minor() == 1 &&
!downstream->get_request_header(http2::HD_HOST)) {
auto host = req.fs.header(http2::HD_HOST);
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 (method != HTTP_CONNECT) {
http_parser_url u{};
// make a copy of request path, since we may set request path
// while we are refering to original request path.
auto path = downstream->get_request_path();
auto path = req.path;
rv = http_parser_parse_url(path.c_str(), path.size(), 0, &u);
if (rv != 0) {
// Expect to respond with 400 bad request
@ -308,25 +311,25 @@ int htp_hdrs_completecb(http_parser *htp) {
return -1;
}
req.no_authority = true;
if (method == HTTP_OPTIONS && path == "*") {
downstream->set_request_path("");
req.path = "";
} else {
downstream->set_request_path(
http2::rewrite_clean_path(std::begin(path), std::end(path)));
req.path = http2::rewrite_clean_path(std::begin(path), std::end(path));
}
auto host = downstream->get_request_header(http2::HD_HOST);
if (host) {
downstream->set_request_http2_authority(host->value);
req.authority = host->value;
}
if (upstream->get_client_handler()->get_ssl()) {
downstream->set_request_http2_scheme("https");
req.scheme = "https";
} else {
downstream->set_request_http2_scheme("http");
req.scheme = "http";
}
} else {
rewrite_request_host_path_from_uri(downstream, path.c_str(), u);
rewrite_request_host_path_from_uri(req, path.c_str(), u);
}
}
@ -337,8 +340,10 @@ int htp_hdrs_completecb(http_parser *htp) {
auto worker = handler->get_worker();
auto mruby_ctx = worker->get_mruby_context();
auto &resp = downstream->response();
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
downstream->set_response_http_status(500);
resp.http_status = 500;
return -1;
}
#endif // HAVE_MRUBY
@ -527,7 +532,7 @@ int HttpsUpstream::on_read() {
if (htperr == HPE_INVALID_METHOD) {
status_code = 501;
} else if (downstream) {
status_code = downstream->get_response_http_status();
status_code = downstream->response().http_status;
if (status_code == 0) {
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
status_code = 503;
@ -569,20 +574,8 @@ int HttpsUpstream::on_write() {
return 0;
}
auto dconn = downstream->get_downstream_connection();
auto output = downstream->get_response_buf();
if (output->rleft() == 0 && dconn &&
downstream->get_response_state() != Downstream::MSG_COMPLETE) {
if (downstream->resume_read(SHRPX_NO_BUFFER,
downstream->get_response_datalen()) != 0) {
return -1;
}
if (downstream_read(dconn) != 0) {
return -1;
}
}
const auto &resp = downstream->response();
if (output->rleft() > 0) {
return 0;
@ -591,7 +584,7 @@ int HttpsUpstream::on_write() {
// We need to postpone detachment until all data are sent so that
// we can notify nghttp2 library all data consumed.
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
if (downstream->get_response_connection_close() ||
if (resp.connection_close ||
downstream->get_request_state() != Downstream::MSG_COMPLETE) {
// Connection close
downstream->pop_downstream_connection();
@ -612,8 +605,7 @@ int HttpsUpstream::on_write() {
}
}
return downstream->resume_read(SHRPX_NO_BUFFER,
downstream->get_response_datalen());
return downstream->resume_read(SHRPX_NO_BUFFER, resp.unconsumed_body_length);
}
int HttpsUpstream::on_event() { return 0; }
@ -766,32 +758,31 @@ int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
size_t bodylen) {
auto major = downstream->get_request_major();
auto minor = downstream->get_request_minor();
const auto &req = downstream->request();
auto &resp = downstream->response();
auto connection_close = false;
if (major <= 0 || (major == 1 && minor == 0)) {
if (req.http_major <= 0 || (req.http_major == 1 && req.http_minor == 0)) {
connection_close = true;
} else {
auto c = downstream->get_response_header(http2::HD_CONNECTION);
auto c = resp.fs.header(http2::HD_CONNECTION);
if (c && util::strieq_l("close", c->value)) {
connection_close = true;
}
}
if (connection_close) {
downstream->set_response_connection_close(true);
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::get_status_string(downstream->get_response_http_status()));
output->append(http2::get_status_string(resp.http_status));
output->append("\r\n");
for (auto &kv : downstream->get_response_headers()) {
for (auto &kv : resp.fs.headers()) {
if (kv.name.empty() || kv.name[0] == ':') {
continue;
}
@ -801,10 +792,9 @@ int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
output->append("\r\n");
}
if (!downstream->get_response_header(http2::HD_SERVER)) {
if (!resp.fs.header(http2::HD_SERVER)) {
output->append("Server: ");
output->append(get_config()->server_name,
strlen(get_config()->server_name));
output->append(get_config()->http.server_name);
output->append("\r\n");
}
@ -812,7 +802,7 @@ int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
output->append(body, bodylen);
downstream->add_response_sent_bodylen(bodylen);
downstream->response_sent_body_length += bodylen;
downstream->set_response_state(Downstream::MSG_COMPLETE);
return 0;
@ -823,37 +813,38 @@ void HttpsUpstream::error_reply(unsigned int status_code) {
auto downstream = get_downstream();
if (!downstream) {
attach_downstream(
make_unique<Downstream>(this, handler_->get_mcpool(), 1, 1));
attach_downstream(make_unique<Downstream>(this, handler_->get_mcpool(), 1));
downstream = get_downstream();
}
downstream->set_response_http_status(status_code);
auto &resp = downstream->response();
resp.http_status = status_code;
// we are going to close connection for both frontend and backend in
// error condition. This is safest option.
downstream->set_response_connection_close(true);
resp.connection_close = true;
handler_->set_should_close_after_write(true);
auto output = downstream->get_response_buf();
output->append("HTTP/1.1 ");
auto status_str = http2::get_status_string(status_code);
output->append(status_str.c_str(), status_str.size());
output->append(status_str);
output->append("\r\nServer: ");
output->append(get_config()->server_name, strlen(get_config()->server_name));
output->append(get_config()->http.server_name);
output->append("\r\nContent-Length: ");
auto cl = util::utos(html.size());
output->append(cl.c_str(), cl.size());
output->append(cl);
output->append("\r\nDate: ");
auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now());
auto &date = lgconf->time_http_str;
output->append(date.c_str(), date.size());
output->append(date);
output->append("\r\nContent-Type: text/html; "
"charset=UTF-8\r\nConnection: close\r\n\r\n");
output->append(html.c_str(), html.size());
output->append(html);
downstream->add_response_sent_bodylen(html.size());
downstream->response_sent_body_length += html.size();
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
@ -896,6 +887,9 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
}
}
const auto &req = downstream->request();
auto &resp = downstream->response();
#ifdef HAVE_MRUBY
if (!downstream->get_non_final_response()) {
auto worker = handler_->get_worker();
@ -912,26 +906,27 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
}
#endif // HAVE_MRUBY
auto connect_method = downstream->get_request_method() == HTTP_CONNECT;
auto connect_method = req.method == HTTP_CONNECT;
auto buf = downstream->get_response_buf();
buf->append("HTTP/");
buf->append(util::utos(downstream->get_request_major()));
buf->append(util::utos(req.http_major));
buf->append(".");
buf->append(util::utos(downstream->get_request_minor()));
buf->append(util::utos(req.http_minor));
buf->append(" ");
buf->append(http2::get_status_string(downstream->get_response_http_status()));
buf->append(http2::get_status_string(resp.http_status));
buf->append("\r\n");
auto &httpconf = get_config()->http;
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!get_config()->no_location_rewrite) {
!httpconf.no_location_rewrite) {
downstream->rewrite_location_response_header(
get_client_handler()->get_upstream_scheme());
}
http2::build_http1_headers_from_headers(buf,
downstream->get_response_headers());
http2::build_http1_headers_from_headers(buf, resp.fs.headers());
if (downstream->get_non_final_response()) {
buf->append("\r\n");
@ -940,7 +935,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
log_response_headers(buf);
}
downstream->clear_response_headers();
resp.fs.clear_headers();
return 0;
}
@ -950,15 +945,13 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
// after graceful shutdown commenced, add connection: close header
// field.
if (worker->get_graceful_shutdown()) {
downstream->set_response_connection_close(true);
resp.connection_close = true;
}
// We check downstream->get_response_connection_close() in case when
// the Content-Length is not available.
if (!downstream->get_request_connection_close() &&
!downstream->get_response_connection_close()) {
if (downstream->get_request_major() <= 0 ||
downstream->get_request_minor() <= 0) {
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");
}
@ -967,14 +960,14 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
}
if (!connect_method && downstream->get_upgraded()) {
auto connection = downstream->get_response_header(http2::HD_CONNECTION);
auto connection = resp.fs.header(http2::HD_CONNECTION);
if (connection) {
buf->append("Connection: ");
buf->append((*connection).value);
buf->append("\r\n");
}
auto upgrade = downstream->get_response_header(http2::HD_UPGRADE);
auto upgrade = resp.fs.header(http2::HD_UPGRADE);
if (upgrade) {
buf->append("Upgrade: ");
buf->append((*upgrade).value);
@ -982,12 +975,12 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
}
}
if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
if (!resp.fs.header(http2::HD_ALT_SVC)) {
// We won't change or alter alt-svc from backend for now
if (!get_config()->altsvcs.empty()) {
if (!httpconf.altsvcs.empty()) {
buf->append("Alt-Svc: ");
auto &altsvcs = get_config()->altsvcs;
auto &altsvcs = httpconf.altsvcs;
write_altsvc(buf, altsvcs[0]);
for (size_t i = 1; i < altsvcs.size(); ++i) {
buf->append(", ");
@ -999,10 +992,10 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
buf->append("Server: ");
buf->append(get_config()->server_name, strlen(get_config()->server_name));
buf->append(httpconf.server_name);
buf->append("\r\n");
} else {
auto server = downstream->get_response_header(http2::HD_SERVER);
auto server = resp.fs.header(http2::HD_SERVER);
if (server) {
buf->append("Server: ");
buf->append((*server).value);
@ -1010,8 +1003,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
}
}
auto via = downstream->get_response_header(http2::HD_VIA);
if (get_config()->no_via) {
auto via = resp.fs.header(http2::HD_VIA);
if (httpconf.no_via) {
if (via) {
buf->append("Via: ");
buf->append((*via).value);
@ -1023,12 +1016,12 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
buf->append((*via).value);
buf->append(", ");
}
buf->append(http::create_via_header_value(
downstream->get_response_major(), downstream->get_response_minor()));
buf->append(
http::create_via_header_value(resp.http_major, resp.http_minor));
buf->append("\r\n");
}
for (auto &p : get_config()->add_response_headers) {
for (auto &p : httpconf.add_response_headers) {
buf->append(p.first);
buf->append(": ");
buf->append(p.second);
@ -1052,14 +1045,12 @@ int HttpsUpstream::on_downstream_body(Downstream *downstream,
}
auto output = downstream->get_response_buf();
if (downstream->get_chunked_response()) {
auto chunk_size_hex = util::utox(len);
chunk_size_hex += "\r\n";
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
output->append(util::utox(len));
output->append("\r\n");
}
output->append(data, len);
downstream->add_response_sent_bodylen(len);
downstream->response_sent_body_length += len;
if (downstream->get_chunked_response()) {
output->append("\r\n");
@ -1068,9 +1059,12 @@ int HttpsUpstream::on_downstream_body(Downstream *downstream,
}
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();
auto &trailers = downstream->get_response_trailers();
const auto &trailers = resp.fs.trailers();
if (trailers.empty()) {
output->append("0\r\n\r\n");
} else {
@ -1083,12 +1077,11 @@ int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
DLOG(INFO, downstream) << "HTTP response completed";
}
if (!downstream->validate_response_bodylen()) {
downstream->set_response_connection_close(true);
if (!downstream->validate_response_recv_body_length()) {
resp.connection_close = true;
}
if (downstream->get_request_connection_close() ||
downstream->get_response_connection_close()) {
if (req.connection_close || resp.connection_close) {
auto handler = get_client_handler();
handler->set_should_close_after_write(true);
}

View File

@ -109,12 +109,14 @@ Log::~Log() {
auto lgconf = log_config();
auto &errorconf = get_config()->logging.error;
if (!log_enabled(severity_) ||
(lgconf->errorlog_fd == -1 && !get_config()->errorlog_syslog)) {
(lgconf->errorlog_fd == -1 && !errorconf.syslog)) {
return;
}
if (get_config()->errorlog_syslog) {
if (errorconf.syslog) {
if (severity_ == NOTICE) {
syslog(severity_to_syslog_level(severity_), "[%s] %s",
SEVERITY_STR[severity_], stream_.str().c_str());
@ -182,6 +184,22 @@ std::pair<OutputIterator, size_t> copy(const std::string &src, size_t avail,
}
} // namespace
namespace {
template <typename OutputIterator>
std::pair<OutputIterator, size_t> copy(const StringRef &src, size_t avail,
OutputIterator oitr) {
return copy(src.c_str(), src.size(), avail, oitr);
}
} // namespace
namespace {
template <typename OutputIterator>
std::pair<OutputIterator, size_t> copy(const ImmutableString &src, size_t avail,
OutputIterator oitr) {
return copy(src.c_str(), src.size(), avail, oitr);
}
} // namespace
namespace {
template <size_t N, typename OutputIterator>
std::pair<OutputIterator, size_t> copy_l(const char(&src)[N], size_t avail,
@ -211,8 +229,9 @@ std::pair<OutputIterator, size_t> copy_hex_low(const uint8_t *src,
void upstream_accesslog(const std::vector<LogFragment> &lfv,
const LogSpec &lgsp) {
auto lgconf = log_config();
auto &accessconf = get_config()->logging.access;
if (lgconf->accesslog_fd == -1 && !get_config()->accesslog_syslog) {
if (lgconf->accesslog_fd == -1 && !accessconf.syslog) {
return;
}
@ -220,6 +239,11 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
auto downstream = lgsp.downstream;
const Request *req = nullptr;
if (downstream) {
req = &downstream->request();
}
auto p = buf;
auto avail = sizeof(buf) - 2;
@ -230,7 +254,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
for (auto &lf : lfv) {
switch (lf.type) {
case SHRPX_LOGF_LITERAL:
std::tie(p, avail) = copy(lf.value.get(), avail, p);
std::tie(p, avail) = copy(lf.value, avail, p);
break;
case SHRPX_LOGF_REMOTE_ADDR:
std::tie(p, avail) = copy(lgsp.remote_addr, avail, p);
@ -259,8 +283,8 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
std::tie(p, avail) = copy(util::utos(lgsp.body_bytes_sent), avail, p);
break;
case SHRPX_LOGF_HTTP:
if (downstream) {
auto hd = downstream->get_request_header(lf.value.get());
if (req) {
auto hd = req->fs.header(StringRef(lf.value));
if (hd) {
std::tie(p, avail) = copy((*hd).value, avail, p);
break;
@ -271,10 +295,9 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
break;
case SHRPX_LOGF_AUTHORITY:
if (downstream) {
auto &authority = downstream->get_request_http2_authority();
if (!authority.empty()) {
std::tie(p, avail) = copy(authority, avail, p);
if (req) {
if (!req->authority.empty()) {
std::tie(p, avail) = copy(req->authority, avail, p);
break;
}
}
@ -348,7 +371,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
*p = '\0';
if (get_config()->accesslog_syslog) {
if (accessconf.syslog) {
syslog(LOG_INFO, "%s", buf);
return;
@ -367,29 +390,27 @@ int reopen_log_files() {
int new_errorlog_fd = -1;
auto lgconf = log_config();
auto &accessconf = get_config()->logging.access;
auto &errorconf = get_config()->logging.error;
if (!get_config()->accesslog_syslog && get_config()->accesslog_file) {
new_accesslog_fd = util::open_log_file(get_config()->accesslog_file.get());
if (!accessconf.syslog && accessconf.file) {
new_accesslog_fd = util::open_log_file(accessconf.file.get());
if (new_accesslog_fd == -1) {
LOG(ERROR) << "Failed to open accesslog file "
<< get_config()->accesslog_file.get();
LOG(ERROR) << "Failed to open accesslog file " << accessconf.file.get();
res = -1;
}
}
if (!get_config()->errorlog_syslog && get_config()->errorlog_file) {
new_errorlog_fd = util::open_log_file(get_config()->errorlog_file.get());
if (!errorconf.syslog && errorconf.file) {
new_errorlog_fd = util::open_log_file(errorconf.file.get());
if (new_errorlog_fd == -1) {
if (lgconf->errorlog_fd != -1) {
LOG(ERROR) << "Failed to open errorlog file "
<< get_config()->errorlog_file.get();
LOG(ERROR) << "Failed to open errorlog file " << errorconf.file.get();
} else {
std::cerr << "Failed to open errorlog file "
<< get_config()->errorlog_file.get() << std::endl;
std::cerr << "Failed to open errorlog file " << errorconf.file.get()
<< std::endl;
}
res = -1;
@ -432,8 +453,9 @@ void log_chld(pid_t pid, int rstatus, const char *msg) {
void redirect_stderr_to_errorlog() {
auto lgconf = log_config();
auto &errorconf = get_config()->logging.error;
if (get_config()->errorlog_syslog || lgconf->errorlog_fd == -1) {
if (errorconf.syslog || lgconf->errorlog_fd == -1) {
return;
}

View File

@ -36,6 +36,9 @@
#include "shrpx_log_config.h"
#include "ssl.h"
#include "template.h"
using namespace nghttp2;
#define ENABLE_LOG 1
@ -131,18 +134,18 @@ enum LogFragmentType {
};
struct LogFragment {
LogFragment(LogFragmentType type, std::unique_ptr<char[]> value = nullptr)
LogFragment(LogFragmentType type, ImmutableString value = ImmutableString())
: type(type), value(std::move(value)) {}
LogFragmentType type;
std::unique_ptr<char[]> value;
ImmutableString value;
};
struct LogSpec {
Downstream *downstream;
const char *remote_addr;
const char *method;
const char *path;
const char *alpn;
StringRef remote_addr;
StringRef method;
StringRef path;
StringRef alpn;
const nghttp2::ssl::TLSSessionInfo *tls_info;
std::chrono::system_clock::time_point time_now;
std::chrono::high_resolution_clock::time_point request_start_time;
@ -150,7 +153,7 @@ struct LogSpec {
int major, minor;
unsigned int status;
int64_t body_bytes_sent;
const char *remote_port;
StringRef remote_port;
uint16_t server_port;
pid_t pid;
};

View File

@ -92,9 +92,12 @@ constexpr ev_tstamp read_timeout = 10.;
MemcachedConnection::MemcachedConnection(const Address *addr,
struct ev_loop *loop)
: conn_(loop, -1, nullptr, nullptr, write_timeout, read_timeout, 0, 0, 0, 0,
: conn_(loop, -1, nullptr, nullptr, write_timeout, read_timeout, {}, {},
connectcb, readcb, timeoutcb, this, 0, 0.),
parse_state_{}, addr_(addr), sendsum_(0), connected_(false) {}
parse_state_{},
addr_(addr),
sendsum_(0),
connected_(false) {}
MemcachedConnection::~MemcachedConnection() { disconnect(); }

View File

@ -96,11 +96,11 @@ int MRubyContext::run_app(Downstream *downstream, int phase) {
mrb_->ud = nullptr;
if (data.request_headers_dirty) {
downstream->index_request_headers();
downstream->request().fs.index_headers();
}
if (data.response_headers_dirty) {
downstream->index_response_headers();
downstream->response().fs.index_headers();
}
return rv;

View File

@ -49,7 +49,8 @@ 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;
return mrb_fixnum_value(downstream->get_request_major());
const auto &req = downstream->request();
return mrb_fixnum_value(req.http_major);
}
} // namespace
@ -57,7 +58,8 @@ 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;
return mrb_fixnum_value(downstream->get_request_minor());
const auto &req = downstream->request();
return mrb_fixnum_value(req.http_minor);
}
} // namespace
@ -65,7 +67,8 @@ namespace {
mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
auto method = http2::to_method_string(downstream->get_request_method());
const auto &req = downstream->request();
auto method = http2::to_method_string(req.method);
return mrb_str_new_cstr(mrb, method);
}
@ -75,6 +78,7 @@ 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);
@ -90,7 +94,7 @@ mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported");
}
downstream->set_request_method(token);
req.method = token;
return self;
}
@ -100,9 +104,9 @@ namespace {
mrb_value request_get_authority(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
auto &authority = downstream->get_request_http2_authority();
const auto &req = downstream->request();
return mrb_str_new(mrb, authority.c_str(), authority.size());
return mrb_str_new(mrb, req.authority.c_str(), req.authority.size());
}
} // namespace
@ -110,6 +114,7 @@ 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();
check_phase(mrb, data->phase, PHASE_REQUEST);
@ -120,7 +125,7 @@ mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string");
}
downstream->set_request_http2_authority(std::string(authority, n));
req.authority.assign(authority, n);
return self;
}
@ -130,9 +135,9 @@ namespace {
mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
auto &scheme = downstream->get_request_http2_scheme();
const auto &req = downstream->request();
return mrb_str_new(mrb, scheme.c_str(), scheme.size());
return mrb_str_new(mrb, req.scheme.c_str(), req.scheme.size());
}
} // namespace
@ -140,6 +145,7 @@ 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();
check_phase(mrb, data->phase, PHASE_REQUEST);
@ -150,7 +156,7 @@ mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string");
}
downstream->set_request_http2_scheme(std::string(scheme, n));
req.scheme.assign(scheme, n);
return self;
}
@ -160,9 +166,9 @@ namespace {
mrb_value request_get_path(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
auto &path = downstream->get_request_path();
const auto &req = downstream->request();
return mrb_str_new(mrb, path.c_str(), path.size());
return mrb_str_new(mrb, req.path.c_str(), req.path.size());
}
} // namespace
@ -170,6 +176,7 @@ 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();
check_phase(mrb, data->phase, PHASE_REQUEST);
@ -177,7 +184,7 @@ mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
mrb_int pathlen;
mrb_get_args(mrb, "s", &path, &pathlen);
downstream->set_request_path(std::string(path, pathlen));
req.path.assign(path, pathlen);
return self;
}
@ -187,7 +194,8 @@ namespace {
mrb_value request_get_headers(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
return create_headers_hash(mrb, downstream->get_request_headers());
const auto &req = downstream->request();
return create_headers_hash(mrb, req.fs.headers());
}
} // namespace
@ -195,6 +203,7 @@ 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();
check_phase(mrb, data->phase, PHASE_REQUEST);
@ -209,7 +218,7 @@ mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
if (repl) {
size_t p = 0;
auto &headers = downstream->get_request_headers();
auto &headers = req.fs.headers();
for (size_t i = 0; i < headers.size(); ++i) {
auto &hd = headers[i];
if (util::streq(std::begin(hd.name), hd.name.size(), RSTRING_PTR(key),
@ -227,14 +236,12 @@ mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
auto n = mrb_ary_len(mrb, values);
for (int i = 0; i < n; ++i) {
auto value = mrb_ary_entry(values, i);
downstream->add_request_header(
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
req.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
}
} else if (!mrb_nil_p(values)) {
downstream->add_request_header(
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
req.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
}
data->request_headers_dirty = true;
@ -259,10 +266,11 @@ 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);
downstream->clear_request_headers();
req.fs.clear_headers();
return mrb_nil_value();
}

View File

@ -49,7 +49,8 @@ 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;
return mrb_fixnum_value(downstream->get_response_major());
const auto &resp = downstream->response();
return mrb_fixnum_value(resp.http_major);
}
} // namespace
@ -57,7 +58,8 @@ 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;
return mrb_fixnum_value(downstream->get_response_minor());
const auto &resp = downstream->response();
return mrb_fixnum_value(resp.http_minor);
}
} // namespace
@ -65,8 +67,8 @@ namespace {
mrb_value response_get_status(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
return mrb_fixnum_value(downstream->get_response_http_status());
const auto &resp = downstream->response();
return mrb_fixnum_value(resp.http_status);
}
} // namespace
@ -74,6 +76,7 @@ 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);
@ -83,7 +86,7 @@ mrb_value response_set_status(mrb_state *mrb, mrb_value self) {
"invalid status; it should be [200, 999], inclusive");
}
downstream->set_response_http_status(status);
resp.http_status = status;
return self;
}
@ -93,7 +96,9 @@ namespace {
mrb_value response_get_headers(mrb_state *mrb, mrb_value self) {
auto data = static_cast<MRubyAssocData *>(mrb->ud);
auto downstream = data->downstream;
return create_headers_hash(mrb, downstream->get_response_headers());
const auto &resp = downstream->response();
return create_headers_hash(mrb, resp.fs.headers());
}
} // namespace
@ -101,6 +106,7 @@ 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();
mrb_value key, values;
mrb_get_args(mrb, "oo", &key, &values);
@ -113,7 +119,7 @@ mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
if (repl) {
size_t p = 0;
auto &headers = downstream->get_response_headers();
auto &headers = resp.fs.headers();
for (size_t i = 0; i < headers.size(); ++i) {
auto &hd = headers[i];
if (util::streq(std::begin(hd.name), hd.name.size(), RSTRING_PTR(key),
@ -131,14 +137,12 @@ mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
auto n = mrb_ary_len(mrb, values);
for (int i = 0; i < n; ++i) {
auto value = mrb_ary_entry(values, i);
downstream->add_response_header(
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
resp.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
}
} else if (!mrb_nil_p(values)) {
downstream->add_response_header(
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
resp.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
}
data->response_headers_dirty = true;
@ -163,8 +167,9 @@ 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();
downstream->clear_response_headers();
resp.fs.clear_headers();
return mrb_nil_value();
}
@ -174,6 +179,7 @@ namespace {
mrb_value response_return(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() == Downstream::MSG_COMPLETE) {
@ -187,12 +193,12 @@ mrb_value response_return(mrb_state *mrb, mrb_value self) {
const uint8_t *body = nullptr;
size_t bodylen = 0;
if (downstream->get_response_http_status() == 0) {
downstream->set_response_http_status(200);
if (resp.http_status == 0) {
resp.http_status = 200;
}
if (data->response_headers_dirty) {
downstream->index_response_headers();
resp.fs.index_headers();
data->response_headers_dirty = false;
}
@ -201,21 +207,20 @@ mrb_value response_return(mrb_state *mrb, mrb_value self) {
bodylen = vallen;
}
auto cl = downstream->get_response_header(http2::HD_CONTENT_LENGTH);
auto cl = resp.fs.header(http2::HD_CONTENT_LENGTH);
if (cl) {
cl->value = util::utos(bodylen);
} else {
downstream->add_response_header("content-length", util::utos(bodylen),
http2::HD_CONTENT_LENGTH);
resp.fs.add_header("content-length", util::utos(bodylen),
http2::HD_CONTENT_LENGTH);
}
downstream->set_response_content_length(bodylen);
resp.fs.content_length = bodylen;
auto date = downstream->get_response_header(http2::HD_DATE);
auto date = resp.fs.header(http2::HD_DATE);
if (!date) {
auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now());
downstream->add_response_header("date", lgconf->time_http_str,
http2::HD_DATE);
resp.fs.add_header("date", lgconf->time_http_str, http2::HD_DATE);
}
auto upstream = downstream->get_upstream();

View File

@ -39,8 +39,13 @@ void regencb(struct ev_loop *loop, ev_timer *w, int revents) {
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) {
: 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) {

View File

@ -49,19 +49,23 @@ using namespace nghttp2;
namespace shrpx {
namespace {
constexpr size_t MAX_BUFFER_SIZE = 32_k;
} // namespace
namespace {
ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len,
int flags, void *user_data) {
auto upstream = static_cast<SpdyUpstream *>(user_data);
auto wb = upstream->get_response_buf();
if (wb->wleft() == 0) {
if (wb->rleft() >= MAX_BUFFER_SIZE) {
return SPDYLAY_ERR_WOULDBLOCK;
}
auto nread = wb->write(data, len);
wb->append(data, len);
return nread;
return len;
}
} // namespace
@ -102,9 +106,11 @@ void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
return;
}
upstream->consume(stream_id, downstream->get_request_datalen());
auto &req = downstream->request();
downstream->reset_request_datalen();
upstream->consume(stream_id, req.unconsumed_body_length);
req.unconsumed_body_length = 0;
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream);
@ -144,8 +150,10 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
<< frame->syn_stream.stream_id;
}
auto downstream = upstream->add_pending_downstream(
frame->syn_stream.stream_id, frame->syn_stream.pri);
auto downstream =
upstream->add_pending_downstream(frame->syn_stream.stream_id);
auto &req = downstream->request();
downstream->reset_upstream_rtimer();
@ -169,29 +177,31 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
header_buffer += strlen(nv[i]) + strlen(nv[i + 1]);
}
auto &httpconf = get_config()->http;
// spdy does not define usage of trailer fields, and we ignores
// them.
if (header_buffer > get_config()->header_field_buffer ||
num_headers > get_config()->max_header_fields) {
if (header_buffer > httpconf.request_header_field_buffer ||
num_headers > httpconf.max_request_header_fields) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
return;
}
for (size_t i = 0; nv[i]; i += 2) {
downstream->add_request_header(nv[i], nv[i + 1]);
req.fs.add_header(nv[i], nv[i + 1]);
}
if (downstream->index_request_headers() != 0) {
if (req.fs.index_headers() != 0) {
if (upstream->error_reply(downstream, 400) != 0) {
ULOG(FATAL, upstream) << "error_reply failed";
}
return;
}
auto path = downstream->get_request_header(http2::HD__PATH);
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
auto host = downstream->get_request_header(http2::HD__HOST);
auto method = downstream->get_request_header(http2::HD__METHOD);
auto path = req.fs.header(http2::HD__PATH);
auto scheme = req.fs.header(http2::HD__SCHEME);
auto host = req.fs.header(http2::HD__HOST);
auto method = req.fs.header(http2::HD__METHOD);
if (!method) {
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
@ -214,6 +224,27 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
return;
}
if (std::find_if(std::begin(host->value), std::end(host->value),
[](char c) { return c == '"' || c == '\\'; }) !=
std::end(host->value)) {
if (upstream->error_reply(downstream, 400) != 0) {
ULOG(FATAL, upstream) << "error_reply failed";
}
return;
}
if (scheme) {
for (auto c : scheme->value) {
if (!(util::is_alpha(c) || util::is_digit(c) || c == '+' || c == '-' ||
c == '.')) {
if (upstream->error_reply(downstream, 400) != 0) {
ULOG(FATAL, upstream) << "error_reply failed";
}
return;
}
}
}
// For other than CONNECT method, path must start with "/", except
// for OPTIONS method, which can take "*" as path.
if (!is_connect && path->value[0] != '/' &&
@ -222,24 +253,24 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
return;
}
downstream->set_request_method(method_token);
req.method = method_token;
if (is_connect) {
downstream->set_request_http2_authority(path->value);
req.authority = path->value;
} else {
downstream->set_request_http2_scheme(scheme->value);
downstream->set_request_http2_authority(host->value);
req.scheme = scheme->value;
req.authority = host->value;
if (get_config()->http2_proxy || get_config()->client_proxy) {
downstream->set_request_path(path->value);
req.path = path->value;
} else if (method_token == HTTP_OPTIONS && path->value == "*") {
// Server-wide OPTIONS request. Path is empty.
} else {
downstream->set_request_path(http2::rewrite_clean_path(
std::begin(path->value), std::end(path->value)));
req.path = http2::rewrite_clean_path(std::begin(path->value),
std::end(path->value));
}
}
if (!(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN)) {
downstream->set_request_http2_expect_body(true);
req.http2_expect_body = true;
}
downstream->inspect_http2_request();
@ -261,7 +292,7 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
#endif // HAVE_MRUBY
if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) {
if (!downstream->validate_request_bodylen()) {
if (!downstream->validate_request_recv_body_length()) {
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
return;
}
@ -285,8 +316,7 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
} // namespace
void SpdyUpstream::start_downstream(Downstream *downstream) {
if (downstream_queue_.can_activate(
downstream->get_request_http2_authority())) {
if (downstream_queue_.can_activate(downstream->request().authority)) {
initiate_downstream(downstream);
return;
}
@ -346,31 +376,33 @@ void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
return;
}
auto &http2conf = get_config()->http2;
// If connection-level window control is not enabled (e.g,
// spdy/3), spdylay_session_get_recv_data_length() is always
// returns 0.
if (spdylay_session_get_recv_data_length(session) >
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
1 << get_config()->http2_upstream_connection_window_bits)) {
1 << http2conf.upstream.connection_window_bits)) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream)
<< "Flow control error on connection: "
<< "recv_window_size="
<< spdylay_session_get_recv_data_length(session) << ", window_size="
<< (1 << get_config()->http2_upstream_connection_window_bits);
ULOG(INFO, upstream) << "Flow control error on connection: "
<< "recv_window_size="
<< spdylay_session_get_recv_data_length(session)
<< ", window_size="
<< (1 << http2conf.upstream.connection_window_bits);
}
spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR);
return;
}
if (spdylay_session_get_stream_recv_data_length(session, stream_id) >
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
1 << get_config()->http2_upstream_window_bits)) {
1 << http2conf.upstream.window_bits)) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Flow control error: recv_window_size="
<< spdylay_session_get_stream_recv_data_length(
session, stream_id)
<< ", initial_window_size="
<< (1 << get_config()->http2_upstream_window_bits);
<< (1 << http2conf.upstream.window_bits);
}
upstream->rst_stream(downstream, SPDYLAY_FLOW_CONTROL_ERROR);
return;
@ -385,7 +417,7 @@ void on_data_recv_callback(spdylay_session *session, uint8_t flags,
auto downstream = static_cast<Downstream *>(
spdylay_session_get_stream_user_data(session, stream_id));
if (downstream && (flags & SPDYLAY_DATA_FLAG_FIN)) {
if (!downstream->validate_request_bodylen()) {
if (!downstream->validate_request_recv_body_length()) {
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
return;
}
@ -464,14 +496,16 @@ uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) {
} // namespace
SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
: downstream_queue_(
: wb_(handler->get_worker()->get_mcpool()),
downstream_queue_(
get_config()->http2_proxy
? get_config()->downstream_connections_per_host
: get_config()->downstream_proto == PROTO_HTTP
? get_config()->downstream_connections_per_frontend
? get_config()->conn.downstream.connections_per_host
: get_config()->conn.downstream.proto == PROTO_HTTP
? get_config()->conn.downstream.connections_per_frontend
: 0,
!get_config()->http2_proxy),
handler_(handler), session_(nullptr) {
handler_(handler),
session_(nullptr) {
spdylay_session_callbacks callbacks{};
callbacks.send_callback = send_callback;
callbacks.recv_callback = recv_callback;
@ -494,10 +528,12 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
&max_buffer, sizeof(max_buffer));
assert(rv == 0);
auto &http2conf = get_config()->http2;
if (version >= SPDYLAY_PROTO_SPDY3) {
int val = 1;
flow_control_ = true;
initial_window_size_ = 1 << get_config()->http2_upstream_window_bits;
initial_window_size_ = 1 << http2conf.upstream.window_bits;
rv = spdylay_session_set_option(
session_, SPDYLAY_OPT_NO_AUTO_WINDOW_UPDATE2, &val, sizeof(val));
assert(rv == 0);
@ -508,7 +544,7 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
// TODO Maybe call from outside?
std::array<spdylay_settings_entry, 2> entry;
entry[0].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = get_config()->http2_max_concurrent_streams;
entry[0].value = http2conf.max_concurrent_streams;
entry[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE;
entry[1].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE;
@ -520,15 +556,15 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
assert(rv == 0);
if (version >= SPDYLAY_PROTO_SPDY3_1 &&
get_config()->http2_upstream_connection_window_bits > 16) {
int32_t delta = (1 << get_config()->http2_upstream_connection_window_bits) -
http2conf.upstream.connection_window_bits > 16) {
int32_t delta = (1 << http2conf.upstream.connection_window_bits) -
SPDYLAY_INITIAL_WINDOW_SIZE;
rv = spdylay_submit_window_update(session_, 0, delta);
assert(rv == 0);
}
handler_->reset_upstream_read_timeout(
get_config()->http2_upstream_read_timeout);
get_config()->conn.upstream.timeout.http2_read);
handler_->signal_write();
}
@ -556,8 +592,8 @@ int SpdyUpstream::on_read() {
int SpdyUpstream::on_write() {
int rv = 0;
if (wb_.rleft() == 0) {
wb_.reset();
if (wb_.rleft() >= MAX_BUFFER_SIZE) {
return 0;
}
rv = spdylay_session_send(session_);
@ -756,17 +792,6 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
auto body = downstream->get_response_buf();
assert(body);
auto dconn = downstream->get_downstream_connection();
if (body->rleft() == 0 && dconn &&
downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// Try to read more if buffer is empty. This will help small
// buffer and make priority handling a bit better.
if (upstream->downstream_read(dconn) != 0) {
return SPDYLAY_ERR_CALLBACK_FAILURE;
}
}
auto nread = body->remove(buf, length);
auto body_empty = body->rleft() == 0;
@ -801,7 +826,7 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
}
if (nread > 0) {
downstream->add_response_sent_bodylen(nread);
downstream->response_sent_body_length += nread;
}
return nread;
@ -819,10 +844,11 @@ int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body,
data_prd_ptr = &data_prd;
}
auto status_string =
http2::get_status_string(downstream->get_response_http_status());
const auto &resp = downstream->response();
auto &headers = downstream->get_response_headers();
auto status_string = http2::get_status_string(resp.http_status);
const auto &headers = resp.fs.headers();
auto nva = std::vector<const char *>();
// 3 for :status, :version and server
@ -848,9 +874,9 @@ int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body,
nva.push_back(kv.value.c_str());
}
if (!downstream->get_response_header(http2::HD_SERVER)) {
if (!resp.fs.header(http2::HD_SERVER)) {
nva.push_back("server");
nva.push_back(get_config()->server_name);
nva.push_back(get_config()->http.server_name.c_str());
}
nva.push_back(nullptr);
@ -875,10 +901,12 @@ int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body,
int SpdyUpstream::error_reply(Downstream *downstream,
unsigned int status_code) {
int rv;
auto &resp = downstream->response();
auto html = http::create_error_html(status_code);
downstream->set_response_http_status(status_code);
resp.http_status = status_code;
auto body = downstream->get_response_buf();
body->append(html.c_str(), html.size());
body->append(html);
downstream->set_response_state(Downstream::MSG_COMPLETE);
spdylay_data_provider data_prd;
@ -892,7 +920,7 @@ int SpdyUpstream::error_reply(Downstream *downstream,
std::string status_string = http2::get_status_string(status_code);
const char *nv[] = {":status", status_string.c_str(), ":version", "http/1.1",
"content-type", "text/html; charset=UTF-8", "server",
get_config()->server_name, "content-length",
get_config()->http.server_name.c_str(), "content-length",
content_length.c_str(), "date",
lgconf->time_http_str.c_str(), nullptr};
@ -907,10 +935,9 @@ int SpdyUpstream::error_reply(Downstream *downstream,
return 0;
}
Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id,
int32_t priority) {
auto downstream = make_unique<Downstream>(this, handler_->get_mcpool(),
stream_id, priority);
Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id) {
auto downstream =
make_unique<Downstream>(this, handler_->get_mcpool(), stream_id);
spdylay_session_set_stream_user_data(session_, stream_id, downstream.get());
auto res = downstream.get();
@ -937,15 +964,19 @@ void SpdyUpstream::remove_downstream(Downstream *downstream) {
// WARNING: Never call directly or indirectly spdylay_session_send or
// spdylay_session_recv. These calls may delete downstream.
int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
auto &resp = downstream->response();
if (downstream->get_non_final_response()) {
// SPDY does not support non-final response. We could send it
// with HEADERS and final response in SYN_REPLY, but it is not
// official way.
downstream->clear_response_headers();
resp.fs.clear_headers();
return 0;
}
const auto &req = downstream->request();
#ifdef HAVE_MRUBY
auto worker = handler_->get_worker();
auto mruby_ctx = worker->get_mruby_context();
@ -967,25 +998,26 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
DLOG(INFO, downstream) << "HTTP response header completed";
}
auto &httpconf = get_config()->http;
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!get_config()->no_location_rewrite) {
downstream->rewrite_location_response_header(
downstream->get_request_http2_scheme());
!httpconf.no_location_rewrite) {
downstream->rewrite_location_response_header(req.scheme);
}
size_t nheader = downstream->get_response_headers().size();
// 8 means server, :status, :version and possible via header field.
auto nv = make_unique<const char *[]>(
nheader * 2 + 8 + get_config()->add_response_headers.size() * 2 + 1);
auto nv =
make_unique<const char *[]>(resp.fs.headers().size() * 2 + 8 +
httpconf.add_response_headers.size() * 2 + 1);
size_t hdidx = 0;
std::string via_value;
std::string status_string =
http2::get_status_string(downstream->get_response_http_status());
auto status_string = http2::get_status_string(resp.http_status);
nv[hdidx++] = ":status";
nv[hdidx++] = status_string.c_str();
nv[hdidx++] = ":version";
nv[hdidx++] = "HTTP/1.1";
for (auto &hd : downstream->get_response_headers()) {
for (auto &hd : resp.fs.headers()) {
if (hd.name.empty() || hd.name.c_str()[0] == ':') {
continue;
}
@ -1005,17 +1037,17 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
nv[hdidx++] = "server";
nv[hdidx++] = get_config()->server_name;
nv[hdidx++] = httpconf.server_name.c_str();
} else {
auto server = downstream->get_response_header(http2::HD_SERVER);
auto server = resp.fs.header(http2::HD_SERVER);
if (server) {
nv[hdidx++] = "server";
nv[hdidx++] = server->value.c_str();
}
}
auto via = downstream->get_response_header(http2::HD_VIA);
if (get_config()->no_via) {
auto via = resp.fs.header(http2::HD_VIA);
if (httpconf.no_via) {
if (via) {
nv[hdidx++] = "via";
nv[hdidx++] = via->value.c_str();
@ -1025,13 +1057,13 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
via_value = via->value;
via_value += ", ";
}
via_value += http::create_via_header_value(
downstream->get_response_major(), downstream->get_response_minor());
via_value +=
http::create_via_header_value(resp.http_major, resp.http_minor);
nv[hdidx++] = "via";
nv[hdidx++] = via_value.c_str();
}
for (auto &p : get_config()->add_response_headers) {
for (auto &p : httpconf.add_response_headers) {
nv[hdidx++] = p.first.c_str();
nv[hdidx++] = p.second.c_str();
}
@ -1084,9 +1116,11 @@ int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) {
DLOG(INFO, downstream) << "HTTP response completed";
}
if (!downstream->validate_response_bodylen()) {
auto &resp = downstream->response();
if (!downstream->validate_response_recv_body_length()) {
rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
downstream->set_response_connection_close(true);
resp.connection_close = true;
return 0;
}
@ -1103,13 +1137,13 @@ void SpdyUpstream::pause_read(IOCtrlReason reason) {}
int SpdyUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
size_t consumed) {
if (get_flow_control()) {
assert(downstream->get_request_datalen() >= consumed);
if (consume(downstream->get_stream_id(), consumed) != 0) {
return -1;
}
downstream->dec_request_datalen(consumed);
auto &req = downstream->request();
req.consume(consumed);
}
handler_->signal_write();
@ -1220,17 +1254,14 @@ int SpdyUpstream::response_riovec(struct iovec *iov, int iovcnt) const {
return 0;
}
iov->iov_base = wb_.pos;
iov->iov_len = wb_.rleft();
return 1;
return wb_.riovec(iov, iovcnt);
}
void SpdyUpstream::response_drain(size_t n) { wb_.drain(n); }
bool SpdyUpstream::response_empty() const { return wb_.rleft() == 0; }
SpdyUpstream::WriteBuffer *SpdyUpstream::get_response_buf() { return &wb_; }
DefaultMemchunks *SpdyUpstream::get_response_buf() { return &wb_; }
Downstream *
SpdyUpstream::on_downstream_push_promise(Downstream *downstream,

View File

@ -56,7 +56,7 @@ public:
virtual int downstream_write(DownstreamConnection *dconn);
virtual int downstream_eof(DownstreamConnection *dconn);
virtual int downstream_error(DownstreamConnection *dconn, int events);
Downstream *add_pending_downstream(int32_t stream_id, int32_t priority);
Downstream *add_pending_downstream(int32_t stream_id);
void remove_downstream(Downstream *downstream);
int rst_stream(Downstream *downstream, int status_code);
@ -97,12 +97,10 @@ public:
void start_downstream(Downstream *downstream);
void initiate_downstream(Downstream *downstream);
using WriteBuffer = Buffer<32_k>;
WriteBuffer *get_response_buf();
DefaultMemchunks *get_response_buf();
private:
WriteBuffer wb_;
DefaultMemchunks wb_;
DownstreamQueue downstream_queue_;
ClientHandler *handler_;
spdylay_session *session_;

View File

@ -70,7 +70,7 @@ namespace ssl {
namespace {
int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
void *arg) {
auto &prefs = get_config()->alpn_prefs;
auto &prefs = get_config()->tls.alpn_prefs;
*data = prefs.data();
*len = prefs.size();
return SSL_TLSEXT_ERR_OK;
@ -124,13 +124,13 @@ set_alpn_prefs(const std::vector<std::string> &protos) {
namespace {
int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) {
auto config = static_cast<Config *>(user_data);
int len = (int)strlen(config->private_key_passwd.get());
int len = (int)strlen(config->tls.private_key_passwd.get());
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->private_key_passwd.get(), len + 1);
memcpy(buf, config->tls.private_key_passwd.get(), len + 1);
return len;
}
} // namespace
@ -346,7 +346,7 @@ int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
std::copy(std::begin(key.data.name), std::end(key.data.name), key_name);
EVP_EncryptInit_ex(ctx, get_config()->tls_ticket_key_cipher, nullptr,
EVP_EncryptInit_ex(ctx, get_config()->tls.ticket.cipher, nullptr,
key.data.enc_key.data(), iv);
HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac,
nullptr);
@ -411,7 +411,7 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
// We assume that get_config()->npn_list contains ALPN protocol
// identifier sorted by preference order. So we just break when we
// found the first overlap.
for (const auto &target_proto_id : get_config()->npn_list) {
for (const auto &target_proto_id : get_config()->tls.npn_list) {
for (auto p = in, end = in + inlen; p < end;) {
auto proto_id = p + 1;
auto proto_len = *p;
@ -477,22 +477,24 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE |
SSL_OP_SINGLE_DH_USE | SSL_OP_CIPHER_SERVER_PREFERENCE;
SSL_CTX_set_options(ssl_ctx, ssl_opts | get_config()->tls_proto_mask);
auto &tlsconf = get_config()->tls;
SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
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 (get_config()->session_cache_memcached_host) {
if (tlsconf.session_cache.memcached.host) {
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, get_config()->tls_session_timeout.count());
SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count());
const char *ciphers;
if (get_config()->ciphers) {
ciphers = get_config()->ciphers.get();
if (tlsconf.ciphers) {
ciphers = tlsconf.ciphers.get();
} else {
ciphers = nghttp2::ssl::DEFAULT_CIPHER_LIST;
}
@ -525,9 +527,9 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
#endif // OPENSSL_NO_EC
if (get_config()->dh_param_file) {
if (tlsconf.dh_param_file) {
// Read DH parameters from file
auto bio = BIO_new_file(get_config()->dh_param_file.get(), "r");
auto bio = BIO_new_file(tlsconf.dh_param_file.get(), "r");
if (bio == nullptr) {
LOG(FATAL) << "BIO_new_file() failed: "
<< ERR_error_string(ERR_get_error(), nullptr);
@ -546,7 +548,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
if (get_config()->private_key_passwd) {
if (tlsconf.private_key_passwd) {
SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)get_config());
}
@ -576,14 +578,13 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
if (get_config()->verify_client) {
if (get_config()->verify_client_cacert) {
if (tlsconf.client_verify.enabled) {
if (tlsconf.client_verify.cacert) {
if (SSL_CTX_load_verify_locations(
ssl_ctx, get_config()->verify_client_cacert.get(), nullptr) !=
1) {
ssl_ctx, tlsconf.client_verify.cacert.get(), nullptr) != 1) {
LOG(FATAL) << "Could not load trusted ca certificates from "
<< get_config()->verify_client_cacert.get() << ": "
<< tlsconf.client_verify.cacert.get() << ": "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
@ -591,11 +592,10 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
// 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(get_config()->verify_client_cacert.get());
auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.get());
if (!list) {
LOG(FATAL) << "Could not load ca certificates from "
<< get_config()->verify_client_cacert.get() << ": "
<< tlsconf.client_verify.cacert.get() << ": "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
@ -628,9 +628,9 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
}
namespace {
int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen,
void *arg) {
int select_h2_next_proto_cb(SSL *ssl, unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
inlen)) {
return SSL_TLSEXT_ERR_NOACK;
@ -640,6 +640,24 @@ int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
}
} // namespace
namespace {
int select_h1_next_proto_cb(SSL *ssl, unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto end = in + inlen;
for (; in < end;) {
if (util::streq_l(NGHTTP2_H1_1_ALPN, in, in[0] + 1)) {
*out = const_cast<unsigned char *>(in) + 1;
*outlen = in[0];
return SSL_TLSEXT_ERR_OK;
}
in += in[0] + 1;
}
return SSL_TLSEXT_ERR_NOACK;
}
} // namespace
SSL_CTX *create_ssl_client_context(
#ifdef HAVE_NEVERBLEED
neverbleed_t *nb
@ -656,11 +674,13 @@ SSL_CTX *create_ssl_client_context(
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
SSL_CTX_set_options(ssl_ctx, ssl_opts | get_config()->tls_proto_mask);
auto &tlsconf = get_config()->tls;
SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
const char *ciphers;
if (get_config()->ciphers) {
ciphers = get_config()->ciphers.get();
if (tlsconf.ciphers) {
ciphers = tlsconf.ciphers.get();
} else {
ciphers = nghttp2::ssl::DEFAULT_CIPHER_LIST;
}
@ -678,57 +698,71 @@ SSL_CTX *create_ssl_client_context(
<< ERR_error_string(ERR_get_error(), nullptr);
}
if (get_config()->cacert) {
if (SSL_CTX_load_verify_locations(ssl_ctx, get_config()->cacert.get(),
nullptr) != 1) {
if (tlsconf.cacert) {
if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.get(), nullptr) !=
1) {
LOG(FATAL) << "Could not load trusted ca certificates from "
<< get_config()->cacert.get() << ": "
<< tlsconf.cacert.get() << ": "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
}
if (get_config()->client_private_key_file) {
if (tlsconf.client.private_key_file) {
#ifndef HAVE_NEVERBLEED
if (SSL_CTX_use_PrivateKey_file(ssl_ctx,
get_config()->client_private_key_file.get(),
tlsconf.client.private_key_file.get(),
SSL_FILETYPE_PEM) != 1) {
LOG(FATAL) << "Could not load client private key from "
<< get_config()->client_private_key_file.get() << ": "
<< tlsconf.client.private_key_file.get() << ": "
<< 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, get_config()->client_private_key_file.get(),
errbuf.data()) != 1) {
if (neverbleed_load_private_key_file(nb, ssl_ctx,
tlsconf.client.private_key_file.get(),
errbuf.data()) != 1) {
LOG(FATAL) << "neverbleed_load_private_key_file failed: "
<< errbuf.data();
DIE();
}
#endif // HAVE_NEVERBLEED
}
if (get_config()->client_cert_file) {
if (tlsconf.client.cert_file) {
if (SSL_CTX_use_certificate_chain_file(
ssl_ctx, get_config()->client_cert_file.get()) != 1) {
ssl_ctx, tlsconf.client.cert_file.get()) != 1) {
LOG(FATAL) << "Could not load client certificate from "
<< get_config()->client_cert_file.get() << ": "
<< tlsconf.client.cert_file.get() << ": "
<< ERR_error_string(ERR_get_error(), nullptr);
DIE();
}
}
// NPN selection callback
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, nullptr);
auto &downstreamconf = get_config()->conn.downstream;
if (downstreamconf.proto == PROTO_HTTP2) {
// NPN selection callback
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h2_next_proto_cb, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// ALPN advertisement; We only advertise HTTP/2
auto proto_list = util::get_default_alpn();
// ALPN advertisement; We only advertise HTTP/2
auto proto_list = util::get_default_alpn();
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
} else {
// NPN selection callback
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h1_next_proto_cb, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CTX_set_alpn_protos(
ssl_ctx, reinterpret_cast<const unsigned char *>(NGHTTP2_H1_1_ALPN),
str_size(NGHTTP2_H1_1_ALPN));
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
}
return ssl_ctx;
}
@ -745,19 +779,23 @@ SSL *create_ssl(SSL_CTX *ssl_ctx) {
}
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
int addrlen) {
int addrlen, const UpstreamAddr *faddr) {
char host[NI_MAXHOST];
char service[NI_MAXSERV];
int rv;
rv = getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service),
NI_NUMERICHOST | NI_NUMERICSERV);
if (rv != 0) {
LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
return nullptr;
}
if (addr->sa_family == AF_UNIX) {
std::copy_n("localhost", sizeof("localhost"), host);
service[0] = '\0';
} else {
rv = getnameinfo(addr, addrlen, host, sizeof(host), service,
sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV);
if (rv != 0) {
LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
return nullptr;
}
if (addr->sa_family != AF_UNIX) {
rv = util::make_socket_nodelay(fd);
if (rv == -1) {
LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
@ -777,30 +815,39 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
}
}
return new ClientHandler(worker, fd, ssl, host, service);
return new ClientHandler(worker, fd, ssl, host, service, addr->sa_family,
faddr);
}
namespace {
bool tls_hostname_match(const char *pattern, const char *hostname) {
const char *ptWildcard = strchr(pattern, '*');
if (ptWildcard == nullptr) {
return util::strieq(pattern, hostname);
bool tls_hostname_match(const char *pattern, size_t plen, const char *hostname,
size_t hlen) {
auto pend = pattern + plen;
auto ptWildcard = std::find(pattern, pend, '*');
if (ptWildcard == pend) {
return util::strieq(pattern, plen, hostname, hlen);
}
const char *ptLeftLabelEnd = strchr(pattern, '.');
bool wildcardEnabled = true;
auto ptLeftLabelEnd = std::find(pattern, pend, '.');
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 == 0 || strchr(ptLeftLabelEnd + 1, '.') == 0 ||
ptLeftLabelEnd < ptWildcard || util::istarts_with(pattern, "xn--")) {
if (ptLeftLabelEnd == pend ||
std::find(ptLeftLabelEnd + 1, pend, '.') == pend ||
ptLeftLabelEnd < ptWildcard ||
util::istarts_with(pattern, plen, "xn--")) {
wildcardEnabled = false;
}
if (!wildcardEnabled) {
return util::strieq(pattern, hostname);
return util::strieq(pattern, plen, hostname, hlen);
}
const char *hnLeftLabelEnd = strchr(hostname, '.');
if (hnLeftLabelEnd == 0 || !util::strieq(ptLeftLabelEnd, hnLeftLabelEnd)) {
auto hend = hostname + hlen;
auto hnLeftLabelEnd = std::find(hostname, hend, '.');
if (hnLeftLabelEnd == hend ||
!util::strieq(ptLeftLabelEnd, pend, hnLeftLabelEnd, hend)) {
return false;
}
// Perform wildcard match. Here '*' must match at least one
@ -812,107 +859,143 @@ bool tls_hostname_match(const char *pattern, const char *hostname) {
util::iends_with(hostname, hnLeftLabelEnd, ptWildcard + 1,
ptLeftLabelEnd);
}
} // namespace
namespace {
int verify_hostname(const char *hostname, const Address *addr,
const std::vector<std::string> &dns_names,
const std::vector<std::string> &ip_addrs,
const std::string &common_name) {
if (util::numeric_host(hostname)) {
if (ip_addrs.empty()) {
return util::strieq(common_name.c_str(), hostname) ? 0 : -1;
}
const void *saddr;
switch (addr->su.storage.ss_family) {
case AF_INET:
saddr = &addr->su.in.sin_addr;
ssize_t get_common_name(unsigned char **out_ptr, X509 *cert) {
auto subjectname = X509_get_subject_name(cert);
if (!subjectname) {
LOG(WARN) << "Could not get X509 name object from the certificate.";
return -1;
}
int lastpos = -1;
for (;;) {
lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos);
if (lastpos == -1) {
break;
case AF_INET6:
saddr = &addr->su.in6.sin6_addr;
break;
default:
return -1;
}
for (size_t i = 0; i < ip_addrs.size(); ++i) {
if (addr->len == ip_addrs[i].size() &&
memcmp(saddr, ip_addrs[i].c_str(), addr->len) == 0) {
return 0;
}
auto entry = X509_NAME_get_entry(subjectname, lastpos);
auto outlen = ASN1_STRING_to_UTF8(out_ptr, X509_NAME_ENTRY_get_data(entry));
if (outlen < 0) {
continue;
}
} else {
if (dns_names.empty()) {
return tls_hostname_match(common_name.c_str(), hostname) ? 0 : -1;
}
for (size_t i = 0; i < dns_names.size(); ++i) {
if (tls_hostname_match(dns_names[i].c_str(), hostname)) {
return 0;
}
if (std::find(*out_ptr, *out_ptr + outlen, '\0') != *out_ptr + outlen) {
// Embedded NULL is not permitted.
continue;
}
return outlen;
}
return -1;
}
} // namespace
void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
std::vector<std::string> &ip_addrs,
std::string &common_name) {
GENERAL_NAMES *altnames = static_cast<GENERAL_NAMES *>(
namespace {
int verify_numeric_hostname(X509 *cert, const char *hostname, size_t hlen,
const Address *addr) {
const void *saddr;
switch (addr->su.storage.ss_family) {
case AF_INET:
saddr = &addr->su.in.sin_addr;
break;
case AF_INET6:
saddr = &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);
for (size_t i = 0; i < n; ++i) {
const GENERAL_NAME *altname = sk_GENERAL_NAME_value(altnames, i);
if (altname->type == GEN_DNS) {
const char *name;
name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
if (!name) {
continue;
}
size_t len = ASN1_STRING_length(altname->d.ia5);
if (std::find(name, name + len, '\0') != name + len) {
// Embedded NULL is not permitted.
continue;
}
dns_names.push_back(std::string(name, len));
} else if (altname->type == GEN_IPADD) {
const unsigned char *ip_addr = altname->d.iPAddress->data;
if (!ip_addr) {
continue;
}
size_t len = altname->d.iPAddress->length;
ip_addrs.push_back(
std::string(reinterpret_cast<const char *>(ip_addr), len));
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;
if (addr->len == ip_addrlen && memcmp(saddr, ip_addr, ip_addrlen) == 0) {
return 0;
}
}
}
X509_NAME *subjectname = X509_get_subject_name(cert);
if (!subjectname) {
LOG(WARN) << "Could not get X509 name object from the certificate.";
return;
unsigned char *cn;
auto cnlen = get_common_name(&cn, cert);
if (cnlen == -1) {
return -1;
}
int lastpos = -1;
while (1) {
lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos);
if (lastpos == -1) {
break;
}
X509_NAME_ENTRY *entry = X509_NAME_get_entry(subjectname, lastpos);
unsigned char *out;
int outlen = ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(entry));
if (outlen < 0) {
continue;
}
if (std::find(out, out + outlen, '\0') != out + outlen) {
// Embedded NULL is not permitted.
continue;
}
common_name.assign(&out[0], &out[outlen]);
OPENSSL_free(out);
break;
// cn is not NULL terminated
auto rv = util::streq(hostname, hlen, cn, cnlen);
OPENSSL_free(cn);
if (rv) {
return 0;
}
return -1;
}
} // namespace
namespace {
int verify_hostname(X509 *cert, const char *hostname, size_t hlen,
const Address *addr) {
if (util::numeric_host(hostname)) {
return verify_numeric_hostname(cert, hostname, hlen, addr);
}
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);
for (size_t i = 0; i < n; ++i) {
auto altname = sk_GENERAL_NAME_value(altnames, i);
if (altname->type != GEN_DNS) {
continue;
}
auto name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
if (!name) {
continue;
}
auto len = ASN1_STRING_length(altname->d.ia5);
if (std::find(name, name + len, '\0') != name + len) {
// Embedded NULL is not permitted.
continue;
}
if (tls_hostname_match(name, len, hostname, hlen)) {
return 0;
}
}
}
unsigned char *cn;
auto cnlen = get_common_name(&cn, cert);
if (cnlen == -1) {
return -1;
}
auto rv = util::strieq(hostname, hlen, cn, cnlen);
OPENSSL_free(cn);
if (rv) {
return 0;
}
return -1;
}
} // namespace
int check_cert(SSL *ssl, const DownstreamAddr *addr) {
auto cert = SSL_get_peer_certificate(ssl);
@ -921,21 +1004,19 @@ int check_cert(SSL *ssl, const DownstreamAddr *addr) {
return -1;
}
auto cert_deleter = defer(X509_free, cert);
long verify_res = SSL_get_verify_result(ssl);
auto verify_res = SSL_get_verify_result(ssl);
if (verify_res != X509_V_OK) {
LOG(ERROR) << "Certificate verification failed: "
<< X509_verify_cert_error_string(verify_res);
return -1;
}
std::string common_name;
std::vector<std::string> dns_names;
std::vector<std::string> ip_addrs;
get_altnames(cert, dns_names, ip_addrs, common_name);
auto hostname = get_config()->backend_tls_sni_name
? get_config()->backend_tls_sni_name.get()
: addr->host.get();
if (verify_hostname(hostname, &addr->addr, dns_names, ip_addrs,
common_name) != 0) {
auto &backend_sni_name = get_config()->tls.backend_sni_name;
auto hostname = !backend_sni_name.empty() ? StringRef(backend_sni_name)
: StringRef(addr->host);
if (verify_hostname(cert, hostname.c_str(), hostname.size(), &addr->addr) !=
0) {
LOG(ERROR) << "Certificate verification failed: hostname does not match";
return -1;
}
@ -969,7 +1050,7 @@ void cert_lookup_tree_add_cert(CertNode *node, SSL_CTX *ssl_ctx, char *hostname,
// some restrictions for wildcard hostname. We just ignore
// these rules here but do the proper check when we do the
// match.
node->wildcard_certs.emplace_back(hostname, ssl_ctx);
node->wildcard_certs.push_back({ssl_ctx, hostname, len});
return;
}
@ -986,7 +1067,7 @@ void cert_lookup_tree_add_cert(CertNode *node, SSL_CTX *ssl_ctx, char *hostname,
new_node->ssl_ctx = ssl_ctx;
} else {
new_node->ssl_ctx = nullptr;
new_node->wildcard_certs.emplace_back(hostname, ssl_ctx);
new_node->wildcard_certs.push_back({ssl_ctx, hostname, len});
}
node->next.push_back(std::move(new_node));
return;
@ -1073,9 +1154,11 @@ SSL_CTX *cert_lookup_tree_lookup(CertNode *node, const char *hostname,
// one character.
return nullptr;
}
for (const auto &wildcert : node->wildcard_certs) {
if (tls_hostname_match(wildcert.first, hostname)) {
return wildcert.second;
if (tls_hostname_match(wildcert.hostname, wildcert.hostnamelen, hostname,
len)) {
return wildcert.ssl_ctx;
}
}
auto c = util::lowcase(hostname[j]);
@ -1111,14 +1194,43 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
return -1;
}
auto cert_deleter = defer(X509_free, cert);
std::string common_name;
std::vector<std::string> dns_names;
std::vector<std::string> ip_addrs;
get_altnames(cert, dns_names, ip_addrs, common_name);
for (auto &dns_name : dns_names) {
lt->add_cert(ssl_ctx, dns_name.c_str(), dns_name.size());
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);
for (size_t i = 0; i < n; ++i) {
auto altname = sk_GENERAL_NAME_value(altnames, i);
if (altname->type != GEN_DNS) {
continue;
}
auto name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
if (!name) {
continue;
}
auto len = ASN1_STRING_length(altname->d.ia5);
if (std::find(name, name + len, '\0') != name + len) {
// Embedded NULL is not permitted.
continue;
}
lt->add_cert(ssl_ctx, name, len);
}
}
lt->add_cert(ssl_ctx, common_name.c_str(), common_name.size());
unsigned char *cn;
auto cnlen = get_common_name(&cn, cert);
if (cnlen == -1) {
return 0;
}
lt->add_cert(ssl_ctx, reinterpret_cast<char *>(cn), cnlen);
OPENSSL_free(cn);
return 0;
}
@ -1139,12 +1251,14 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
neverbleed_t *nb
#endif // HAVE_NEVERBLEED
) {
if (get_config()->upstream_no_tls) {
if (get_config()->conn.upstream.no_tls) {
return nullptr;
}
auto ssl_ctx = ssl::create_ssl_context(get_config()->private_key_file.get(),
get_config()->cert_file.get()
auto &tlsconf = get_config()->tls;
auto ssl_ctx = ssl::create_ssl_context(tlsconf.private_key_file.get(),
tlsconf.cert_file.get()
#ifdef HAVE_NEVERBLEED
,
nb
@ -1153,7 +1267,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
all_ssl_ctx.push_back(ssl_ctx);
if (get_config()->subcerts.empty()) {
if (tlsconf.subcerts.empty()) {
return ssl_ctx;
}
@ -1163,7 +1277,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
return ssl_ctx;
}
for (auto &keycert : get_config()->subcerts) {
for (auto &keycert : tlsconf.subcerts) {
auto ssl_ctx =
ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str()
#ifdef HAVE_NEVERBLEED
@ -1179,8 +1293,8 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
}
}
if (ssl::cert_lookup_tree_add_cert_from_file(
cert_tree, ssl_ctx, get_config()->cert_file.get()) == -1) {
if (ssl::cert_lookup_tree_add_cert_from_file(cert_tree, ssl_ctx,
tlsconf.cert_file.get()) == -1) {
LOG(FATAL) << "Failed to add default certificate.";
DIE();
}
@ -1188,13 +1302,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
return ssl_ctx;
}
bool downstream_tls_enabled() {
if (get_config()->client_mode) {
return !get_config()->downstream_no_tls;
}
return get_config()->http2_bridge && !get_config()->downstream_no_tls;
}
bool downstream_tls_enabled() { return !get_config()->conn.downstream.no_tls; }
SSL_CTX *setup_client_ssl_context(
#ifdef HAVE_NEVERBLEED
@ -1213,7 +1321,8 @@ SSL_CTX *setup_client_ssl_context(
}
CertLookupTree *create_cert_lookup_tree() {
if (get_config()->upstream_no_tls || get_config()->subcerts.empty()) {
if (get_config()->conn.upstream.no_tls ||
get_config()->tls.subcerts.empty()) {
return nullptr;
}
return new ssl::CertLookupTree();

View File

@ -45,6 +45,7 @@ class ClientHandler;
class Worker;
class DownstreamConnectionPool;
struct DownstreamAddr;
struct UpstreamAddr;
namespace ssl {
@ -76,7 +77,7 @@ SSL_CTX *create_ssl_client_context(
);
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
int addrlen);
int addrlen, const UpstreamAddr *faddr);
// Check peer's certificate against first downstream address in
// Config::downstream_addrs. We only consider first downstream since
@ -108,10 +109,16 @@ void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
// them. If there is a match, its SSL_CTX is returned. If none
// matches, query is continued to the next character.
struct WildcardCert {
SSL_CTX *ssl_ctx;
char *hostname;
size_t hostnamelen;
};
struct CertNode {
// list of wildcard domain name and its SSL_CTX pair, the wildcard
// '*' appears in this position.
std::vector<std::pair<char *, SSL_CTX *>> wildcard_certs;
std::vector<WildcardCert> wildcard_certs;
// Next CertNode index of CertLookupTree::nodes
std::vector<std::unique_ptr<CertNode>> next;
// SSL_CTX for exact match
@ -198,6 +205,13 @@ SSL *create_ssl(SSL_CTX *ssl_ctx);
// Returns true if SSL/TLS is enabled on downstream
bool downstream_tls_enabled();
// Performs TLS hostname match. |pattern| of length |plen| 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 char *pattern, size_t plen, const char *hostname,
size_t hlen);
} // namespace ssl
} // namespace shrpx

View File

@ -115,4 +115,37 @@ void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void) {
SSL_CTX_free(ssl_ctx);
}
template <size_t N, size_t M>
bool tls_hostname_match_wrapper(const char(&pattern)[N],
const char(&hostname)[M]) {
return ssl::tls_hostname_match(pattern, N, hostname, M);
}
void test_shrpx_ssl_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"));
}
} // namespace shrpx

View File

@ -33,6 +33,7 @@ namespace shrpx {
void test_shrpx_ssl_create_lookup_tree(void);
void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void);
void test_shrpx_ssl_tls_hostname_match(void);
} // namespace shrpx

Some files were not shown because too many files have changed in this diff Show More