commit
7398e57174
|
@ -28,6 +28,7 @@ addons:
|
||||||
- libevent-dev
|
- libevent-dev
|
||||||
- libjansson-dev
|
- libjansson-dev
|
||||||
- libjemalloc-dev
|
- libjemalloc-dev
|
||||||
|
- libc-ares-dev
|
||||||
- cmake
|
- cmake
|
||||||
- cmake-data
|
- cmake-data
|
||||||
before_install:
|
before_install:
|
||||||
|
|
|
@ -59,6 +59,7 @@ find_package(PythonInterp)
|
||||||
# Auto-detection of features that can be toggled
|
# Auto-detection of features that can be toggled
|
||||||
find_package(OpenSSL 1.0.1)
|
find_package(OpenSSL 1.0.1)
|
||||||
find_package(Libev 4.11)
|
find_package(Libev 4.11)
|
||||||
|
find_package(Libcares 1.7.5)
|
||||||
find_package(ZLIB 1.2.3)
|
find_package(ZLIB 1.2.3)
|
||||||
if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND)
|
if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND)
|
||||||
set(ENABLE_APP_DEFAULT ON)
|
set(ENABLE_APP_DEFAULT ON)
|
||||||
|
@ -207,6 +208,14 @@ if(LIBEVENT_FOUND)
|
||||||
# Must both link the core and openssl libraries.
|
# Must both link the core and openssl libraries.
|
||||||
set(LIBEVENT_OPENSSL_LIBRARIES ${LIBEVENT_LIBRARIES})
|
set(LIBEVENT_OPENSSL_LIBRARIES ${LIBEVENT_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
# libc-ares (for src)
|
||||||
|
set(HAVE_LIBCARES ${LIBCARES_FOUND})
|
||||||
|
if(LIBCARES_FOUND)
|
||||||
|
set(LIBCARES_INCLUDE_DIRS ${LIBCARES_INCLUDE_DIR})
|
||||||
|
else()
|
||||||
|
set(LIBCARES_INCLUDE_DIRS "")
|
||||||
|
set(LIBCARES_LIBRARIES "")
|
||||||
|
endif()
|
||||||
# jansson (for src/nghttp, src/deflatehd and src/inflatehd)
|
# jansson (for src/nghttp, src/deflatehd and src/inflatehd)
|
||||||
set(HAVE_JANSSON ${JANSSON_FOUND})
|
set(HAVE_JANSSON ${JANSSON_FOUND})
|
||||||
# libxml2 (for src/nghttp)
|
# libxml2 (for src/nghttp)
|
||||||
|
@ -499,6 +508,7 @@ message(STATUS "summary of build options:
|
||||||
OpenSSL: ${HAVE_OPENSSL} (LIBS='${OPENSSL_LIBRARIES}')
|
OpenSSL: ${HAVE_OPENSSL} (LIBS='${OPENSSL_LIBRARIES}')
|
||||||
Libxml2: ${HAVE_LIBXML2} (LIBS='${LIBXML2_LIBRARIES}')
|
Libxml2: ${HAVE_LIBXML2} (LIBS='${LIBXML2_LIBRARIES}')
|
||||||
Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}')
|
Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}')
|
||||||
|
Libc-ares: ${HAVE_LIBCARES} (LIBS='${LIBCARES_LIBRARIES}')
|
||||||
Libevent(SSL): ${HAVE_LIBEVENT_OPENSSL} (LIBS='${LIBEVENT_OPENSSL_LIBRARIES}')
|
Libevent(SSL): ${HAVE_LIBEVENT_OPENSSL} (LIBS='${LIBEVENT_OPENSSL_LIBRARIES}')
|
||||||
Spdylay: ${HAVE_SPDYLAY} (LIBS='${SPDYLAY_LIBRARIES}')
|
Spdylay: ${HAVE_SPDYLAY} (LIBS='${SPDYLAY_LIBRARIES}')
|
||||||
Jansson: ${HAVE_JANSSON} (LIBS='${JANSSON_LIBRARIES}')
|
Jansson: ${HAVE_JANSSON} (LIBS='${JANSSON_LIBRARIES}')
|
||||||
|
|
|
@ -45,7 +45,8 @@ EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-make \
|
||||||
cmake/Version.cmake \
|
cmake/Version.cmake \
|
||||||
cmake/FindCython.cmake \
|
cmake/FindCython.cmake \
|
||||||
cmake/FindLibevent.cmake \
|
cmake/FindLibevent.cmake \
|
||||||
cmake/FindJansson.cmake
|
cmake/FindJansson.cmake \
|
||||||
|
cmake/FindLibcares.cmake
|
||||||
|
|
||||||
.PHONY: clang-format
|
.PHONY: clang-format
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ are required:
|
||||||
* OpenSSL >= 1.0.1
|
* OpenSSL >= 1.0.1
|
||||||
* libev >= 4.11
|
* libev >= 4.11
|
||||||
* zlib >= 1.2.3
|
* zlib >= 1.2.3
|
||||||
|
* libc-ares >= 1.7.5
|
||||||
|
|
||||||
ALPN support requires OpenSSL >= 1.0.2 (released 22 January 2015).
|
ALPN support requires OpenSSL >= 1.0.2 (released 22 January 2015).
|
||||||
LibreSSL >= 2.2.0 can be used instead of OpenSSL, but OpenSSL has more
|
LibreSSL >= 2.2.0 can be used instead of OpenSSL, but OpenSSL has more
|
||||||
|
@ -115,7 +116,7 @@ If you are using Ubuntu 14.04 LTS (trusty) or Debian 7.0 (wheezy) and above run
|
||||||
|
|
||||||
sudo apt-get install g++ make binutils autoconf automake autotools-dev libtool pkg-config \
|
sudo apt-get install g++ make binutils autoconf automake autotools-dev libtool pkg-config \
|
||||||
zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev libevent-dev libjansson-dev \
|
zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev libevent-dev libjansson-dev \
|
||||||
libjemalloc-dev cython python3-dev python-setuptools
|
libc-ares-dev libjemalloc-dev cython python3-dev python-setuptools
|
||||||
|
|
||||||
From Ubuntu 15.10, spdylay has been available as a package named
|
From Ubuntu 15.10, spdylay has been available as a package named
|
||||||
`libspdylay-dev`. For the earlier Ubuntu release, you need to build
|
`libspdylay-dev`. For the earlier Ubuntu release, you need to build
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# - Try to find libcares
|
||||||
|
# Once done this will define
|
||||||
|
# LIBCARES_FOUND - System has libcares
|
||||||
|
# LIBCARES_INCLUDE_DIRS - The libcares include directories
|
||||||
|
# LIBCARES_LIBRARIES - The libraries needed to use libcares
|
||||||
|
|
||||||
|
find_package(PkgConfig QUIET)
|
||||||
|
pkg_check_modules(PC_LIBCARES QUIET libcares)
|
||||||
|
|
||||||
|
find_path(LIBCARES_INCLUDE_DIR
|
||||||
|
NAMES ares.h
|
||||||
|
HINTS ${PC_LIBCARES_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
find_library(LIBCARES_LIBRARY
|
||||||
|
NAMES cares
|
||||||
|
HINTS ${PC_LIBCARES_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(LIBCARES_INCLUDE_DIR)
|
||||||
|
set(_version_regex "^#define[ \t]+ARES_VERSION_STR[ \t]+\"([^\"]+)\".*")
|
||||||
|
file(STRINGS "${LIBCARES_INCLUDE_DIR}/ares_version.h"
|
||||||
|
LIBCARES_VERSION REGEX "${_version_regex}")
|
||||||
|
string(REGEX REPLACE "${_version_regex}" "\\1"
|
||||||
|
LIBCARES_VERSION "${LIBCARES_VERSION}")
|
||||||
|
unset(_version_regex)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
# handle the QUIETLY and REQUIRED arguments and set LIBCARES_FOUND to TRUE
|
||||||
|
# if all listed variables are TRUE and the requested version matches.
|
||||||
|
find_package_handle_standard_args(Libcares REQUIRED_VARS
|
||||||
|
LIBCARES_LIBRARY LIBCARES_INCLUDE_DIR
|
||||||
|
VERSION_VAR LIBCARES_VERSION)
|
||||||
|
|
||||||
|
if(LIBCARES_FOUND)
|
||||||
|
set(LIBCARES_LIBRARIES ${LIBCARES_LIBRARY})
|
||||||
|
set(LIBCARES_INCLUDE_DIRS ${LIBCARES_INCLUDE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
mark_as_advanced(LIBCARES_INCLUDE_DIR LIBCARES_LIBRARY)
|
35
configure.ac
35
configure.ac
|
@ -370,6 +370,13 @@ if test "x${have_openssl}" = "xno"; then
|
||||||
AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
|
AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# c-ares (for src)
|
||||||
|
PKG_CHECK_MODULES([LIBCARES], [libcares >= 1.7.5], [have_libcares=yes],
|
||||||
|
[have_libcares=no])
|
||||||
|
if test "x${have_libcares}" = "xno"; then
|
||||||
|
AC_MSG_NOTICE($LIBCARES_PKG_ERRORS)
|
||||||
|
fi
|
||||||
|
|
||||||
# libevent_openssl (for examples)
|
# libevent_openssl (for examples)
|
||||||
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
||||||
PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8],
|
PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8],
|
||||||
|
@ -479,13 +486,14 @@ if test "x${request_asio_lib}" = "xyes"; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL
|
# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL,
|
||||||
# and libev
|
# libev, and libc-ares.
|
||||||
enable_app=no
|
enable_app=no
|
||||||
if test "x${request_app}" != "xno" &&
|
if test "x${request_app}" != "xno" &&
|
||||||
test "x${have_zlib}" = "xyes" &&
|
test "x${have_zlib}" = "xyes" &&
|
||||||
test "x${have_openssl}" = "xyes" &&
|
test "x${have_openssl}" = "xyes" &&
|
||||||
test "x${have_libev}" = "xyes"; then
|
test "x${have_libev}" = "xyes" &&
|
||||||
|
test "x${have_libcares}" = "xyes"; then
|
||||||
enable_app=yes
|
enable_app=yes
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -640,6 +648,26 @@ AC_SYS_LARGEFILE
|
||||||
AC_CHECK_MEMBER([struct tm.tm_gmtoff], [have_struct_tm_tm_gmtoff=yes],
|
AC_CHECK_MEMBER([struct tm.tm_gmtoff], [have_struct_tm_tm_gmtoff=yes],
|
||||||
[have_struct_tm_tm_gmtoff=no], [[#include <time.h>]])
|
[have_struct_tm_tm_gmtoff=no], [[#include <time.h>]])
|
||||||
|
|
||||||
|
AC_CHECK_MEMBER([struct sockaddr_in.sin_len],
|
||||||
|
[AC_DEFINE([HAVE_SOCKADDR_IN_SIN_LEN],[1],
|
||||||
|
[Define to 1 if struct sockaddr_in has sin_len member.])],
|
||||||
|
[],
|
||||||
|
[[
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
]])
|
||||||
|
|
||||||
|
AC_CHECK_MEMBER([struct sockaddr_in6.sin6_len],
|
||||||
|
[AC_DEFINE([HAVE_SOCKADDR_IN6_SIN6_LEN],[1],
|
||||||
|
[Define to 1 if struct sockaddr_in6 has sin6_len member.])],
|
||||||
|
[],
|
||||||
|
[[
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
]])
|
||||||
|
|
||||||
if test "x$have_struct_tm_tm_gmtoff" = "xyes"; then
|
if test "x$have_struct_tm_tm_gmtoff" = "xyes"; then
|
||||||
AC_DEFINE([HAVE_STRUCT_TM_TM_GMTOFF], [1],
|
AC_DEFINE([HAVE_STRUCT_TM_TM_GMTOFF], [1],
|
||||||
[Define to 1 if you have `struct tm.tm_gmtoff` member.])
|
[Define to 1 if you have `struct tm.tm_gmtoff` member.])
|
||||||
|
@ -890,6 +918,7 @@ AC_MSG_NOTICE([summary of build options:
|
||||||
OpenSSL: ${have_openssl} (CFLAGS='${OPENSSL_CFLAGS}' LIBS='${OPENSSL_LIBS}')
|
OpenSSL: ${have_openssl} (CFLAGS='${OPENSSL_CFLAGS}' LIBS='${OPENSSL_LIBS}')
|
||||||
Libxml2: ${have_libxml2} (CFLAGS='${XML_CPPFLAGS}' LIBS='${XML_LIBS}')
|
Libxml2: ${have_libxml2} (CFLAGS='${XML_CPPFLAGS}' LIBS='${XML_LIBS}')
|
||||||
Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}')
|
Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}')
|
||||||
|
Libc-ares ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}')
|
||||||
Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}')
|
Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}')
|
||||||
Spdylay: ${have_spdylay} (CFLAGS='${LIBSPDYLAY_CFLAGS}' LIBS='${LIBSPDYLAY_LIBS}')
|
Spdylay: ${have_spdylay} (CFLAGS='${LIBSPDYLAY_CFLAGS}' LIBS='${LIBSPDYLAY_LIBS}')
|
||||||
Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')
|
Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')
|
||||||
|
|
|
@ -1767,10 +1767,10 @@ The replacement is done instantly without breaking existing
|
||||||
connections or requests. It also avoids any process creation as is
|
connections or requests. It also avoids any process creation as is
|
||||||
the case with hot swapping with signals.
|
the case with hot swapping with signals.
|
||||||
|
|
||||||
The one limitation is that only numeric IP address is allowd in
|
The one limitation is that only numeric IP address is allowed in
|
||||||
:option:`backend <--backend>` in request body while non numeric
|
:option:`backend <--backend>` in request body unless "dns" parameter
|
||||||
hostname is allowed in command-line or configuration file is read
|
is missing while non numeric hostname is allowed in command-line or
|
||||||
using :option:`--conf`.
|
configuration file is read using :option:`--conf`.
|
||||||
|
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
--------
|
--------
|
||||||
|
|
|
@ -149,6 +149,9 @@ OPTIONS = [
|
||||||
"ecdh-curves",
|
"ecdh-curves",
|
||||||
"tls-sct-dir",
|
"tls-sct-dir",
|
||||||
"backend-connect-timeout",
|
"backend-connect-timeout",
|
||||||
|
"dns-cache-timeout",
|
||||||
|
"dns-lookup-timeout",
|
||||||
|
"dns-max-try",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOGVARS = [
|
LOGVARS = [
|
||||||
|
|
|
@ -1369,6 +1369,42 @@ func TestH2H1ProxyProtocolV1InvalidID(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestH2H1ExternalDNS tests that DNS resolution using external DNS
|
||||||
|
// with HTTP/1 backend works.
|
||||||
|
func TestH2H1ExternalDNS(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--external-dns"}, t, noopHandler)
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1ExternalDNS",
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestH2H1DNS tests that DNS resolution without external DNS with
|
||||||
|
// HTTP/1 backend works.
|
||||||
|
func TestH2H1DNS(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--dns"}, t, noopHandler)
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1DNS",
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestH2H1GracefulShutdown tests graceful shutdown.
|
// TestH2H1GracefulShutdown tests graceful shutdown.
|
||||||
func TestH2H1GracefulShutdown(t *testing.T) {
|
func TestH2H1GracefulShutdown(t *testing.T) {
|
||||||
st := newServerTester(nil, t, noopHandler)
|
st := newServerTester(nil, t, noopHandler)
|
||||||
|
@ -1845,6 +1881,42 @@ func TestH2H2RespPhaseReturn(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestH2H2ExternalDNS tests that DNS resolution using external DNS
|
||||||
|
// with HTTP/2 backend works.
|
||||||
|
func TestH2H2ExternalDNS(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--http2-bridge", "--external-dns"}, t, noopHandler)
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H2ExternalDNS",
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestH2H2DNS tests that DNS resolution without external DNS with
|
||||||
|
// HTTP/2 backend works.
|
||||||
|
func TestH2H2DNS(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--http2-bridge", "--dns"}, t, noopHandler)
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
res, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H2DNS",
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestH2APIBackendconfig exercise backendconfig API endpoint routine
|
// TestH2APIBackendconfig exercise backendconfig API endpoint routine
|
||||||
// for successful case.
|
// for successful case.
|
||||||
func TestH2APIBackendconfig(t *testing.T) {
|
func TestH2APIBackendconfig(t *testing.T) {
|
||||||
|
|
|
@ -101,10 +101,17 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
|
||||||
args := []string{}
|
args := []string{}
|
||||||
|
|
||||||
backendTLS := false
|
backendTLS := false
|
||||||
|
dns := false
|
||||||
|
externalDNS := false
|
||||||
for _, k := range src_args {
|
for _, k := range src_args {
|
||||||
switch k {
|
switch k {
|
||||||
case "--http2-bridge":
|
case "--http2-bridge":
|
||||||
backendTLS = true
|
backendTLS = true
|
||||||
|
case "--dns":
|
||||||
|
dns = true
|
||||||
|
case "--external-dns":
|
||||||
|
dns = true
|
||||||
|
externalDNS = true
|
||||||
default:
|
default:
|
||||||
args = append(args, k)
|
args = append(args, k)
|
||||||
}
|
}
|
||||||
|
@ -117,7 +124,7 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
|
||||||
ts.TLS = new(tls.Config)
|
ts.TLS = new(tls.Config)
|
||||||
ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2")
|
ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2")
|
||||||
ts.StartTLS()
|
ts.StartTLS()
|
||||||
args = append(args, "-k", "--backend-tls")
|
args = append(args, "-k")
|
||||||
} else {
|
} else {
|
||||||
ts.Start()
|
ts.Start()
|
||||||
}
|
}
|
||||||
|
@ -134,9 +141,23 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
|
||||||
|
|
||||||
// URL.Host looks like "127.0.0.1:8080", but we want
|
// URL.Host looks like "127.0.0.1:8080", but we want
|
||||||
// "127.0.0.1,8080"
|
// "127.0.0.1,8080"
|
||||||
b := "-b" + strings.Replace(backendURL.Host, ":", ",", -1)
|
b := "-b"
|
||||||
|
if !externalDNS {
|
||||||
|
b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1))
|
||||||
|
} else {
|
||||||
|
sep := strings.LastIndex(backendURL.Host, ":")
|
||||||
|
if sep == -1 {
|
||||||
|
t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host)
|
||||||
|
}
|
||||||
|
// We use awesome service xip.io.
|
||||||
|
b += fmt.Sprintf("%v.xip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:])
|
||||||
|
}
|
||||||
|
|
||||||
if backendTLS {
|
if backendTLS {
|
||||||
b += ";;proto=h2;tls"
|
b += ";proto=h2;tls"
|
||||||
|
}
|
||||||
|
if dns {
|
||||||
|
b += ";dns"
|
||||||
}
|
}
|
||||||
|
|
||||||
noTLS := "no-tls"
|
noTLS := "no-tls"
|
||||||
|
|
|
@ -19,6 +19,7 @@ include_directories(
|
||||||
${LIBXML2_INCLUDE_DIRS}
|
${LIBXML2_INCLUDE_DIRS}
|
||||||
${LIBEV_INCLUDE_DIRS}
|
${LIBEV_INCLUDE_DIRS}
|
||||||
${OPENSSL_INCLUDE_DIRS}
|
${OPENSSL_INCLUDE_DIRS}
|
||||||
|
${LIBCARES_INCLUDE_DIRS}
|
||||||
${JANSSON_INCLUDE_DIRS}
|
${JANSSON_INCLUDE_DIRS}
|
||||||
${ZLIB_INCLUDE_DIRS}
|
${ZLIB_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
@ -31,6 +32,7 @@ link_libraries(
|
||||||
${LIBXML2_LIBRARIES}
|
${LIBXML2_LIBRARIES}
|
||||||
${LIBEV_LIBRARIES}
|
${LIBEV_LIBRARIES}
|
||||||
${OPENSSL_LIBRARIES}
|
${OPENSSL_LIBRARIES}
|
||||||
|
${LIBCARES_LIBRARIES}
|
||||||
${JANSSON_LIBRARIES}
|
${JANSSON_LIBRARIES}
|
||||||
${ZLIB_LIBRARIES}
|
${ZLIB_LIBRARIES}
|
||||||
${APP_LIBRARIES}
|
${APP_LIBRARIES}
|
||||||
|
@ -112,6 +114,9 @@ if(ENABLE_APP)
|
||||||
shrpx_api_downstream_connection.cc
|
shrpx_api_downstream_connection.cc
|
||||||
shrpx_health_monitor_downstream_connection.cc
|
shrpx_health_monitor_downstream_connection.cc
|
||||||
shrpx_exec.cc
|
shrpx_exec.cc
|
||||||
|
shrpx_dns_resolver.cc
|
||||||
|
shrpx_dual_dns_resolver.cc
|
||||||
|
shrpx_dns_tracker.cc
|
||||||
xsi_strerror.c
|
xsi_strerror.c
|
||||||
)
|
)
|
||||||
if(HAVE_SPDYLAY)
|
if(HAVE_SPDYLAY)
|
||||||
|
|
|
@ -44,6 +44,7 @@ AM_CPPFLAGS = \
|
||||||
@XML_CPPFLAGS@ \
|
@XML_CPPFLAGS@ \
|
||||||
@LIBEV_CFLAGS@ \
|
@LIBEV_CFLAGS@ \
|
||||||
@OPENSSL_CFLAGS@ \
|
@OPENSSL_CFLAGS@ \
|
||||||
|
@LIBCARES_CFLAGS@ \
|
||||||
@JANSSON_CFLAGS@ \
|
@JANSSON_CFLAGS@ \
|
||||||
@ZLIB_CFLAGS@ \
|
@ZLIB_CFLAGS@ \
|
||||||
@DEFS@
|
@DEFS@
|
||||||
|
@ -55,6 +56,7 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \
|
||||||
@XML_LIBS@ \
|
@XML_LIBS@ \
|
||||||
@LIBEV_LIBS@ \
|
@LIBEV_LIBS@ \
|
||||||
@OPENSSL_LIBS@ \
|
@OPENSSL_LIBS@ \
|
||||||
|
@LIBCARES_LIBS@ \
|
||||||
@JANSSON_LIBS@ \
|
@JANSSON_LIBS@ \
|
||||||
@ZLIB_LIBS@ \
|
@ZLIB_LIBS@ \
|
||||||
@APPLDFLAGS@
|
@APPLDFLAGS@
|
||||||
|
@ -139,6 +141,9 @@ NGHTTPX_SRCS = \
|
||||||
shrpx_health_monitor_downstream_connection.cc \
|
shrpx_health_monitor_downstream_connection.cc \
|
||||||
shrpx_health_monitor_downstream_connection.h \
|
shrpx_health_monitor_downstream_connection.h \
|
||||||
shrpx_exec.cc shrpx_exec.h \
|
shrpx_exec.cc shrpx_exec.h \
|
||||||
|
shrpx_dns_resolver.cc shrpx_dns_resolver.h \
|
||||||
|
shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \
|
||||||
|
shrpx_dns_tracker.cc shrpx_dns_tracker.h \
|
||||||
buffer.h memchunk.h template.h allocator.h \
|
buffer.h memchunk.h template.h allocator.h \
|
||||||
xsi_strerror.c xsi_strerror.h
|
xsi_strerror.c xsi_strerror.h
|
||||||
|
|
||||||
|
|
64
src/shrpx.cc
64
src/shrpx.cc
|
@ -1486,6 +1486,14 @@ void fill_default_config(Config *config) {
|
||||||
|
|
||||||
auto &apiconf = config->api;
|
auto &apiconf = config->api;
|
||||||
apiconf.max_request_body = 16_k;
|
apiconf.max_request_body = 16_k;
|
||||||
|
|
||||||
|
auto &dnsconf = config->dns;
|
||||||
|
{
|
||||||
|
auto &timeoutconf = dnsconf.timeout;
|
||||||
|
timeoutconf.cache = 10_s;
|
||||||
|
timeoutconf.lookup = 5_s;
|
||||||
|
}
|
||||||
|
dnsconf.max_try = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -1587,13 +1595,13 @@ Connections:
|
||||||
Several parameters <PARAM> are accepted after <PATTERN>.
|
Several parameters <PARAM> are accepted after <PATTERN>.
|
||||||
The parameters are delimited by ";". The available
|
The parameters are delimited by ";". The available
|
||||||
parameters are: "proto=<PROTO>", "tls",
|
parameters are: "proto=<PROTO>", "tls",
|
||||||
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>", and
|
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
|
||||||
"affinity=<METHOD>". The parameter consists of keyword,
|
"affinity=<METHOD>", and "dns". The parameter consists
|
||||||
and optionally followed by "=" and value. For example,
|
of keyword, and optionally followed by "=" and value.
|
||||||
the parameter "proto=h2" consists of the keyword "proto"
|
For example, the parameter "proto=h2" consists of the
|
||||||
and value "h2". The parameter "tls" consists of the
|
keyword "proto" and value "h2". The parameter "tls"
|
||||||
keyword "tls" without value. Each parameter is
|
consists of the keyword "tls" without value. Each
|
||||||
described as follows.
|
parameter is described as follows.
|
||||||
|
|
||||||
The backend application protocol can be specified using
|
The backend application protocol can be specified using
|
||||||
optional "proto" parameter, and in the form of
|
optional "proto" parameter, and in the form of
|
||||||
|
@ -1642,6 +1650,14 @@ Connections:
|
||||||
break if one of the backend gets unreachable, or backend
|
break if one of the backend gets unreachable, or backend
|
||||||
settings are reloaded or replaced by API.
|
settings are reloaded or replaced by API.
|
||||||
|
|
||||||
|
By default, name resolution of backend host name is done
|
||||||
|
at start up, or reloading configuration. If "dns"
|
||||||
|
parameter is given, name resolution takes place
|
||||||
|
dynamically. This is useful if backend address changes
|
||||||
|
frequently. If "dns" is given, name resolution of
|
||||||
|
backend host name at start up, or reloading
|
||||||
|
configuration is skipped.
|
||||||
|
|
||||||
Since ";" and ":" are used as delimiter, <PATTERN> must
|
Since ";" and ":" are used as delimiter, <PATTERN> must
|
||||||
not contain these characters. Since ";" has special
|
not contain these characters. Since ";" has special
|
||||||
meaning in shell, the option value must be quoted.
|
meaning in shell, the option value must be quoted.
|
||||||
|
@ -2368,6 +2384,25 @@ API:
|
||||||
Default: )"
|
Default: )"
|
||||||
<< util::utos_unit(config->api.max_request_body) << R"(
|
<< util::utos_unit(config->api.max_request_body) << R"(
|
||||||
|
|
||||||
|
DNS:
|
||||||
|
--dns-cache-timeout=<DURATION>
|
||||||
|
Set duration that cached DNS results remain valid. Note
|
||||||
|
that nghttpx caches the unsuccessful results as well.
|
||||||
|
Default: )"
|
||||||
|
<< util::duration_str(config->dns.timeout.cache) << R"(
|
||||||
|
--dns-lookup-timeout=<DURATION>
|
||||||
|
Set timeout that DNS server is given to respond to the
|
||||||
|
initial DNS query. For the 2nd and later queries,
|
||||||
|
server is given time based on this timeout, and it is
|
||||||
|
scaled linearly.
|
||||||
|
Default: )"
|
||||||
|
<< util::duration_str(config->dns.timeout.lookup) << R"(
|
||||||
|
--dns-max-try=<N>
|
||||||
|
Set the number of DNS query before nghttpx gives up name
|
||||||
|
lookup.
|
||||||
|
Default: )"
|
||||||
|
<< config->dns.max_try << R"(
|
||||||
|
|
||||||
Debug:
|
Debug:
|
||||||
--frontend-http2-dump-request-header=<PATH>
|
--frontend-http2-dump-request-header=<PATH>
|
||||||
Dumps request headers received by HTTP/2 frontend to the
|
Dumps request headers received by HTTP/2 frontend to the
|
||||||
|
@ -3027,6 +3062,9 @@ int main(int argc, char **argv) {
|
||||||
{SHRPX_OPT_TLS_SCT_DIR.c_str(), required_argument, &flag, 141},
|
{SHRPX_OPT_TLS_SCT_DIR.c_str(), required_argument, &flag, 141},
|
||||||
{SHRPX_OPT_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag,
|
{SHRPX_OPT_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag,
|
||||||
142},
|
142},
|
||||||
|
{SHRPX_OPT_DNS_CACHE_TIMEOUT.c_str(), required_argument, &flag, 143},
|
||||||
|
{SHRPX_OPT_DNS_LOOKUP_TIMEOUT.c_str(), required_argument, &flag, 144},
|
||||||
|
{SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -3700,6 +3738,18 @@ int main(int argc, char **argv) {
|
||||||
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT,
|
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT,
|
||||||
StringRef{optarg});
|
StringRef{optarg});
|
||||||
break;
|
break;
|
||||||
|
case 143:
|
||||||
|
// --dns-cache-timeout
|
||||||
|
cmdcfgs.emplace_back(SHRPX_OPT_DNS_CACHE_TIMEOUT, StringRef{optarg});
|
||||||
|
break;
|
||||||
|
case 144:
|
||||||
|
// --dns-lookup-timeou
|
||||||
|
cmdcfgs.emplace_back(SHRPX_OPT_DNS_LOOKUP_TIMEOUT, StringRef{optarg});
|
||||||
|
break;
|
||||||
|
case 145:
|
||||||
|
// --dns-max-try
|
||||||
|
cmdcfgs.emplace_back(SHRPX_OPT_DNS_MAX_TRY, StringRef{optarg});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -728,6 +728,7 @@ struct DownstreamParams {
|
||||||
shrpx_proto proto;
|
shrpx_proto proto;
|
||||||
shrpx_session_affinity affinity;
|
shrpx_session_affinity affinity;
|
||||||
bool tls;
|
bool tls;
|
||||||
|
bool dns;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -801,6 +802,8 @@ int parse_downstream_params(DownstreamParams &out,
|
||||||
LOG(ERROR) << "backend: affinity: value must be either none or ip";
|
LOG(ERROR) << "backend: affinity: value must be either none or ip";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
} else if (util::strieq_l("dns", param)) {
|
||||||
|
out.dns = true;
|
||||||
} else if (!param.empty()) {
|
} else if (!param.empty()) {
|
||||||
LOG(ERROR) << "backend: " << param << ": unknown keyword";
|
LOG(ERROR) << "backend: " << param << ": unknown keyword";
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -841,11 +844,17 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addr.host_unix && params.dns) {
|
||||||
|
LOG(ERROR) << "backend: dns: cannot be used for UNIX domain socket";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
addr.fall = params.fall;
|
addr.fall = params.fall;
|
||||||
addr.rise = params.rise;
|
addr.rise = params.rise;
|
||||||
addr.proto = params.proto;
|
addr.proto = params.proto;
|
||||||
addr.tls = params.tls;
|
addr.tls = params.tls;
|
||||||
addr.sni = make_string_ref(downstreamconf.balloc, params.sni);
|
addr.sni = make_string_ref(downstreamconf.balloc, params.sni);
|
||||||
|
addr.dns = params.dns;
|
||||||
|
|
||||||
auto &routerconf = downstreamconf.router;
|
auto &routerconf = downstreamconf.router;
|
||||||
auto &router = routerconf.router;
|
auto &router = routerconf.router;
|
||||||
|
@ -1361,6 +1370,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'y':
|
case 'y':
|
||||||
|
if (util::strieq_l("dns-max-tr", name, 10)) {
|
||||||
|
return SHRPX_OPTID_DNS_MAX_TRY;
|
||||||
|
}
|
||||||
if (util::strieq_l("http2-prox", name, 10)) {
|
if (util::strieq_l("http2-prox", name, 10)) {
|
||||||
return SHRPX_OPTID_HTTP2_PROXY;
|
return SHRPX_OPTID_HTTP2_PROXY;
|
||||||
}
|
}
|
||||||
|
@ -1522,6 +1534,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
|
if (util::strieq_l("dns-cache-timeou", name, 16)) {
|
||||||
|
return SHRPX_OPTID_DNS_CACHE_TIMEOUT;
|
||||||
|
}
|
||||||
if (util::strieq_l("worker-read-burs", name, 16)) {
|
if (util::strieq_l("worker-read-burs", name, 16)) {
|
||||||
return SHRPX_OPTID_WORKER_READ_BURST;
|
return SHRPX_OPTID_WORKER_READ_BURST;
|
||||||
}
|
}
|
||||||
|
@ -1536,6 +1551,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
|
if (util::strieq_l("dns-lookup-timeou", name, 17)) {
|
||||||
|
return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT;
|
||||||
|
}
|
||||||
if (util::strieq_l("worker-write-burs", name, 17)) {
|
if (util::strieq_l("worker-write-burs", name, 17)) {
|
||||||
return SHRPX_OPTID_WORKER_WRITE_BURST;
|
return SHRPX_OPTID_WORKER_WRITE_BURST;
|
||||||
}
|
}
|
||||||
|
@ -3090,6 +3108,24 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
LOG(WARN) << opt << ": This option requires OpenSSL >= 1.0.2";
|
LOG(WARN) << opt << ": This option requires OpenSSL >= 1.0.2";
|
||||||
return 0;
|
return 0;
|
||||||
#endif // !(!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L)
|
#endif // !(!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L)
|
||||||
|
case SHRPX_OPTID_DNS_CACHE_TIMEOUT:
|
||||||
|
return parse_duration(&config->dns.timeout.cache, opt, optarg);
|
||||||
|
case SHRPX_OPTID_DNS_LOOKUP_TIMEOUT:
|
||||||
|
return parse_duration(&config->dns.timeout.lookup, opt, optarg);
|
||||||
|
case SHRPX_OPTID_DNS_MAX_TRY: {
|
||||||
|
int n;
|
||||||
|
if (parse_uint(&n, opt, optarg) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n > 5) {
|
||||||
|
LOG(ERROR) << opt << ": must be smaller than or equal to 5";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
config->dns.max_try = n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
case SHRPX_OPTID_CONF:
|
case SHRPX_OPTID_CONF:
|
||||||
LOG(WARN) << "conf: ignored";
|
LOG(WARN) << "conf: ignored";
|
||||||
|
|
||||||
|
@ -3442,24 +3478,38 @@ int configure_downstream_group(Config *config, bool http2_proxy,
|
||||||
auto hostport =
|
auto hostport =
|
||||||
util::make_hostport(downstreamconf.balloc, addr.host, addr.port);
|
util::make_hostport(downstreamconf.balloc, addr.host, addr.port);
|
||||||
|
|
||||||
if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
|
if (!addr.dns) {
|
||||||
downstreamconf.family, resolve_flags) == -1) {
|
if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
|
||||||
LOG(FATAL) << "Resolving backend address failed: " << hostport;
|
downstreamconf.family, resolve_flags) == -1) {
|
||||||
return -1;
|
LOG(FATAL) << "Resolving backend address failed: " << hostport;
|
||||||
}
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Resolved backend address: " << hostport << " -> "
|
LOG(INFO) << "Resolved backend address: " << hostport << " -> "
|
||||||
<< util::to_numeric_addr(&addr.addr);
|
<< util::to_numeric_addr(&addr.addr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG(INFO) << "Resolving backend address " << hostport
|
||||||
|
<< " takes place dynamically";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g.affinity == AFFINITY_IP) {
|
if (g.affinity == AFFINITY_IP) {
|
||||||
size_t idx = 0;
|
size_t idx = 0;
|
||||||
for (auto &addr : g.addrs) {
|
for (auto &addr : g.addrs) {
|
||||||
auto p = reinterpret_cast<uint8_t *>(&addr.addr.su);
|
StringRef key;
|
||||||
rv = compute_affinity_hash(g.affinity_hash, idx,
|
if (addr.dns) {
|
||||||
StringRef{p, addr.addr.len});
|
if (addr.host_unix) {
|
||||||
|
key = addr.host;
|
||||||
|
} else {
|
||||||
|
key = addr.hostport;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto p = reinterpret_cast<uint8_t *>(&addr.addr.su);
|
||||||
|
key = StringRef{p, addr.addr.len};
|
||||||
|
}
|
||||||
|
rv = compute_affinity_hash(g.affinity_hash, idx, key);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,6 +312,11 @@ constexpr auto SHRPX_OPT_ECDH_CURVES = StringRef::from_lit("ecdh-curves");
|
||||||
constexpr auto SHRPX_OPT_TLS_SCT_DIR = StringRef::from_lit("tls-sct-dir");
|
constexpr auto SHRPX_OPT_TLS_SCT_DIR = StringRef::from_lit("tls-sct-dir");
|
||||||
constexpr auto SHRPX_OPT_BACKEND_CONNECT_TIMEOUT =
|
constexpr auto SHRPX_OPT_BACKEND_CONNECT_TIMEOUT =
|
||||||
StringRef::from_lit("backend-connect-timeout");
|
StringRef::from_lit("backend-connect-timeout");
|
||||||
|
constexpr auto SHRPX_OPT_DNS_CACHE_TIMEOUT =
|
||||||
|
StringRef::from_lit("dns-cache-timeout");
|
||||||
|
constexpr auto SHRPX_OPT_DNS_LOOKUP_TIMEOUT =
|
||||||
|
StringRef::from_lit("dns-lookup-timeout");
|
||||||
|
constexpr auto SHRPX_OPT_DNS_MAX_TRY = StringRef::from_lit("dns-max-try");
|
||||||
|
|
||||||
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||||
|
|
||||||
|
@ -379,6 +384,7 @@ struct UpstreamAddr {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DownstreamAddrConfig {
|
struct DownstreamAddrConfig {
|
||||||
|
// Resolved address if |dns| is false
|
||||||
Address addr;
|
Address addr;
|
||||||
// backend address. If |host_unix| is true, this is UNIX domain
|
// backend address. If |host_unix| is true, this is UNIX domain
|
||||||
// socket path. This must be NULL terminated string.
|
// socket path. This must be NULL terminated string.
|
||||||
|
@ -397,6 +403,8 @@ struct DownstreamAddrConfig {
|
||||||
// true if |host| contains UNIX domain socket path.
|
// true if |host| contains UNIX domain socket path.
|
||||||
bool host_unix;
|
bool host_unix;
|
||||||
bool tls;
|
bool tls;
|
||||||
|
// true if dynamic DNS is enabled
|
||||||
|
bool dns;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mapping hash to idx which is an index into
|
// Mapping hash to idx which is an index into
|
||||||
|
@ -777,6 +785,16 @@ struct APIConfig {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DNSConfig {
|
||||||
|
struct {
|
||||||
|
ev_tstamp cache;
|
||||||
|
ev_tstamp lookup;
|
||||||
|
} timeout;
|
||||||
|
// The number of tries name resolver makes before abandoning
|
||||||
|
// request.
|
||||||
|
size_t max_try;
|
||||||
|
};
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
Config()
|
Config()
|
||||||
: balloc(4096, 4096),
|
: balloc(4096, 4096),
|
||||||
|
@ -787,6 +805,7 @@ struct Config {
|
||||||
logging{},
|
logging{},
|
||||||
conn{},
|
conn{},
|
||||||
api{},
|
api{},
|
||||||
|
dns{},
|
||||||
num_worker{0},
|
num_worker{0},
|
||||||
padding{0},
|
padding{0},
|
||||||
rlimit_nofile{0},
|
rlimit_nofile{0},
|
||||||
|
@ -815,6 +834,7 @@ struct Config {
|
||||||
LoggingConfig logging;
|
LoggingConfig logging;
|
||||||
ConnectionConfig conn;
|
ConnectionConfig conn;
|
||||||
APIConfig api;
|
APIConfig api;
|
||||||
|
DNSConfig dns;
|
||||||
StringRef pid_file;
|
StringRef pid_file;
|
||||||
StringRef conf_path;
|
StringRef conf_path;
|
||||||
StringRef user;
|
StringRef user;
|
||||||
|
@ -891,6 +911,9 @@ enum {
|
||||||
SHRPX_OPTID_CONF,
|
SHRPX_OPTID_CONF,
|
||||||
SHRPX_OPTID_DAEMON,
|
SHRPX_OPTID_DAEMON,
|
||||||
SHRPX_OPTID_DH_PARAM_FILE,
|
SHRPX_OPTID_DH_PARAM_FILE,
|
||||||
|
SHRPX_OPTID_DNS_CACHE_TIMEOUT,
|
||||||
|
SHRPX_OPTID_DNS_LOOKUP_TIMEOUT,
|
||||||
|
SHRPX_OPTID_DNS_MAX_TRY,
|
||||||
SHRPX_OPTID_ECDH_CURVES,
|
SHRPX_OPTID_ECDH_CURVES,
|
||||||
SHRPX_OPTID_ERROR_PAGE,
|
SHRPX_OPTID_ERROR_PAGE,
|
||||||
SHRPX_OPTID_ERRORLOG_FILE,
|
SHRPX_OPTID_ERRORLOG_FILE,
|
||||||
|
|
|
@ -0,0 +1,344 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_dns_resolver.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "shrpx_log.h"
|
||||||
|
#include "shrpx_connection.h"
|
||||||
|
#include "shrpx_config.h"
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void sock_state_cb(void *data, int s, int read, int write) {
|
||||||
|
auto resolv = static_cast<DNSResolver *>(data);
|
||||||
|
|
||||||
|
if (resolv->get_status(nullptr) != DNS_STATUS_RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read) {
|
||||||
|
resolv->start_rev(s);
|
||||||
|
} else {
|
||||||
|
resolv->stop_rev(s);
|
||||||
|
}
|
||||||
|
if (write) {
|
||||||
|
resolv->start_wev(s);
|
||||||
|
} else {
|
||||||
|
resolv->stop_wev(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void host_cb(void *arg, int status, int timeouts, hostent *hostent) {
|
||||||
|
auto resolv = static_cast<DNSResolver *>(arg);
|
||||||
|
resolv->on_result(status, hostent);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void process_result(DNSResolver *resolv) {
|
||||||
|
auto cb = resolv->get_complete_cb();
|
||||||
|
if (!cb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Address result;
|
||||||
|
auto status = resolv->get_status(&result);
|
||||||
|
switch (status) {
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
cb(status, &result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// resolv may be deleted here.
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto resolv = static_cast<DNSResolver *>(w->data);
|
||||||
|
resolv->on_read(w->fd);
|
||||||
|
process_result(resolv);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto resolv = static_cast<DNSResolver *>(w->data);
|
||||||
|
resolv->on_write(w->fd);
|
||||||
|
process_result(resolv);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto resolv = static_cast<DNSResolver *>(w->data);
|
||||||
|
resolv->on_timeout();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void stop_ev(struct ev_loop *loop,
|
||||||
|
const std::vector<std::unique_ptr<ev_io>> &evs) {
|
||||||
|
for (auto &w : evs) {
|
||||||
|
ev_io_stop(loop, w.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
DNSResolver::DNSResolver(struct ev_loop *loop)
|
||||||
|
: loop_(loop),
|
||||||
|
channel_(nullptr),
|
||||||
|
family_(AF_UNSPEC),
|
||||||
|
status_(DNS_STATUS_IDLE) {
|
||||||
|
ev_timer_init(&timer_, timeoutcb, 0., 0.);
|
||||||
|
timer_.data = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DNSResolver::~DNSResolver() {
|
||||||
|
if (channel_) {
|
||||||
|
ares_destroy(channel_);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_ev(loop_, revs_);
|
||||||
|
stop_ev(loop_, wevs_);
|
||||||
|
|
||||||
|
ev_timer_stop(loop_, &timer_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DNSResolver::resolve(const StringRef &name, int family) {
|
||||||
|
if (status_ != DNS_STATUS_IDLE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Start resolving host " << name << " in IPv"
|
||||||
|
<< (family == AF_INET ? "4" : "6");
|
||||||
|
}
|
||||||
|
|
||||||
|
name_ = name;
|
||||||
|
family_ = family;
|
||||||
|
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
auto &dnsconf = get_config()->dns;
|
||||||
|
|
||||||
|
ares_options opts{};
|
||||||
|
opts.sock_state_cb = sock_state_cb;
|
||||||
|
opts.sock_state_cb_data = this;
|
||||||
|
opts.timeout = static_cast<int>(dnsconf.timeout.lookup * 1000);
|
||||||
|
opts.tries = dnsconf.max_try;
|
||||||
|
|
||||||
|
auto optmask = ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES;
|
||||||
|
|
||||||
|
ares_channel chan;
|
||||||
|
rv = ares_init_options(&chan, &opts, optmask);
|
||||||
|
if (rv != ARES_SUCCESS) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "ares_init_options failed: " << ares_strerror(rv);
|
||||||
|
}
|
||||||
|
status_ = DNS_STATUS_ERROR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_ = chan;
|
||||||
|
status_ = DNS_STATUS_RUNNING;
|
||||||
|
|
||||||
|
ares_gethostbyname(channel_, name_.c_str(), family_, host_cb, this);
|
||||||
|
reset_timeout();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DNSResolver::on_read(int fd) { return handle_event(fd, ARES_SOCKET_BAD); }
|
||||||
|
|
||||||
|
int DNSResolver::on_write(int fd) { return handle_event(ARES_SOCKET_BAD, fd); }
|
||||||
|
|
||||||
|
int DNSResolver::on_timeout() {
|
||||||
|
return handle_event(ARES_SOCKET_BAD, ARES_SOCKET_BAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DNSResolver::handle_event(int rfd, int wfd) {
|
||||||
|
if (status_ == DNS_STATUS_IDLE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ares_process_fd(channel_, rfd, wfd);
|
||||||
|
|
||||||
|
switch (status_) {
|
||||||
|
case DNS_STATUS_RUNNING: {
|
||||||
|
reset_timeout();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
return 0;
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
return -1;
|
||||||
|
default:
|
||||||
|
// Unreachable
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSResolver::reset_timeout() {
|
||||||
|
if (status_ != DNS_STATUS_RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timeval tvout;
|
||||||
|
auto tv = ares_timeout(channel_, nullptr, &tvout);
|
||||||
|
if (tv == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timer_.repeat = tv->tv_sec + tv->tv_usec / 1000000.;
|
||||||
|
ev_timer_again(loop_, &timer_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DNSResolver::get_status(Address *result) const {
|
||||||
|
if (status_ != DNS_STATUS_OK) {
|
||||||
|
return status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
memcpy(result, &result_, sizeof(result_));
|
||||||
|
}
|
||||||
|
|
||||||
|
return status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void start_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
|
||||||
|
int fd, int event, IOCb cb, void *data) {
|
||||||
|
for (auto &w : evs) {
|
||||||
|
if (w->fd == fd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto &w : evs) {
|
||||||
|
if (w->fd == -1) {
|
||||||
|
ev_io_set(w.get(), fd, event);
|
||||||
|
ev_io_start(loop, w.get());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto w = make_unique<ev_io>();
|
||||||
|
ev_io_init(w.get(), cb, fd, event);
|
||||||
|
w->data = data;
|
||||||
|
ev_io_start(loop, w.get());
|
||||||
|
evs.emplace_back(std::move(w));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void stop_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
|
||||||
|
int fd, int event) {
|
||||||
|
for (auto &w : evs) {
|
||||||
|
if (w->fd == fd) {
|
||||||
|
ev_io_stop(loop, w.get());
|
||||||
|
ev_io_set(w.get(), -1, event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void DNSResolver::start_rev(int fd) {
|
||||||
|
start_ev(revs_, loop_, fd, EV_READ, readcb, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSResolver::stop_rev(int fd) { stop_ev(revs_, loop_, fd, EV_READ); }
|
||||||
|
|
||||||
|
void DNSResolver::start_wev(int fd) {
|
||||||
|
start_ev(wevs_, loop_, fd, EV_WRITE, writecb, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSResolver::stop_wev(int fd) { stop_ev(wevs_, loop_, fd, EV_WRITE); }
|
||||||
|
|
||||||
|
void DNSResolver::on_result(int status, hostent *hostent) {
|
||||||
|
stop_ev(loop_, revs_);
|
||||||
|
stop_ev(loop_, wevs_);
|
||||||
|
ev_timer_stop(loop_, &timer_);
|
||||||
|
|
||||||
|
if (status != ARES_SUCCESS) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup for " << name_
|
||||||
|
<< " failed: " << ares_strerror(status);
|
||||||
|
}
|
||||||
|
status_ = DNS_STATUS_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (hostent->h_addrtype) {
|
||||||
|
case AF_INET:
|
||||||
|
for (auto ap = hostent->h_addr_list; *ap; ++ap) {
|
||||||
|
status_ = DNS_STATUS_OK;
|
||||||
|
result_.len = sizeof(result_.su.in);
|
||||||
|
result_.su.in = {};
|
||||||
|
result_.su.in.sin_family = AF_INET;
|
||||||
|
#ifdef HAVE_SOCKADDR_IN_SIN_LEN
|
||||||
|
result_.su.in.sin_len = sizeof(result_.su.in);
|
||||||
|
#endif // HAVE_SOCKADDR_IN_SIN_LEN
|
||||||
|
memcpy(&result_.su.in.sin_addr, *ap, sizeof(result_.su.in.sin_addr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
for (auto ap = hostent->h_addr_list; *ap; ++ap) {
|
||||||
|
status_ = DNS_STATUS_OK;
|
||||||
|
result_.len = sizeof(result_.su.in6);
|
||||||
|
result_.su.in6 = {};
|
||||||
|
result_.su.in6.sin6_family = AF_INET6;
|
||||||
|
#ifdef HAVE_SOCKADDR_IN6_SIN6_LEN
|
||||||
|
result_.su.in6.sin6_len = sizeof(result_.su.in6);
|
||||||
|
#endif // HAVE_SOCKADDR_IN6_SIN6_LEN
|
||||||
|
memcpy(&result_.su.in6.sin6_addr, *ap, sizeof(result_.su.in6.sin6_addr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status_ == DNS_STATUS_OK) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup succeeded: " << name_ << " -> "
|
||||||
|
<< util::numeric_name(&result_.su.sa, result_.len);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = DNS_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSResolver::set_complete_cb(CompleteCb cb) {
|
||||||
|
completeCb_ = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteCb DNSResolver::get_complete_cb() const { return completeCb_; }
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_DNS_RESOLVER_H
|
||||||
|
#define SHRPX_DNS_RESOLVER_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
#include <ares.h>
|
||||||
|
|
||||||
|
#include "template.h"
|
||||||
|
#include "network.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
enum DNSResolverStatus {
|
||||||
|
// Resolver is in initial status
|
||||||
|
DNS_STATUS_IDLE,
|
||||||
|
// Resolver is currently resolving host name
|
||||||
|
DNS_STATUS_RUNNING,
|
||||||
|
// Resolver successfully resolved host name
|
||||||
|
DNS_STATUS_OK,
|
||||||
|
// Resolver failed to resolve host name
|
||||||
|
DNS_STATUS_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback function called when host name lookup is finished.
|
||||||
|
// |status| is either DNS_STATUS_OK, or DNS_STATUS_ERROR. If |status|
|
||||||
|
// is DNS_STATUS_OK, |result| points to the resolved address. Note
|
||||||
|
// that port portion of |result| is undefined, and must be initialized
|
||||||
|
// by application. This callback function is not called if name
|
||||||
|
// lookup finishes in DNSResolver::resolve() completely. In this
|
||||||
|
// case, application should call DNSResolver::get_status() to get
|
||||||
|
// current status and result. In other words, callback is called if
|
||||||
|
// get_status() returns DNS_STATUS_RUNNING.
|
||||||
|
using CompleteCb = std::function<void(int status, const Address *result)>;
|
||||||
|
|
||||||
|
// DNSResolver is asynchronous name resolver, backed by c-ares
|
||||||
|
// library.
|
||||||
|
class DNSResolver {
|
||||||
|
public:
|
||||||
|
DNSResolver(struct ev_loop *loop);
|
||||||
|
~DNSResolver();
|
||||||
|
|
||||||
|
// Starts resolving hostname |name|.
|
||||||
|
int resolve(const StringRef &name, int family);
|
||||||
|
// Returns status. If status_ is DNS_STATUS_SUCCESS && |result| is
|
||||||
|
// not nullptr, |*result| is filled.
|
||||||
|
int get_status(Address *result) const;
|
||||||
|
// Sets callback function when name lookup finishes. The callback
|
||||||
|
// function is called in a way that it can destroy this DNSResolver.
|
||||||
|
void set_complete_cb(CompleteCb cb);
|
||||||
|
CompleteCb get_complete_cb() const;
|
||||||
|
|
||||||
|
// Calls these functions when read/write event occurred respectively.
|
||||||
|
int on_read(int fd);
|
||||||
|
int on_write(int fd);
|
||||||
|
int on_timeout();
|
||||||
|
// Calls this function when DNS query finished.
|
||||||
|
void on_result(int staus, hostent *hostent);
|
||||||
|
void reset_timeout();
|
||||||
|
|
||||||
|
void start_rev(int fd);
|
||||||
|
void stop_rev(int fd);
|
||||||
|
void start_wev(int fd);
|
||||||
|
void stop_wev(int fd);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int handle_event(int rfd, int wfd);
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<ev_io>> revs_, wevs_;
|
||||||
|
Address result_;
|
||||||
|
CompleteCb completeCb_;
|
||||||
|
ev_timer timer_;
|
||||||
|
StringRef name_;
|
||||||
|
struct ev_loop *loop_;
|
||||||
|
// ares_channel is pointer type
|
||||||
|
ares_channel channel_;
|
||||||
|
// AF_INET or AF_INET6. AF_INET for A record lookup, and AF_INET6
|
||||||
|
// for AAAA record lookup.
|
||||||
|
int family_;
|
||||||
|
int status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_DNS_RESOLVER_H
|
|
@ -0,0 +1,325 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_dns_tracker.h"
|
||||||
|
#include "shrpx_config.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void gccb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto dns_tracker = static_cast<DNSTracker *>(w->data);
|
||||||
|
dns_tracker->gc();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
DNSTracker::DNSTracker(struct ev_loop *loop) : loop_(loop) {
|
||||||
|
ev_timer_init(&gc_timer_, gccb, 0., 12_h);
|
||||||
|
gc_timer_.data = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DNSTracker::~DNSTracker() {
|
||||||
|
ev_timer_stop(loop_, &gc_timer_);
|
||||||
|
|
||||||
|
for (auto &p : ents_) {
|
||||||
|
auto &qlist = p.second.qlist;
|
||||||
|
while (!qlist.empty()) {
|
||||||
|
auto head = qlist.head;
|
||||||
|
qlist.remove(head);
|
||||||
|
head->status = DNS_STATUS_ERROR;
|
||||||
|
head->in_qlist = false;
|
||||||
|
// TODO Not sure we should call callback here, or it is even be
|
||||||
|
// safe to do that.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResolverEntry DNSTracker::make_entry(std::unique_ptr<DualDNSResolver> resolv,
|
||||||
|
ImmutableString host, int status,
|
||||||
|
const Address *result) {
|
||||||
|
auto &dnsconf = get_config()->dns;
|
||||||
|
|
||||||
|
auto ent = ResolverEntry{};
|
||||||
|
ent.resolv = std::move(resolv);
|
||||||
|
ent.host = std::move(host);
|
||||||
|
ent.status = status;
|
||||||
|
switch (status) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
ent.expiry = ev_now(loop_) + dnsconf.timeout.cache;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
ent.result = *result;
|
||||||
|
}
|
||||||
|
return ent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSTracker::update_entry(ResolverEntry &ent,
|
||||||
|
std::unique_ptr<DualDNSResolver> resolv,
|
||||||
|
int status, const Address *result) {
|
||||||
|
auto &dnsconf = get_config()->dns;
|
||||||
|
|
||||||
|
ent.resolv = std::move(resolv);
|
||||||
|
ent.status = status;
|
||||||
|
switch (status) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
ent.expiry = ev_now(loop_) + dnsconf.timeout.cache;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
ent.result = *result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DNSTracker::resolve(Address *result, DNSQuery *dnsq) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
auto it = ents_.find(dnsq->host);
|
||||||
|
|
||||||
|
if (it == std::end(ents_)) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "DNS entry not found for " << dnsq->host;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resolv = make_unique<DualDNSResolver>(loop_);
|
||||||
|
auto host_copy =
|
||||||
|
ImmutableString{std::begin(dnsq->host), std::end(dnsq->host)};
|
||||||
|
auto host = StringRef{host_copy};
|
||||||
|
|
||||||
|
rv = resolv->resolve(host);
|
||||||
|
if (rv != 0) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup failed for " << host;
|
||||||
|
}
|
||||||
|
|
||||||
|
ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
|
||||||
|
DNS_STATUS_ERROR, nullptr));
|
||||||
|
|
||||||
|
start_gc_timer();
|
||||||
|
|
||||||
|
return DNS_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = resolv->get_status(result);
|
||||||
|
switch (rv) {
|
||||||
|
case DNS_STATUS_ERROR: {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup failed for " << host;
|
||||||
|
}
|
||||||
|
|
||||||
|
ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
|
||||||
|
DNS_STATUS_ERROR, nullptr));
|
||||||
|
|
||||||
|
start_gc_timer();
|
||||||
|
|
||||||
|
return DNS_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
case DNS_STATUS_OK: {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup succeeded: " << host << " -> "
|
||||||
|
<< util::numeric_name(&result->su.sa, result->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
ents_.emplace(host, make_entry(nullptr, std::move(host_copy),
|
||||||
|
DNS_STATUS_OK, result));
|
||||||
|
|
||||||
|
start_gc_timer();
|
||||||
|
|
||||||
|
return DNS_STATUS_OK;
|
||||||
|
}
|
||||||
|
case DNS_STATUS_RUNNING: {
|
||||||
|
assert(rv == DNS_STATUS_RUNNING);
|
||||||
|
|
||||||
|
auto p = ents_.emplace(host,
|
||||||
|
make_entry(std::move(resolv), std::move(host_copy),
|
||||||
|
DNS_STATUS_RUNNING, nullptr));
|
||||||
|
|
||||||
|
start_gc_timer();
|
||||||
|
|
||||||
|
auto &ent = (*p.first).second;
|
||||||
|
|
||||||
|
add_to_qlist(ent, dnsq);
|
||||||
|
|
||||||
|
return DNS_STATUS_RUNNING;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &ent = (*it).second;
|
||||||
|
|
||||||
|
if (ent.status != DNS_STATUS_RUNNING && ent.expiry < ev_now(loop_)) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "DNS entry found for " << dnsq->host
|
||||||
|
<< ", but it has been expired";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resolv = make_unique<DualDNSResolver>(loop_);
|
||||||
|
auto host = StringRef{ent.host};
|
||||||
|
|
||||||
|
rv = resolv->resolve(host);
|
||||||
|
if (rv != 0) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup failed for " << host;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_entry(ent, nullptr, DNS_STATUS_ERROR, nullptr);
|
||||||
|
|
||||||
|
return DNS_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = resolv->get_status(result);
|
||||||
|
switch (rv) {
|
||||||
|
case DNS_STATUS_ERROR: {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup failed for " << host;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_entry(ent, nullptr, DNS_STATUS_ERROR, nullptr);
|
||||||
|
|
||||||
|
return DNS_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
case DNS_STATUS_OK: {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup succeeded: " << host << " -> "
|
||||||
|
<< util::numeric_name(&result->su.sa, result->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_entry(ent, nullptr, DNS_STATUS_OK, result);
|
||||||
|
|
||||||
|
return DNS_STATUS_OK;
|
||||||
|
}
|
||||||
|
case DNS_STATUS_RUNNING: {
|
||||||
|
update_entry(ent, std::move(resolv), DNS_STATUS_RUNNING, nullptr);
|
||||||
|
add_to_qlist(ent, dnsq);
|
||||||
|
|
||||||
|
return DNS_STATUS_RUNNING;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ent.status) {
|
||||||
|
case DNS_STATUS_RUNNING:
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Waiting for name lookup complete for " << dnsq->host;
|
||||||
|
}
|
||||||
|
ent.qlist.append(dnsq);
|
||||||
|
dnsq->in_qlist = true;
|
||||||
|
return DNS_STATUS_RUNNING;
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup failed for " << dnsq->host << " (cached)";
|
||||||
|
}
|
||||||
|
return DNS_STATUS_ERROR;
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Name lookup succeeded (cached): " << dnsq->host << " -> "
|
||||||
|
<< util::numeric_name(&ent.result.su.sa, ent.result.len);
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
memcpy(result, &ent.result, sizeof(*result));
|
||||||
|
}
|
||||||
|
return DNS_STATUS_OK;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSTracker::add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq) {
|
||||||
|
auto loop = loop_;
|
||||||
|
ent.resolv->set_complete_cb([&ent, loop](int status, const Address *result) {
|
||||||
|
auto &qlist = ent.qlist;
|
||||||
|
while (!qlist.empty()) {
|
||||||
|
auto head = qlist.head;
|
||||||
|
qlist.remove(head);
|
||||||
|
head->status = status;
|
||||||
|
head->in_qlist = false;
|
||||||
|
auto cb = head->cb;
|
||||||
|
cb(status, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &dnsconf = get_config()->dns;
|
||||||
|
|
||||||
|
ent.resolv.reset();
|
||||||
|
ent.status = status;
|
||||||
|
ent.expiry = ev_now(loop) + dnsconf.timeout.cache;
|
||||||
|
if (ent.status == DNS_STATUS_OK) {
|
||||||
|
ent.result = *result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ent.qlist.append(dnsq);
|
||||||
|
dnsq->in_qlist = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSTracker::cancel(DNSQuery *dnsq) {
|
||||||
|
if (!dnsq->in_qlist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = ents_.find(dnsq->host);
|
||||||
|
if (it == std::end(ents_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &ent = (*it).second;
|
||||||
|
ent.qlist.remove(dnsq);
|
||||||
|
dnsq->in_qlist = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSTracker::start_gc_timer() {
|
||||||
|
if (ev_is_active(&gc_timer_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ev_timer_again(loop_, &gc_timer_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSTracker::gc() {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Starting removing expired DNS cache entries";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto now = ev_now(loop_);
|
||||||
|
for (auto it = std::begin(ents_); it != std::end(ents_);) {
|
||||||
|
auto &ent = (*it).second;
|
||||||
|
if (ent.expiry >= now) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
it = ents_.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ents_.empty()) {
|
||||||
|
ev_timer_stop(loop_, &gc_timer_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_DNS_TRACKER_H
|
||||||
|
#define SHRPX_DNS_TRACKER_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "shrpx_dual_dns_resolver.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
struct DNSQuery {
|
||||||
|
DNSQuery(StringRef host, CompleteCb cb)
|
||||||
|
: host(std::move(host)),
|
||||||
|
cb(std::move(cb)),
|
||||||
|
dlnext(nullptr),
|
||||||
|
dlprev(nullptr),
|
||||||
|
status(DNS_STATUS_IDLE),
|
||||||
|
in_qlist(false) {}
|
||||||
|
|
||||||
|
// Host name we lookup for.
|
||||||
|
StringRef host;
|
||||||
|
// Callback function called when name lookup finished. This
|
||||||
|
// callback is not called if name lookup finishes within
|
||||||
|
// DNSTracker::resolve().
|
||||||
|
CompleteCb cb;
|
||||||
|
DNSQuery *dlnext, *dlprev;
|
||||||
|
int status;
|
||||||
|
// true if this object is in linked list ResolverEntry::qlist.
|
||||||
|
bool in_qlist;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResolverEntry {
|
||||||
|
// Host name this entry lookups for.
|
||||||
|
ImmutableString host;
|
||||||
|
// DNS resolver. Only non-nullptr if status is DNS_STATUS_RUNNING.
|
||||||
|
std::unique_ptr<DualDNSResolver> resolv;
|
||||||
|
// DNSQuery interested in this name lookup result. The result is
|
||||||
|
// notified to them all.
|
||||||
|
DList<DNSQuery> qlist;
|
||||||
|
// Use the same enum with DNSResolverStatus
|
||||||
|
int status;
|
||||||
|
// result and its expiry time
|
||||||
|
Address result;
|
||||||
|
// time point when cached result expires.
|
||||||
|
ev_tstamp expiry;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DNSTracker {
|
||||||
|
public:
|
||||||
|
DNSTracker(struct ev_loop *loop);
|
||||||
|
~DNSTracker();
|
||||||
|
|
||||||
|
// Lookups host name described in |dnsq|. If name lookup finishes
|
||||||
|
// within this function (either it came from /etc/hosts, host name
|
||||||
|
// is numeric, lookup result is cached, etc), it returns
|
||||||
|
// DNS_STATUS_OK or DNS_STATUS_ERROR. If lookup is successful,
|
||||||
|
// DNS_STATUS_OK is returned, and |result| is filled. If lookup
|
||||||
|
// failed, DNS_STATUS_ERROR is returned. If name lookup is being
|
||||||
|
// done background, it returns DNS_STATUS_RUNNING. Its completion
|
||||||
|
// is notified by calling dnsq->cb.
|
||||||
|
int resolve(Address *result, DNSQuery *dnsq);
|
||||||
|
// Cancels name lookup requested by |dnsq|.
|
||||||
|
void cancel(DNSQuery *dnsq);
|
||||||
|
// Removes expired entries from ents_.
|
||||||
|
void gc();
|
||||||
|
// Starts GC timer.
|
||||||
|
void start_gc_timer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ResolverEntry make_entry(std::unique_ptr<DualDNSResolver> resolv,
|
||||||
|
ImmutableString host, int status,
|
||||||
|
const Address *result);
|
||||||
|
|
||||||
|
void update_entry(ResolverEntry &ent, std::unique_ptr<DualDNSResolver> resolv,
|
||||||
|
int status, const Address *result);
|
||||||
|
|
||||||
|
void add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq);
|
||||||
|
|
||||||
|
std::map<StringRef, ResolverEntry> ents_;
|
||||||
|
// Periodically iterates ents_, and removes expired entries to avoid
|
||||||
|
// excessive use of memory. Since only backend API can potentially
|
||||||
|
// increase memory consumption, interval could be very long.
|
||||||
|
ev_timer gc_timer_;
|
||||||
|
struct ev_loop *loop_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_DNS_TRACKER_H
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_dual_dns_resolver.h"
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
DualDNSResolver::DualDNSResolver(struct ev_loop *loop)
|
||||||
|
: resolv4_(loop), resolv6_(loop) {
|
||||||
|
auto cb = [this](int, const Address *) {
|
||||||
|
int rv;
|
||||||
|
Address result;
|
||||||
|
|
||||||
|
rv = this->get_status(&result);
|
||||||
|
switch (rv) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cb = this->get_complete_cb();
|
||||||
|
cb(rv, &result);
|
||||||
|
};
|
||||||
|
|
||||||
|
resolv4_.set_complete_cb(cb);
|
||||||
|
resolv6_.set_complete_cb(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DualDNSResolver::resolve(const StringRef &host) {
|
||||||
|
int rv4, rv6;
|
||||||
|
rv4 = resolv4_.resolve(host, AF_INET);
|
||||||
|
rv6 = resolv6_.resolve(host, AF_INET6);
|
||||||
|
|
||||||
|
if (rv4 != 0 && rv6 != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompleteCb DualDNSResolver::get_complete_cb() const { return complete_cb_; }
|
||||||
|
|
||||||
|
void DualDNSResolver::set_complete_cb(CompleteCb cb) { complete_cb_ = cb; }
|
||||||
|
|
||||||
|
int DualDNSResolver::get_status(Address *result) const {
|
||||||
|
int rv4, rv6;
|
||||||
|
rv6 = resolv6_.get_status(result);
|
||||||
|
if (rv6 == DNS_STATUS_OK) {
|
||||||
|
return DNS_STATUS_OK;
|
||||||
|
}
|
||||||
|
rv4 = resolv4_.get_status(result);
|
||||||
|
if (rv4 == DNS_STATUS_OK) {
|
||||||
|
return DNS_STATUS_OK;
|
||||||
|
}
|
||||||
|
if (rv4 == DNS_STATUS_RUNNING || rv6 == DNS_STATUS_RUNNING) {
|
||||||
|
return DNS_STATUS_RUNNING;
|
||||||
|
}
|
||||||
|
if (rv4 == DNS_STATUS_ERROR || rv6 == DNS_STATUS_ERROR) {
|
||||||
|
return DNS_STATUS_ERROR;
|
||||||
|
}
|
||||||
|
return DNS_STATUS_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_DUAL_DNS_RESOLVER_H
|
||||||
|
#define SHRPX_DUAL_DNS_RESOLVER_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
|
#include "shrpx_dns_resolver.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
// DualDNSResolver performs name resolution for both A and AAAA
|
||||||
|
// records at the same time. The first successful return (or if we
|
||||||
|
// have both successful results, prefer to AAAA) is chosen. This is
|
||||||
|
// wrapper around 2 DNSResolver inside. resolve(), get_status(), and
|
||||||
|
// how CompleteCb is called have the same semantics with DNSResolver.
|
||||||
|
class DualDNSResolver {
|
||||||
|
public:
|
||||||
|
DualDNSResolver(struct ev_loop *loop);
|
||||||
|
|
||||||
|
// Resolves |host|. |host| must be NULL-terminated string.
|
||||||
|
int resolve(const StringRef &host);
|
||||||
|
CompleteCb get_complete_cb() const;
|
||||||
|
void set_complete_cb(CompleteCb cb);
|
||||||
|
int get_status(Address *result) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// For A record
|
||||||
|
DNSResolver resolv4_;
|
||||||
|
// For AAAA record
|
||||||
|
DNSResolver resolv6_;
|
||||||
|
CompleteCb complete_cb_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_DUAL_DNS_RESOLVER_H
|
|
@ -95,7 +95,7 @@ void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
SSLOG(INFO, http2session) << "SETTINGS timeout";
|
SSLOG(INFO, http2session) << "SETTINGS timeout";
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream_failure(http2session->get_addr());
|
downstream_failure(http2session->get_addr(), http2session->get_raddr());
|
||||||
|
|
||||||
if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
|
if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
|
||||||
delete http2session;
|
delete http2session;
|
||||||
|
@ -202,6 +202,7 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
|
||||||
group_(group),
|
group_(group),
|
||||||
addr_(addr),
|
addr_(addr),
|
||||||
session_(nullptr),
|
session_(nullptr),
|
||||||
|
raddr_(nullptr),
|
||||||
state_(DISCONNECTED),
|
state_(DISCONNECTED),
|
||||||
connection_check_state_(CONNECTION_CHECK_NONE),
|
connection_check_state_(CONNECTION_CHECK_NONE),
|
||||||
freelist_zone_(FREELIST_ZONE_NONE) {
|
freelist_zone_(FREELIST_ZONE_NONE) {
|
||||||
|
@ -244,6 +245,11 @@ int Http2Session::disconnect(bool hard) {
|
||||||
|
|
||||||
wb_.reset();
|
wb_.reset();
|
||||||
|
|
||||||
|
if (dns_query_) {
|
||||||
|
auto dns_tracker = worker_->get_dns_tracker();
|
||||||
|
dns_tracker->cancel(dns_query_.get());
|
||||||
|
}
|
||||||
|
|
||||||
conn_.rlimit.stopw();
|
conn_.rlimit.stopw();
|
||||||
conn_.wlimit.stopw();
|
conn_.wlimit.stopw();
|
||||||
|
|
||||||
|
@ -302,12 +308,47 @@ int Http2Session::disconnect(bool hard) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Http2Session::resolve_name() {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
auto dns_query = make_unique<DNSQuery>(
|
||||||
|
addr_->host, [this](int status, const Address *result) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
if (status == DNS_STATUS_OK) {
|
||||||
|
*resolved_addr_ = *result;
|
||||||
|
util::set_port(*this->resolved_addr_, this->addr_->port);
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = this->initiate_connection();
|
||||||
|
if (rv != 0) {
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolved_addr_ = make_unique<Address>();
|
||||||
|
auto dns_tracker = worker_->get_dns_tracker();
|
||||||
|
rv = dns_tracker->resolve(resolved_addr_.get(), dns_query.get());
|
||||||
|
switch (rv) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
return -1;
|
||||||
|
case DNS_STATUS_RUNNING:
|
||||||
|
dns_query_ = std::move(dns_query);
|
||||||
|
state_ = RESOLVING_NAME;
|
||||||
|
return 0;
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
util::set_port(*resolved_addr_, addr_->port);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int Http2Session::initiate_connection() {
|
int Http2Session::initiate_connection() {
|
||||||
int rv = 0;
|
int rv = 0;
|
||||||
|
|
||||||
auto worker_blocker = worker_->get_connect_blocker();
|
auto worker_blocker = worker_->get_connect_blocker();
|
||||||
|
|
||||||
if (state_ == DISCONNECTED) {
|
if (state_ == DISCONNECTED || state_ == RESOLVING_NAME) {
|
||||||
if (worker_blocker->blocked()) {
|
if (worker_blocker->blocked()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
SSLOG(INFO, this)
|
SSLOG(INFO, this)
|
||||||
|
@ -350,6 +391,8 @@ int Http2Session::initiate_connection() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
raddr_ = &proxy.addr;
|
||||||
|
|
||||||
worker_blocker->on_success();
|
worker_blocker->on_success();
|
||||||
|
|
||||||
ev_io_set(&conn_.rev, conn_.fd, EV_READ);
|
ev_io_set(&conn_.rev, conn_.fd, EV_READ);
|
||||||
|
@ -374,36 +417,68 @@ int Http2Session::initiate_connection() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state_ == DISCONNECTED || state_ == PROXY_CONNECTED) {
|
if (state_ == DISCONNECTED || state_ == PROXY_CONNECTED ||
|
||||||
|
state_ == RESOLVING_NAME) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
SSLOG(INFO, this) << "Connecting to downstream server";
|
if (state_ != RESOLVING_NAME) {
|
||||||
|
SSLOG(INFO, this) << "Connecting to downstream server";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (addr_->tls) {
|
if (addr_->tls) {
|
||||||
assert(ssl_ctx_);
|
assert(ssl_ctx_);
|
||||||
|
|
||||||
auto ssl = ssl::create_ssl(ssl_ctx_);
|
if (state_ != RESOLVING_NAME) {
|
||||||
if (!ssl) {
|
auto ssl = ssl::create_ssl(ssl_ctx_);
|
||||||
return -1;
|
if (!ssl) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl::setup_downstream_http2_alpn(ssl);
|
||||||
|
|
||||||
|
conn_.set_ssl(ssl);
|
||||||
|
|
||||||
|
auto sni_name =
|
||||||
|
addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
|
||||||
|
|
||||||
|
if (!util::numeric_host(sni_name.c_str())) {
|
||||||
|
// TLS extensions: SNI. There is no documentation about the return
|
||||||
|
// code for this function (actually this is macro wrapping SSL_ctrl
|
||||||
|
// at the time of this writing).
|
||||||
|
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tls_session = ssl::reuse_tls_session(addr_->tls_session_cache);
|
||||||
|
if (tls_session) {
|
||||||
|
SSL_set_session(conn_.tls.ssl, tls_session);
|
||||||
|
SSL_SESSION_free(tls_session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ssl::setup_downstream_http2_alpn(ssl);
|
if (state_ == DISCONNECTED) {
|
||||||
|
if (addr_->dns) {
|
||||||
conn_.set_ssl(ssl);
|
rv = resolve_name();
|
||||||
|
if (rv != 0) {
|
||||||
auto sni_name =
|
downstream_failure(addr_, nullptr);
|
||||||
addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni};
|
return -1;
|
||||||
|
}
|
||||||
if (!util::numeric_host(sni_name.c_str())) {
|
if (state_ == RESOLVING_NAME) {
|
||||||
// TLS extensions: SNI. There is no documentation about the return
|
return 0;
|
||||||
// code for this function (actually this is macro wrapping SSL_ctrl
|
}
|
||||||
// at the time of this writing).
|
raddr_ = resolved_addr_.get();
|
||||||
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
|
} else {
|
||||||
|
raddr_ = &addr_->addr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tls_session = ssl::reuse_tls_session(addr_->tls_session_cache);
|
if (state_ == RESOLVING_NAME) {
|
||||||
if (tls_session) {
|
if (dns_query_->status == DNS_STATUS_ERROR) {
|
||||||
SSL_set_session(conn_.tls.ssl, tls_session);
|
downstream_failure(addr_, nullptr);
|
||||||
SSL_SESSION_free(tls_session);
|
return -1;
|
||||||
|
}
|
||||||
|
assert(dns_query_->status == DNS_STATUS_OK);
|
||||||
|
state_ = DISCONNECTED;
|
||||||
|
dns_query_.reset();
|
||||||
|
raddr_ = resolved_addr_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If state_ == PROXY_CONNECTED, we has connected to the proxy
|
// If state_ == PROXY_CONNECTED, we has connected to the proxy
|
||||||
|
@ -411,12 +486,11 @@ int Http2Session::initiate_connection() {
|
||||||
if (state_ == DISCONNECTED) {
|
if (state_ == DISCONNECTED) {
|
||||||
assert(conn_.fd == -1);
|
assert(conn_.fd == -1);
|
||||||
|
|
||||||
conn_.fd =
|
conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
|
||||||
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
|
|
||||||
if (conn_.fd == -1) {
|
if (conn_.fd == -1) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
SSLOG(WARN, this)
|
SSLOG(WARN, this)
|
||||||
<< "socket() failed; addr=" << util::to_numeric_addr(&addr_->addr)
|
<< "socket() failed; addr=" << util::to_numeric_addr(raddr_)
|
||||||
<< ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
|
|
||||||
worker_blocker->on_failure();
|
worker_blocker->on_failure();
|
||||||
|
@ -427,15 +501,14 @@ int Http2Session::initiate_connection() {
|
||||||
|
|
||||||
rv = connect(conn_.fd,
|
rv = connect(conn_.fd,
|
||||||
// TODO maybe not thread-safe?
|
// TODO maybe not thread-safe?
|
||||||
const_cast<sockaddr *>(&addr_->addr.su.sa),
|
const_cast<sockaddr *>(&raddr_->su.sa), raddr_->len);
|
||||||
addr_->addr.len);
|
|
||||||
if (rv != 0 && errno != EINPROGRESS) {
|
if (rv != 0 && errno != EINPROGRESS) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
SSLOG(WARN, this) << "connect() failed; addr="
|
SSLOG(WARN, this)
|
||||||
<< util::to_numeric_addr(&addr_->addr)
|
<< "connect() failed; addr=" << util::to_numeric_addr(raddr_)
|
||||||
<< ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
|
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,17 +518,44 @@ int Http2Session::initiate_connection() {
|
||||||
|
|
||||||
conn_.prepare_client_handshake();
|
conn_.prepare_client_handshake();
|
||||||
} else {
|
} else {
|
||||||
|
if (state_ == DISCONNECTED) {
|
||||||
|
// Without TLS and proxy.
|
||||||
|
if (addr_->dns) {
|
||||||
|
rv = resolve_name();
|
||||||
|
if (rv != 0) {
|
||||||
|
downstream_failure(addr_, nullptr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (state_ == RESOLVING_NAME) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
raddr_ = resolved_addr_.get();
|
||||||
|
} else {
|
||||||
|
raddr_ = &addr_->addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ == RESOLVING_NAME) {
|
||||||
|
if (dns_query_->status == DNS_STATUS_ERROR) {
|
||||||
|
downstream_failure(addr_, nullptr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
assert(dns_query_->status == DNS_STATUS_OK);
|
||||||
|
state_ = DISCONNECTED;
|
||||||
|
dns_query_.reset();
|
||||||
|
raddr_ = resolved_addr_.get();
|
||||||
|
}
|
||||||
|
|
||||||
if (state_ == DISCONNECTED) {
|
if (state_ == DISCONNECTED) {
|
||||||
// Without TLS and proxy.
|
// Without TLS and proxy.
|
||||||
assert(conn_.fd == -1);
|
assert(conn_.fd == -1);
|
||||||
|
|
||||||
conn_.fd =
|
conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
|
||||||
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
|
|
||||||
|
|
||||||
if (conn_.fd == -1) {
|
if (conn_.fd == -1) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
SSLOG(WARN, this)
|
SSLOG(WARN, this)
|
||||||
<< "socket() failed; addr=" << util::to_numeric_addr(&addr_->addr)
|
<< "socket() failed; addr=" << util::to_numeric_addr(raddr_)
|
||||||
<< ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
|
|
||||||
worker_blocker->on_failure();
|
worker_blocker->on_failure();
|
||||||
|
@ -464,15 +564,15 @@ int Http2Session::initiate_connection() {
|
||||||
|
|
||||||
worker_blocker->on_success();
|
worker_blocker->on_success();
|
||||||
|
|
||||||
rv = connect(conn_.fd, const_cast<sockaddr *>(&addr_->addr.su.sa),
|
rv = connect(conn_.fd, const_cast<sockaddr *>(&raddr_->su.sa),
|
||||||
addr_->addr.len);
|
raddr_->len);
|
||||||
if (rv != 0 && errno != EINPROGRESS) {
|
if (rv != 0 && errno != EINPROGRESS) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
SSLOG(WARN, this) << "connect() failed; addr="
|
SSLOG(WARN, this)
|
||||||
<< util::to_numeric_addr(&addr_->addr)
|
<< "connect() failed; addr=" << util::to_numeric_addr(raddr_)
|
||||||
<< ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
|
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1544,7 +1644,7 @@ int Http2Session::connection_made() {
|
||||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
|
||||||
if (!next_proto) {
|
if (!next_proto) {
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1553,7 +1653,7 @@ int Http2Session::connection_made() {
|
||||||
SSLOG(INFO, this) << "Negotiated next protocol: " << proto;
|
SSLOG(INFO, this) << "Negotiated next protocol: " << proto;
|
||||||
}
|
}
|
||||||
if (!util::check_h2_is_selected(proto)) {
|
if (!util::check_h2_is_selected(proto)) {
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1842,10 +1942,10 @@ int Http2Session::connected() {
|
||||||
auto sock_error = util::get_socket_error(conn_.fd);
|
auto sock_error = util::get_socket_error(conn_.fd);
|
||||||
if (sock_error != 0) {
|
if (sock_error != 0) {
|
||||||
SSLOG(WARN, this) << "Backend connect failed; addr="
|
SSLOG(WARN, this) << "Backend connect failed; addr="
|
||||||
<< util::to_numeric_addr(&addr_->addr)
|
<< util::to_numeric_addr(raddr_)
|
||||||
<< ": errno=" << sock_error;
|
<< ": errno=" << sock_error;
|
||||||
|
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -1955,7 +2055,7 @@ int Http2Session::tls_handshake() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv < 0) {
|
if (rv < 0) {
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -1965,8 +2065,8 @@ int Http2Session::tls_handshake() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!get_config()->tls.insecure &&
|
if (!get_config()->tls.insecure &&
|
||||||
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
|
ssl::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -1974,8 +2074,8 @@ int Http2Session::tls_handshake() {
|
||||||
if (!SSL_session_reused(conn_.tls.ssl)) {
|
if (!SSL_session_reused(conn_.tls.ssl)) {
|
||||||
auto tls_session = SSL_get0_session(conn_.tls.ssl);
|
auto tls_session = SSL_get0_session(conn_.tls.ssl);
|
||||||
if (tls_session) {
|
if (tls_session) {
|
||||||
ssl::try_cache_tls_session(addr_->tls_session_cache, addr_->addr,
|
ssl::try_cache_tls_session(addr_->tls_session_cache, *raddr_, tls_session,
|
||||||
tls_session, ev_now(conn_.loop));
|
ev_now(conn_.loop));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2262,9 +2362,9 @@ void Http2Session::on_timeout() {
|
||||||
}
|
}
|
||||||
case CONNECTING: {
|
case CONNECTING: {
|
||||||
SSLOG(WARN, this) << "Connect time out; addr="
|
SSLOG(WARN, this) << "Connect time out; addr="
|
||||||
<< util::to_numeric_addr(&addr_->addr);
|
<< util::to_numeric_addr(raddr_);
|
||||||
|
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2284,4 +2384,6 @@ void Http2Session::check_retire() {
|
||||||
signal_write();
|
signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Address *Http2Session::get_raddr() const { return raddr_; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -50,6 +50,7 @@ class Http2DownstreamConnection;
|
||||||
class Worker;
|
class Worker;
|
||||||
struct DownstreamAddrGroup;
|
struct DownstreamAddrGroup;
|
||||||
struct DownstreamAddr;
|
struct DownstreamAddr;
|
||||||
|
struct DNSQuery;
|
||||||
|
|
||||||
struct StreamData {
|
struct StreamData {
|
||||||
StreamData *dlnext, *dlprev;
|
StreamData *dlnext, *dlprev;
|
||||||
|
@ -81,6 +82,7 @@ public:
|
||||||
// associated ClientHandlers will be deleted.
|
// associated ClientHandlers will be deleted.
|
||||||
int disconnect(bool hard = false);
|
int disconnect(bool hard = false);
|
||||||
int initiate_connection();
|
int initiate_connection();
|
||||||
|
int resolve_name();
|
||||||
|
|
||||||
void add_downstream_connection(Http2DownstreamConnection *dconn);
|
void add_downstream_connection(Http2DownstreamConnection *dconn);
|
||||||
void remove_downstream_connection(Http2DownstreamConnection *dconn);
|
void remove_downstream_connection(Http2DownstreamConnection *dconn);
|
||||||
|
@ -203,6 +205,9 @@ public:
|
||||||
// shutdown the connection.
|
// shutdown the connection.
|
||||||
void check_retire();
|
void check_retire();
|
||||||
|
|
||||||
|
// Returns address used to connect to backend. Could be nullptr.
|
||||||
|
const Address *get_raddr() const;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
// Disconnected
|
// Disconnected
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
|
@ -218,6 +223,8 @@ public:
|
||||||
CONNECTED,
|
CONNECTED,
|
||||||
// Connection is started to fail
|
// Connection is started to fail
|
||||||
CONNECT_FAILING,
|
CONNECT_FAILING,
|
||||||
|
// Resolving host name
|
||||||
|
RESOLVING_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -259,6 +266,13 @@ private:
|
||||||
// Address of remote endpoint
|
// Address of remote endpoint
|
||||||
DownstreamAddr *addr_;
|
DownstreamAddr *addr_;
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
|
// Actual remote address used to contact backend. This is initially
|
||||||
|
// nullptr, and may point to either &addr_->addr,
|
||||||
|
// resolved_addr_.get(), or HTTP proxy's address structure.
|
||||||
|
const Address *raddr_;
|
||||||
|
// Resolved IP address if dns parameter is used
|
||||||
|
std::unique_ptr<Address> resolved_addr_;
|
||||||
|
std::unique_ptr<DNSQuery> dns_query_;
|
||||||
int state_;
|
int state_;
|
||||||
int connection_check_state_;
|
int connection_check_state_;
|
||||||
int freelist_zone_;
|
int freelist_zone_;
|
||||||
|
|
|
@ -75,11 +75,12 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
auto conn = static_cast<Connection *>(w->data);
|
auto conn = static_cast<Connection *>(w->data);
|
||||||
auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
|
auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
|
||||||
auto addr = dconn->get_addr();
|
auto addr = dconn->get_addr();
|
||||||
|
auto raddr = dconn->get_raddr();
|
||||||
|
|
||||||
DCLOG(WARN, dconn) << "Connect time out; addr="
|
DCLOG(WARN, dconn) << "Connect time out; addr="
|
||||||
<< util::to_numeric_addr(&addr->addr);
|
<< util::to_numeric_addr(raddr);
|
||||||
|
|
||||||
downstream_failure(addr);
|
downstream_failure(addr, raddr);
|
||||||
|
|
||||||
auto downstream = dconn->get_downstream();
|
auto downstream = dconn->get_downstream();
|
||||||
auto upstream = downstream->get_upstream();
|
auto upstream = downstream->get_upstream();
|
||||||
|
@ -182,13 +183,34 @@ HttpDownstreamConnection::~HttpDownstreamConnection() {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "Deleted";
|
DCLOG(INFO, this) << "Deleted";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dns_query_) {
|
||||||
|
auto dns_tracker = worker_->get_dns_tracker();
|
||||||
|
dns_tracker->cancel(dns_query_.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downstream_ = downstream;
|
||||||
|
|
||||||
|
rv = initiate_connection();
|
||||||
|
if (rv != 0) {
|
||||||
|
downstream_ = nullptr;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HttpDownstreamConnection::initiate_connection() {
|
||||||
|
int rv;
|
||||||
|
|
||||||
auto worker_blocker = worker_->get_connect_blocker();
|
auto worker_blocker = worker_->get_connect_blocker();
|
||||||
if (worker_blocker->blocked()) {
|
if (worker_blocker->blocked()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -212,42 +234,136 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
shared_addr->affinity == AFFINITY_NONE ? shared_addr->next : temp_idx;
|
shared_addr->affinity == AFFINITY_NONE ? shared_addr->next : temp_idx;
|
||||||
auto end = next_downstream;
|
auto end = next_downstream;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto &addr = addrs[next_downstream];
|
auto check_dns_result = dns_query_.get() != nullptr;
|
||||||
|
|
||||||
if (++next_downstream >= addrs.size()) {
|
DownstreamAddr *addr;
|
||||||
next_downstream = 0;
|
if (check_dns_result) {
|
||||||
}
|
addr = addr_;
|
||||||
|
addr_ = nullptr;
|
||||||
|
assert(addr);
|
||||||
|
assert(addr->dns);
|
||||||
|
} else {
|
||||||
|
assert(addr_ == nullptr);
|
||||||
|
addr = &addrs[next_downstream];
|
||||||
|
|
||||||
if (addr.proto != PROTO_HTTP1) {
|
if (++next_downstream >= addrs.size()) {
|
||||||
if (end == next_downstream) {
|
next_downstream = 0;
|
||||||
return SHRPX_ERR_NETWORK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
if (addr->proto != PROTO_HTTP1) {
|
||||||
|
if (end == next_downstream) {
|
||||||
|
return SHRPX_ERR_NETWORK;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &connect_blocker = addr.connect_blocker;
|
auto &connect_blocker = addr->connect_blocker;
|
||||||
|
|
||||||
if (connect_blocker->blocked()) {
|
if (connect_blocker->blocked()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "Backend server "
|
DCLOG(INFO, this) << "Backend server " << addr->host << ":"
|
||||||
<< util::to_numeric_addr(&addr.addr)
|
<< addr->port << " was not available temporarily";
|
||||||
<< " was not available temporarily";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end == next_downstream) {
|
if (check_dns_result) {
|
||||||
|
dns_query_.reset();
|
||||||
|
} else if (end == next_downstream) {
|
||||||
return SHRPX_ERR_NETWORK;
|
return SHRPX_ERR_NETWORK;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn_.fd = util::create_nonblock_socket(addr.addr.su.storage.ss_family);
|
Address *raddr;
|
||||||
|
|
||||||
|
if (addr->dns) {
|
||||||
|
if (!check_dns_result) {
|
||||||
|
auto dns_query = make_unique<DNSQuery>(
|
||||||
|
addr->host, [this](int status, const Address *result) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
if (status == DNS_STATUS_OK) {
|
||||||
|
*this->resolved_addr_ = *result;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = this->initiate_connection();
|
||||||
|
if (rv != 0) {
|
||||||
|
// This callback destroys |this|.
|
||||||
|
auto downstream = this->downstream_;
|
||||||
|
auto upstream = downstream->get_upstream();
|
||||||
|
auto handler = upstream->get_client_handler();
|
||||||
|
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
|
||||||
|
auto ndconn = handler->get_downstream_connection(downstream);
|
||||||
|
if (ndconn) {
|
||||||
|
if (downstream->attach_downstream_connection(
|
||||||
|
std::move(ndconn)) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downstream->set_request_state(Downstream::CONNECT_FAIL);
|
||||||
|
|
||||||
|
if (upstream->on_downstream_abort_request(downstream, 503) !=
|
||||||
|
0) {
|
||||||
|
delete handler;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto dns_tracker = worker_->get_dns_tracker();
|
||||||
|
|
||||||
|
if (!resolved_addr_) {
|
||||||
|
resolved_addr_ = make_unique<Address>();
|
||||||
|
}
|
||||||
|
rv = dns_tracker->resolve(resolved_addr_.get(), dns_query.get());
|
||||||
|
switch (rv) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
downstream_failure(addr, nullptr);
|
||||||
|
if (end == next_downstream) {
|
||||||
|
return SHRPX_ERR_NETWORK;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
case DNS_STATUS_RUNNING:
|
||||||
|
dns_query_ = std::move(dns_query);
|
||||||
|
// Remember current addr
|
||||||
|
addr_ = addr;
|
||||||
|
return 0;
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (dns_query_->status) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
dns_query_.reset();
|
||||||
|
downstream_failure(addr, nullptr);
|
||||||
|
continue;
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
dns_query_.reset();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raddr = resolved_addr_.get();
|
||||||
|
util::set_port(*resolved_addr_, addr->port);
|
||||||
|
} else {
|
||||||
|
raddr = &addr->addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_.fd = util::create_nonblock_socket(raddr->su.storage.ss_family);
|
||||||
|
|
||||||
if (conn_.fd == -1) {
|
if (conn_.fd == -1) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
DCLOG(WARN, this) << "socket() failed; addr="
|
DCLOG(WARN, this) << "socket() failed; addr="
|
||||||
<< util::to_numeric_addr(&addr.addr)
|
<< util::to_numeric_addr(raddr)
|
||||||
<< ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
|
|
||||||
worker_blocker->on_failure();
|
worker_blocker->on_failure();
|
||||||
|
@ -257,20 +373,19 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
|
|
||||||
worker_blocker->on_success();
|
worker_blocker->on_success();
|
||||||
|
|
||||||
int rv;
|
rv = connect(conn_.fd, &raddr->su.sa, raddr->len);
|
||||||
rv = connect(conn_.fd, &addr.addr.su.sa, addr.addr.len);
|
|
||||||
if (rv != 0 && errno != EINPROGRESS) {
|
if (rv != 0 && errno != EINPROGRESS) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
DCLOG(WARN, this) << "connect() failed; addr="
|
DCLOG(WARN, this) << "connect() failed; addr="
|
||||||
<< util::to_numeric_addr(&addr.addr)
|
<< util::to_numeric_addr(raddr)
|
||||||
<< ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
|
|
||||||
downstream_failure(&addr);
|
downstream_failure(addr, raddr);
|
||||||
|
|
||||||
close(conn_.fd);
|
close(conn_.fd);
|
||||||
conn_.fd = -1;
|
conn_.fd = -1;
|
||||||
|
|
||||||
if (end == next_downstream) {
|
if (!check_dns_result && end == next_downstream) {
|
||||||
return SHRPX_ERR_NETWORK;
|
return SHRPX_ERR_NETWORK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +397,8 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
DCLOG(INFO, this) << "Connecting to downstream server";
|
DCLOG(INFO, this) << "Connecting to downstream server";
|
||||||
}
|
}
|
||||||
|
|
||||||
addr_ = &addr;
|
addr_ = addr;
|
||||||
|
raddr_ = raddr;
|
||||||
|
|
||||||
if (addr_->tls) {
|
if (addr_->tls) {
|
||||||
assert(ssl_ctx_);
|
assert(ssl_ctx_);
|
||||||
|
@ -334,8 +450,6 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
ev_set_cb(&conn_.rev, readcb);
|
ev_set_cb(&conn_.rev, readcb);
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream_ = downstream;
|
|
||||||
|
|
||||||
http_parser_init(&response_htp_, HTTP_RESPONSE);
|
http_parser_init(&response_htp_, HTTP_RESPONSE);
|
||||||
response_htp_.data = downstream_;
|
response_htp_.data = downstream_;
|
||||||
|
|
||||||
|
@ -1031,7 +1145,7 @@ int HttpDownstreamConnection::tls_handshake() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv < 0) {
|
if (rv < 0) {
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
@ -1041,8 +1155,8 @@ int HttpDownstreamConnection::tls_handshake() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!get_config()->tls.insecure &&
|
if (!get_config()->tls.insecure &&
|
||||||
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
|
ssl::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -1050,7 +1164,7 @@ int HttpDownstreamConnection::tls_handshake() {
|
||||||
if (!SSL_session_reused(conn_.tls.ssl)) {
|
if (!SSL_session_reused(conn_.tls.ssl)) {
|
||||||
auto session = SSL_get0_session(conn_.tls.ssl);
|
auto session = SSL_get0_session(conn_.tls.ssl);
|
||||||
if (session) {
|
if (session) {
|
||||||
ssl::try_cache_tls_session(addr_->tls_session_cache, addr_->addr, session,
|
ssl::try_cache_tls_session(addr_->tls_session_cache, *raddr_, session,
|
||||||
ev_now(conn_.loop));
|
ev_now(conn_.loop));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1215,10 +1329,10 @@ int HttpDownstreamConnection::connected() {
|
||||||
conn_.wlimit.stopw();
|
conn_.wlimit.stopw();
|
||||||
|
|
||||||
DCLOG(WARN, this) << "Backend connect failed; addr="
|
DCLOG(WARN, this) << "Backend connect failed; addr="
|
||||||
<< util::to_numeric_addr(&addr_->addr)
|
<< util::to_numeric_addr(raddr_)
|
||||||
<< ": errno=" << sock_error;
|
<< ": errno=" << sock_error;
|
||||||
|
|
||||||
downstream_failure(addr_);
|
downstream_failure(addr_, raddr_);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -1282,4 +1396,6 @@ DownstreamAddr *HttpDownstreamConnection::get_addr() const { return addr_; }
|
||||||
|
|
||||||
bool HttpDownstreamConnection::poolable() const { return !group_->retired; }
|
bool HttpDownstreamConnection::poolable() const { return !group_->retired; }
|
||||||
|
|
||||||
|
const Address *HttpDownstreamConnection::get_raddr() const { return raddr_; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -39,6 +39,7 @@ class DownstreamConnectionPool;
|
||||||
class Worker;
|
class Worker;
|
||||||
struct DownstreamAddrGroup;
|
struct DownstreamAddrGroup;
|
||||||
struct DownstreamAddr;
|
struct DownstreamAddr;
|
||||||
|
struct DNSQuery;
|
||||||
|
|
||||||
class HttpDownstreamConnection : public DownstreamConnection {
|
class HttpDownstreamConnection : public DownstreamConnection {
|
||||||
public:
|
public:
|
||||||
|
@ -68,6 +69,8 @@ public:
|
||||||
get_downstream_addr_group() const;
|
get_downstream_addr_group() const;
|
||||||
virtual DownstreamAddr *get_addr() const;
|
virtual DownstreamAddr *get_addr() const;
|
||||||
|
|
||||||
|
int initiate_connection();
|
||||||
|
|
||||||
int read_clear();
|
int read_clear();
|
||||||
int write_clear();
|
int write_clear();
|
||||||
int read_tls();
|
int read_tls();
|
||||||
|
@ -80,6 +83,9 @@ public:
|
||||||
void signal_write();
|
void signal_write();
|
||||||
int actual_signal_write();
|
int actual_signal_write();
|
||||||
|
|
||||||
|
// Returns address used to connect to backend. Could be nullptr.
|
||||||
|
const Address *get_raddr() const;
|
||||||
|
|
||||||
int noop();
|
int noop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -92,6 +98,13 @@ private:
|
||||||
std::shared_ptr<DownstreamAddrGroup> group_;
|
std::shared_ptr<DownstreamAddrGroup> group_;
|
||||||
// Address of remote endpoint
|
// Address of remote endpoint
|
||||||
DownstreamAddr *addr_;
|
DownstreamAddr *addr_;
|
||||||
|
// Actual remote address used to contact backend. This is initially
|
||||||
|
// nullptr, and may point to either &addr_->addr, or
|
||||||
|
// resolved_addr_.get().
|
||||||
|
const Address *raddr_;
|
||||||
|
// Resolved IP address if dns parameter is used
|
||||||
|
std::unique_ptr<Address> resolved_addr_;
|
||||||
|
std::unique_ptr<DNSQuery> dns_query_;
|
||||||
IOControl ioctrl_;
|
IOControl ioctrl_;
|
||||||
http_parser response_htp_;
|
http_parser response_htp_;
|
||||||
ssize_t initial_addr_idx_;
|
ssize_t initial_addr_idx_;
|
||||||
|
|
|
@ -114,6 +114,7 @@ LiveCheck::LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
|
||||||
ssl_ctx_(ssl_ctx),
|
ssl_ctx_(ssl_ctx),
|
||||||
addr_(addr),
|
addr_(addr),
|
||||||
session_(nullptr),
|
session_(nullptr),
|
||||||
|
raddr_(nullptr),
|
||||||
success_count_(0),
|
success_count_(0),
|
||||||
fail_count_(0),
|
fail_count_(0),
|
||||||
settings_ack_received_(false),
|
settings_ack_received_(false),
|
||||||
|
@ -134,6 +135,16 @@ LiveCheck::~LiveCheck() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LiveCheck::disconnect() {
|
void LiveCheck::disconnect() {
|
||||||
|
if (dns_query_) {
|
||||||
|
auto dns_tracker = worker_->get_dns_tracker();
|
||||||
|
|
||||||
|
dns_tracker->cancel(dns_query_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
dns_query_.reset();
|
||||||
|
// We can reuse resolved_addr_
|
||||||
|
raddr_ = nullptr;
|
||||||
|
|
||||||
conn_.rlimit.stopw();
|
conn_.rlimit.stopw();
|
||||||
conn_.wlimit.stopw();
|
conn_.wlimit.stopw();
|
||||||
|
|
||||||
|
@ -190,7 +201,7 @@ int LiveCheck::initiate_connection() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr_->tls) {
|
if (!dns_query_ && addr_->tls) {
|
||||||
assert(ssl_ctx_);
|
assert(ssl_ctx_);
|
||||||
|
|
||||||
auto ssl = ssl::create_ssl(ssl_ctx_);
|
auto ssl = ssl::create_ssl(ssl_ctx_);
|
||||||
|
@ -212,20 +223,71 @@ int LiveCheck::initiate_connection() {
|
||||||
conn_.set_ssl(ssl);
|
conn_.set_ssl(ssl);
|
||||||
}
|
}
|
||||||
|
|
||||||
conn_.fd = util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
|
if (addr_->dns) {
|
||||||
|
if (!dns_query_) {
|
||||||
|
auto dns_query = make_unique<DNSQuery>(
|
||||||
|
addr_->host, [this](int status, const Address *result) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
if (status == DNS_STATUS_OK) {
|
||||||
|
*this->resolved_addr_ = *result;
|
||||||
|
}
|
||||||
|
rv = this->initiate_connection();
|
||||||
|
if (rv != 0) {
|
||||||
|
this->on_failure();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
auto dns_tracker = worker_->get_dns_tracker();
|
||||||
|
|
||||||
|
if (!resolved_addr_) {
|
||||||
|
resolved_addr_ = make_unique<Address>();
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = dns_tracker->resolve(resolved_addr_.get(), dns_query.get());
|
||||||
|
switch (rv) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
return -1;
|
||||||
|
case DNS_STATUS_RUNNING:
|
||||||
|
dns_query_ = std::move(dns_query);
|
||||||
|
return 0;
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (dns_query_->status) {
|
||||||
|
case DNS_STATUS_ERROR:
|
||||||
|
dns_query_.reset();
|
||||||
|
return -1;
|
||||||
|
case DNS_STATUS_OK:
|
||||||
|
dns_query_.reset();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util::set_port(*resolved_addr_, addr_->port);
|
||||||
|
raddr_ = resolved_addr_.get();
|
||||||
|
} else {
|
||||||
|
raddr_ = &addr_->addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
|
||||||
|
|
||||||
if (conn_.fd == -1) {
|
if (conn_.fd == -1) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
LOG(WARN) << "socket() failed; addr=" << util::to_numeric_addr(&addr_->addr)
|
LOG(WARN) << "socket() failed; addr=" << util::to_numeric_addr(raddr_)
|
||||||
<< ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = connect(conn_.fd, &addr_->addr.su.sa, addr_->addr.len);
|
rv = connect(conn_.fd, &raddr_->su.sa, raddr_->len);
|
||||||
if (rv != 0 && errno != EINPROGRESS) {
|
if (rv != 0 && errno != EINPROGRESS) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
LOG(WARN) << "connect() failed; addr="
|
LOG(WARN) << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
|
||||||
<< util::to_numeric_addr(&addr_->addr) << ", errno=" << error;
|
<< ", errno=" << error;
|
||||||
|
|
||||||
close(conn_.fd);
|
close(conn_.fd);
|
||||||
conn_.fd = -1;
|
conn_.fd = -1;
|
||||||
|
@ -269,8 +331,7 @@ int LiveCheck::connected() {
|
||||||
if (sock_error != 0) {
|
if (sock_error != 0) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Backend connect failed; addr="
|
LOG(INFO) << "Backend connect failed; addr="
|
||||||
<< util::to_numeric_addr(&addr_->addr)
|
<< util::to_numeric_addr(raddr_) << ": errno=" << sock_error;
|
||||||
<< ": errno=" << sock_error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -334,15 +395,15 @@ int LiveCheck::tls_handshake() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!get_config()->tls.insecure &&
|
if (!get_config()->tls.insecure &&
|
||||||
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
|
ssl::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SSL_session_reused(conn_.tls.ssl)) {
|
if (!SSL_session_reused(conn_.tls.ssl)) {
|
||||||
auto tls_session = SSL_get0_session(conn_.tls.ssl);
|
auto tls_session = SSL_get0_session(conn_.tls.ssl);
|
||||||
if (tls_session) {
|
if (tls_session) {
|
||||||
ssl::try_cache_tls_session(addr_->tls_session_cache, addr_->addr,
|
ssl::try_cache_tls_session(addr_->tls_session_cache, *raddr_, tls_session,
|
||||||
tls_session, ev_now(conn_.loop));
|
ev_now(conn_.loop));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,7 +662,7 @@ void LiveCheck::on_failure() {
|
||||||
++fail_count_;
|
++fail_count_;
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Liveness check for " << util::to_numeric_addr(&addr_->addr)
|
LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port
|
||||||
<< " failed " << fail_count_ << " time(s) in a row";
|
<< " failed " << fail_count_ << " time(s) in a row";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,7 +676,7 @@ void LiveCheck::on_success() {
|
||||||
fail_count_ = 0;
|
fail_count_ = 0;
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Liveness check for " << util::to_numeric_addr(&addr_->addr)
|
LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port
|
||||||
<< " succeeded " << success_count_ << " time(s) in a row";
|
<< " succeeded " << success_count_ << " time(s) in a row";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ namespace shrpx {
|
||||||
|
|
||||||
class Worker;
|
class Worker;
|
||||||
struct DownstreamAddr;
|
struct DownstreamAddr;
|
||||||
|
struct DNSQuery;
|
||||||
|
|
||||||
class LiveCheck {
|
class LiveCheck {
|
||||||
public:
|
public:
|
||||||
|
@ -102,6 +103,13 @@ private:
|
||||||
// Address of remote endpoint
|
// Address of remote endpoint
|
||||||
DownstreamAddr *addr_;
|
DownstreamAddr *addr_;
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
|
// Actual remote address used to contact backend. This is initially
|
||||||
|
// nullptr, and may point to either &addr_->addr, or
|
||||||
|
// resolved_addr_.get().
|
||||||
|
const Address *raddr_;
|
||||||
|
// Resolved IP address if dns parameter is used
|
||||||
|
std::unique_ptr<Address> resolved_addr_;
|
||||||
|
std::unique_ptr<DNSQuery> dns_query_;
|
||||||
// The number of successful connect attempt in a row.
|
// The number of successful connect attempt in a row.
|
||||||
size_t success_count_;
|
size_t success_count_;
|
||||||
// The number of unsuccessful connect attempt in a row.
|
// The number of unsuccessful connect attempt in a row.
|
||||||
|
|
|
@ -1171,10 +1171,10 @@ int check_cert(SSL *ssl, const Address *addr, const StringRef &host) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int check_cert(SSL *ssl, const DownstreamAddr *addr) {
|
int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr) {
|
||||||
auto hostname =
|
auto hostname =
|
||||||
addr->sni.empty() ? StringRef{addr->host} : StringRef{addr->sni};
|
addr->sni.empty() ? StringRef{addr->host} : StringRef{addr->sni};
|
||||||
return check_cert(ssl, &addr->addr, hostname);
|
return check_cert(ssl, raddr, hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
CertLookupTree::CertLookupTree() {}
|
CertLookupTree::CertLookupTree() {}
|
||||||
|
|
|
@ -104,7 +104,10 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
|
||||||
|
|
||||||
// Check peer's certificate against given |address| and |host|.
|
// Check peer's certificate against given |address| and |host|.
|
||||||
int check_cert(SSL *ssl, const Address *addr, const StringRef &host);
|
int check_cert(SSL *ssl, const Address *addr, const StringRef &host);
|
||||||
int check_cert(SSL *ssl, const DownstreamAddr *addr);
|
// Check peer's certificate against given host name described in
|
||||||
|
// |addr| and numeric address in |raddr|. Note that |raddr| might not
|
||||||
|
// point to &addr->addr.
|
||||||
|
int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr);
|
||||||
|
|
||||||
struct WildcardRevPrefix {
|
struct WildcardRevPrefix {
|
||||||
WildcardRevPrefix(const StringRef &prefix, size_t idx)
|
WildcardRevPrefix(const StringRef &prefix, size_t idx)
|
||||||
|
|
|
@ -92,7 +92,7 @@ bool match_shared_downstream_addr(
|
||||||
auto &b = rhs->addrs[i];
|
auto &b = rhs->addrs[i];
|
||||||
if (a.host == b.host && a.port == b.port && a.host_unix == b.host_unix &&
|
if (a.host == b.host && a.port == b.port && a.host_unix == b.host_unix &&
|
||||||
a.proto == b.proto && a.tls == b.tls && a.sni == b.sni &&
|
a.proto == b.proto && a.tls == b.tls && a.sni == b.sni &&
|
||||||
a.fall == b.fall && a.rise == b.rise) {
|
a.fall == b.fall && a.rise == b.rise && a.dns == b.dns) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
|
||||||
std::shared_ptr<DownstreamConfig> downstreamconf)
|
std::shared_ptr<DownstreamConfig> downstreamconf)
|
||||||
: randgen_(rd()),
|
: randgen_(rd()),
|
||||||
worker_stat_{},
|
worker_stat_{},
|
||||||
|
dns_tracker_(loop),
|
||||||
loop_(loop),
|
loop_(loop),
|
||||||
sv_ssl_ctx_(sv_ssl_ctx),
|
sv_ssl_ctx_(sv_ssl_ctx),
|
||||||
cl_ssl_ctx_(cl_ssl_ctx),
|
cl_ssl_ctx_(cl_ssl_ctx),
|
||||||
|
@ -209,6 +210,7 @@ void Worker::replace_downstream_config(
|
||||||
dst_addr.sni = make_string_ref(shared_addr->balloc, src_addr.sni);
|
dst_addr.sni = make_string_ref(shared_addr->balloc, src_addr.sni);
|
||||||
dst_addr.fall = src_addr.fall;
|
dst_addr.fall = src_addr.fall;
|
||||||
dst_addr.rise = src_addr.rise;
|
dst_addr.rise = src_addr.rise;
|
||||||
|
dst_addr.dns = src_addr.dns;
|
||||||
|
|
||||||
auto shared_addr_ptr = shared_addr.get();
|
auto shared_addr_ptr = shared_addr.get();
|
||||||
|
|
||||||
|
@ -490,6 +492,8 @@ ConnectionHandler *Worker::get_connection_handler() const {
|
||||||
return conn_handler_;
|
return conn_handler_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; }
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
size_t match_downstream_addr_group_host(
|
size_t match_downstream_addr_group_host(
|
||||||
const RouterConfig &routerconf, const StringRef &host,
|
const RouterConfig &routerconf, const StringRef &host,
|
||||||
|
@ -635,7 +639,7 @@ size_t match_downstream_addr_group(
|
||||||
catch_all, balloc);
|
catch_all, balloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void downstream_failure(DownstreamAddr *addr) {
|
void downstream_failure(DownstreamAddr *addr, const Address *raddr) {
|
||||||
const auto &connect_blocker = addr->connect_blocker;
|
const auto &connect_blocker = addr->connect_blocker;
|
||||||
|
|
||||||
if (connect_blocker->in_offline()) {
|
if (connect_blocker->in_offline()) {
|
||||||
|
@ -651,8 +655,15 @@ void downstream_failure(DownstreamAddr *addr) {
|
||||||
auto fail_count = connect_blocker->get_fail_count();
|
auto fail_count = connect_blocker->get_fail_count();
|
||||||
|
|
||||||
if (fail_count >= addr->fall) {
|
if (fail_count >= addr->fall) {
|
||||||
LOG(WARN) << "Could not connect to " << util::to_numeric_addr(&addr->addr)
|
if (raddr) {
|
||||||
<< " " << fail_count << " times in a row; considered as offline";
|
LOG(WARN) << "Could not connect to " << util::to_numeric_addr(raddr)
|
||||||
|
<< " " << fail_count
|
||||||
|
<< " times in a row; considered as offline";
|
||||||
|
} else {
|
||||||
|
LOG(WARN) << "Could not connect to " << addr->host << ":" << addr->port
|
||||||
|
<< " " << fail_count
|
||||||
|
<< " times in a row; considered as offline";
|
||||||
|
}
|
||||||
|
|
||||||
connect_blocker->offline();
|
connect_blocker->offline();
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
#include "shrpx_ssl.h"
|
#include "shrpx_ssl.h"
|
||||||
#include "shrpx_live_check.h"
|
#include "shrpx_live_check.h"
|
||||||
#include "shrpx_connect_blocker.h"
|
#include "shrpx_connect_blocker.h"
|
||||||
|
#include "shrpx_dns_tracker.h"
|
||||||
#include "allocator.h"
|
#include "allocator.h"
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
@ -112,6 +113,8 @@ struct DownstreamAddr {
|
||||||
shrpx_proto proto;
|
shrpx_proto proto;
|
||||||
// true if TLS is used in this backend
|
// true if TLS is used in this backend
|
||||||
bool tls;
|
bool tls;
|
||||||
|
// true if dynamic DNS is enabled
|
||||||
|
bool dns;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simplified weighted fair queuing. Actually we don't use queue here
|
// Simplified weighted fair queuing. Actually we don't use queue here
|
||||||
|
@ -263,6 +266,8 @@ public:
|
||||||
|
|
||||||
ConnectionHandler *get_connection_handler() const;
|
ConnectionHandler *get_connection_handler() const;
|
||||||
|
|
||||||
|
DNSTracker *get_dns_tracker();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifndef NOTHREADS
|
#ifndef NOTHREADS
|
||||||
std::future<void> fut_;
|
std::future<void> fut_;
|
||||||
|
@ -275,6 +280,7 @@ private:
|
||||||
ev_timer proc_wev_timer_;
|
ev_timer proc_wev_timer_;
|
||||||
MemchunkPool mcpool_;
|
MemchunkPool mcpool_;
|
||||||
WorkerStat worker_stat_;
|
WorkerStat worker_stat_;
|
||||||
|
DNSTracker dns_tracker_;
|
||||||
|
|
||||||
std::shared_ptr<DownstreamConfig> downstreamconf_;
|
std::shared_ptr<DownstreamConfig> downstreamconf_;
|
||||||
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
|
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
|
||||||
|
@ -314,7 +320,10 @@ size_t match_downstream_addr_group(
|
||||||
const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
|
const std::vector<std::shared_ptr<DownstreamAddrGroup>> &groups,
|
||||||
size_t catch_all, BlockAllocator &balloc);
|
size_t catch_all, BlockAllocator &balloc);
|
||||||
|
|
||||||
void downstream_failure(DownstreamAddr *addr);
|
// Calls this function if connecting to backend failed. |raddr| is
|
||||||
|
// the actual address used to connect to backend, and it could be
|
||||||
|
// nullptr. This function may schedule live check.
|
||||||
|
void downstream_failure(DownstreamAddr *addr, const Address *raddr);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,8 @@
|
||||||
|
|
||||||
#include <ev.h>
|
#include <ev.h>
|
||||||
|
|
||||||
|
#include <ares.h>
|
||||||
|
|
||||||
#include "shrpx_config.h"
|
#include "shrpx_config.h"
|
||||||
#include "shrpx_connection_handler.h"
|
#include "shrpx_connection_handler.h"
|
||||||
#include "shrpx_log_config.h"
|
#include "shrpx_log_config.h"
|
||||||
|
@ -392,6 +394,7 @@ std::random_device rd;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int worker_process_event_loop(WorkerProcessConfig *wpconf) {
|
int worker_process_event_loop(WorkerProcessConfig *wpconf) {
|
||||||
|
int rv;
|
||||||
std::array<char, STRERROR_BUFSIZE> errbuf;
|
std::array<char, STRERROR_BUFSIZE> errbuf;
|
||||||
(void)errbuf;
|
(void)errbuf;
|
||||||
|
|
||||||
|
@ -400,6 +403,12 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rv = ares_library_init(ARES_LIB_INIT_ALL);
|
||||||
|
if (rv != 0) {
|
||||||
|
LOG(FATAL) << "ares_library_init failed: " << ares_strerror(rv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
auto loop = EV_DEFAULT;
|
auto loop = EV_DEFAULT;
|
||||||
|
|
||||||
auto gen = std::mt19937(rd());
|
auto gen = std::mt19937(rd());
|
||||||
|
@ -494,8 +503,6 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int rv;
|
|
||||||
|
|
||||||
if (config->num_worker == 1) {
|
if (config->num_worker == 1) {
|
||||||
rv = conn_handler.create_single_worker();
|
rv = conn_handler.create_single_worker();
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
|
@ -575,6 +582,8 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
|
||||||
}
|
}
|
||||||
#endif // HAVE_NEVERBLEED
|
#endif // HAVE_NEVERBLEED
|
||||||
|
|
||||||
|
ares_library_cleanup();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/util.cc
11
src/util.cc
|
@ -666,6 +666,17 @@ std::string to_numeric_addr(const Address *addr) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_port(Address &addr, uint16_t port) {
|
||||||
|
switch (addr.su.storage.ss_family) {
|
||||||
|
case AF_INET:
|
||||||
|
addr.su.in.sin_port = htons(port);
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
addr.su.in6.sin6_port = htons(port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int STDERR_COPY = -1;
|
static int STDERR_COPY = -1;
|
||||||
static int STDOUT_COPY = -1;
|
static int STDOUT_COPY = -1;
|
||||||
|
|
||||||
|
|
|
@ -478,6 +478,9 @@ std::string numeric_name(const struct sockaddr *sa, socklen_t salen);
|
||||||
// IPv6 address, address is enclosed by square brackets ([]).
|
// IPv6 address, address is enclosed by square brackets ([]).
|
||||||
std::string to_numeric_addr(const Address *addr);
|
std::string to_numeric_addr(const Address *addr);
|
||||||
|
|
||||||
|
// Sets |port| to |addr|.
|
||||||
|
void set_port(Address &addr, uint16_t port);
|
||||||
|
|
||||||
// Makes internal copy of stderr (and possibly stdout in the future),
|
// Makes internal copy of stderr (and possibly stdout in the future),
|
||||||
// which is then used as pointer to /dev/stderr or /proc/self/fd/2
|
// which is then used as pointer to /dev/stderr or /proc/self/fd/2
|
||||||
void store_original_fds();
|
void store_original_fds();
|
||||||
|
|
Loading…
Reference in New Issue