Merge pull request #740 from nghttp2/backend-dns

nghttpx: Dynamic DNS
This commit is contained in:
Tatsuhiro Tsujikawa 2016-12-11 16:48:28 +09:00 committed by GitHub
commit 7398e57174
34 changed files with 1858 additions and 136 deletions

View File

@ -28,6 +28,7 @@ addons:
- libevent-dev
- libjansson-dev
- libjemalloc-dev
- libc-ares-dev
- cmake
- cmake-data
before_install:

View File

@ -59,6 +59,7 @@ find_package(PythonInterp)
# Auto-detection of features that can be toggled
find_package(OpenSSL 1.0.1)
find_package(Libev 4.11)
find_package(Libcares 1.7.5)
find_package(ZLIB 1.2.3)
if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND)
set(ENABLE_APP_DEFAULT ON)
@ -207,6 +208,14 @@ if(LIBEVENT_FOUND)
# Must both link the core and openssl libraries.
set(LIBEVENT_OPENSSL_LIBRARIES ${LIBEVENT_LIBRARIES})
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)
set(HAVE_JANSSON ${JANSSON_FOUND})
# libxml2 (for src/nghttp)
@ -499,6 +508,7 @@ message(STATUS "summary of build options:
OpenSSL: ${HAVE_OPENSSL} (LIBS='${OPENSSL_LIBRARIES}')
Libxml2: ${HAVE_LIBXML2} (LIBS='${LIBXML2_LIBRARIES}')
Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}')
Libc-ares: ${HAVE_LIBCARES} (LIBS='${LIBCARES_LIBRARIES}')
Libevent(SSL): ${HAVE_LIBEVENT_OPENSSL} (LIBS='${LIBEVENT_OPENSSL_LIBRARIES}')
Spdylay: ${HAVE_SPDYLAY} (LIBS='${SPDYLAY_LIBRARIES}')
Jansson: ${HAVE_JANSSON} (LIBS='${JANSSON_LIBRARIES}')

View File

@ -45,7 +45,8 @@ EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-make \
cmake/Version.cmake \
cmake/FindCython.cmake \
cmake/FindLibevent.cmake \
cmake/FindJansson.cmake
cmake/FindJansson.cmake \
cmake/FindLibcares.cmake
.PHONY: clang-format

View File

@ -70,6 +70,7 @@ are required:
* OpenSSL >= 1.0.1
* libev >= 4.11
* zlib >= 1.2.3
* libc-ares >= 1.7.5
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
@ -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 \
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
`libspdylay-dev`. For the earlier Ubuntu release, you need to build

40
cmake/FindLibcares.cmake Normal file
View File

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

View File

@ -370,6 +370,13 @@ if test "x${have_openssl}" = "xno"; then
AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
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)
# 2.0.8 is required because we use evconnlistener_set_error_cb()
PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8],
@ -479,13 +486,14 @@ if test "x${request_asio_lib}" = "xyes"; then
fi
fi
# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL
# and libev
# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL,
# libev, and libc-ares.
enable_app=no
if test "x${request_app}" != "xno" &&
test "x${have_zlib}" = "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
fi
@ -640,6 +648,26 @@ AC_SYS_LARGEFILE
AC_CHECK_MEMBER([struct tm.tm_gmtoff], [have_struct_tm_tm_gmtoff=yes],
[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
AC_DEFINE([HAVE_STRUCT_TM_TM_GMTOFF], [1],
[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}')
Libxml2: ${have_libxml2} (CFLAGS='${XML_CPPFLAGS}' LIBS='${XML_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}')
Spdylay: ${have_spdylay} (CFLAGS='${LIBSPDYLAY_CFLAGS}' LIBS='${LIBSPDYLAY_LIBS}')
Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')

View File

@ -1767,10 +1767,10 @@ The replacement is done instantly without breaking existing
connections or requests. It also avoids any process creation as is
the case with hot swapping with signals.
The one limitation is that only numeric IP address is allowd in
:option:`backend <--backend>` in request body while non numeric
hostname is allowed in command-line or configuration file is read
using :option:`--conf`.
The one limitation is that only numeric IP address is allowed in
:option:`backend <--backend>` in request body unless "dns" parameter
is missing while non numeric hostname is allowed in command-line or
configuration file is read using :option:`--conf`.
SEE ALSO
--------

View File

@ -149,6 +149,9 @@ OPTIONS = [
"ecdh-curves",
"tls-sct-dir",
"backend-connect-timeout",
"dns-cache-timeout",
"dns-lookup-timeout",
"dns-max-try",
]
LOGVARS = [

View File

@ -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.
func TestH2H1GracefulShutdown(t *testing.T) {
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
// for successful case.
func TestH2APIBackendconfig(t *testing.T) {

View File

@ -101,10 +101,17 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
args := []string{}
backendTLS := false
dns := false
externalDNS := false
for _, k := range src_args {
switch k {
case "--http2-bridge":
backendTLS = true
case "--dns":
dns = true
case "--external-dns":
dns = true
externalDNS = true
default:
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.NextProtos = append(ts.TLS.NextProtos, "h2")
ts.StartTLS()
args = append(args, "-k", "--backend-tls")
args = append(args, "-k")
} else {
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
// "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 {
b += ";;proto=h2;tls"
b += ";proto=h2;tls"
}
if dns {
b += ";dns"
}
noTLS := "no-tls"

View File

@ -19,6 +19,7 @@ include_directories(
${LIBXML2_INCLUDE_DIRS}
${LIBEV_INCLUDE_DIRS}
${OPENSSL_INCLUDE_DIRS}
${LIBCARES_INCLUDE_DIRS}
${JANSSON_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS}
)
@ -31,6 +32,7 @@ link_libraries(
${LIBXML2_LIBRARIES}
${LIBEV_LIBRARIES}
${OPENSSL_LIBRARIES}
${LIBCARES_LIBRARIES}
${JANSSON_LIBRARIES}
${ZLIB_LIBRARIES}
${APP_LIBRARIES}
@ -112,6 +114,9 @@ if(ENABLE_APP)
shrpx_api_downstream_connection.cc
shrpx_health_monitor_downstream_connection.cc
shrpx_exec.cc
shrpx_dns_resolver.cc
shrpx_dual_dns_resolver.cc
shrpx_dns_tracker.cc
xsi_strerror.c
)
if(HAVE_SPDYLAY)

View File

@ -44,6 +44,7 @@ AM_CPPFLAGS = \
@XML_CPPFLAGS@ \
@LIBEV_CFLAGS@ \
@OPENSSL_CFLAGS@ \
@LIBCARES_CFLAGS@ \
@JANSSON_CFLAGS@ \
@ZLIB_CFLAGS@ \
@DEFS@
@ -55,6 +56,7 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \
@XML_LIBS@ \
@LIBEV_LIBS@ \
@OPENSSL_LIBS@ \
@LIBCARES_LIBS@ \
@JANSSON_LIBS@ \
@ZLIB_LIBS@ \
@APPLDFLAGS@
@ -139,6 +141,9 @@ NGHTTPX_SRCS = \
shrpx_health_monitor_downstream_connection.cc \
shrpx_health_monitor_downstream_connection.h \
shrpx_exec.cc shrpx_exec.h \
shrpx_dns_resolver.cc shrpx_dns_resolver.h \
shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \
shrpx_dns_tracker.cc shrpx_dns_tracker.h \
buffer.h memchunk.h template.h allocator.h \
xsi_strerror.c xsi_strerror.h

View File

@ -1486,6 +1486,14 @@ void fill_default_config(Config *config) {
auto &apiconf = config->api;
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
@ -1587,13 +1595,13 @@ Connections:
Several parameters <PARAM> are accepted after <PATTERN>.
The parameters are delimited by ";". The available
parameters are: "proto=<PROTO>", "tls",
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>", and
"affinity=<METHOD>". The parameter consists of keyword,
and optionally followed by "=" and value. For example,
the parameter "proto=h2" consists of the keyword "proto"
and value "h2". The parameter "tls" consists of the
keyword "tls" without value. Each parameter is
described as follows.
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
"affinity=<METHOD>", and "dns". The parameter consists
of keyword, and optionally followed by "=" and value.
For example, the parameter "proto=h2" consists of the
keyword "proto" and value "h2". The parameter "tls"
consists of the keyword "tls" without value. Each
parameter is described as follows.
The backend application protocol can be specified using
optional "proto" parameter, and in the form of
@ -1642,6 +1650,14 @@ Connections:
break if one of the backend gets unreachable, or backend
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
not contain these characters. Since ";" has special
meaning in shell, the option value must be quoted.
@ -2368,6 +2384,25 @@ API:
Default: )"
<< util::utos_unit(config->api.max_request_body) << R"(
DNS:
--dns-cache-timeout=<DURATION>
Set duration that cached DNS results remain valid. Note
that nghttpx caches the unsuccessful results as well.
Default: )"
<< util::duration_str(config->dns.timeout.cache) << R"(
--dns-lookup-timeout=<DURATION>
Set timeout that DNS server is given to respond to the
initial DNS query. For the 2nd and later queries,
server is given time based on this timeout, and it is
scaled linearly.
Default: )"
<< util::duration_str(config->dns.timeout.lookup) << R"(
--dns-max-try=<N>
Set the number of DNS query before nghttpx gives up name
lookup.
Default: )"
<< config->dns.max_try << R"(
Debug:
--frontend-http2-dump-request-header=<PATH>
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_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag,
142},
{SHRPX_OPT_DNS_CACHE_TIMEOUT.c_str(), required_argument, &flag, 143},
{SHRPX_OPT_DNS_LOOKUP_TIMEOUT.c_str(), required_argument, &flag, 144},
{SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -3700,6 +3738,18 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT,
StringRef{optarg});
break;
case 143:
// --dns-cache-timeout
cmdcfgs.emplace_back(SHRPX_OPT_DNS_CACHE_TIMEOUT, StringRef{optarg});
break;
case 144:
// --dns-lookup-timeou
cmdcfgs.emplace_back(SHRPX_OPT_DNS_LOOKUP_TIMEOUT, StringRef{optarg});
break;
case 145:
// --dns-max-try
cmdcfgs.emplace_back(SHRPX_OPT_DNS_MAX_TRY, StringRef{optarg});
break;
default:
break;
}

View File

@ -728,6 +728,7 @@ struct DownstreamParams {
shrpx_proto proto;
shrpx_session_affinity affinity;
bool tls;
bool dns;
};
namespace {
@ -801,6 +802,8 @@ int parse_downstream_params(DownstreamParams &out,
LOG(ERROR) << "backend: affinity: value must be either none or ip";
return -1;
}
} else if (util::strieq_l("dns", param)) {
out.dns = true;
} else if (!param.empty()) {
LOG(ERROR) << "backend: " << param << ": unknown keyword";
return -1;
@ -841,11 +844,17 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
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.rise = params.rise;
addr.proto = params.proto;
addr.tls = params.tls;
addr.sni = make_string_ref(downstreamconf.balloc, params.sni);
addr.dns = params.dns;
auto &routerconf = downstreamconf.router;
auto &router = routerconf.router;
@ -1361,6 +1370,9 @@ int option_lookup_token(const char *name, size_t namelen) {
}
break;
case 'y':
if (util::strieq_l("dns-max-tr", name, 10)) {
return SHRPX_OPTID_DNS_MAX_TRY;
}
if (util::strieq_l("http2-prox", name, 10)) {
return SHRPX_OPTID_HTTP2_PROXY;
}
@ -1522,6 +1534,9 @@ int option_lookup_token(const char *name, size_t namelen) {
}
break;
case 't':
if (util::strieq_l("dns-cache-timeou", name, 16)) {
return SHRPX_OPTID_DNS_CACHE_TIMEOUT;
}
if (util::strieq_l("worker-read-burs", name, 16)) {
return SHRPX_OPTID_WORKER_READ_BURST;
}
@ -1536,6 +1551,9 @@ int option_lookup_token(const char *name, size_t namelen) {
}
break;
case 't':
if (util::strieq_l("dns-lookup-timeou", name, 17)) {
return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT;
}
if (util::strieq_l("worker-write-burs", name, 17)) {
return SHRPX_OPTID_WORKER_WRITE_BURST;
}
@ -3090,6 +3108,24 @@ int parse_config(Config *config, int optid, const StringRef &opt,
LOG(WARN) << opt << ": This option requires OpenSSL >= 1.0.2";
return 0;
#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:
LOG(WARN) << "conf: ignored";
@ -3442,24 +3478,38 @@ int configure_downstream_group(Config *config, bool http2_proxy,
auto hostport =
util::make_hostport(downstreamconf.balloc, addr.host, addr.port);
if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
downstreamconf.family, resolve_flags) == -1) {
LOG(FATAL) << "Resolving backend address failed: " << hostport;
return -1;
}
if (!addr.dns) {
if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
downstreamconf.family, resolve_flags) == -1) {
LOG(FATAL) << "Resolving backend address failed: " << hostport;
return -1;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Resolved backend address: " << hostport << " -> "
<< util::to_numeric_addr(&addr.addr);
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Resolved backend address: " << hostport << " -> "
<< util::to_numeric_addr(&addr.addr);
}
} else {
LOG(INFO) << "Resolving backend address " << hostport
<< " takes place dynamically";
}
}
if (g.affinity == AFFINITY_IP) {
size_t idx = 0;
for (auto &addr : g.addrs) {
auto p = reinterpret_cast<uint8_t *>(&addr.addr.su);
rv = compute_affinity_hash(g.affinity_hash, idx,
StringRef{p, addr.addr.len});
StringRef key;
if (addr.dns) {
if (addr.host_unix) {
key = addr.host;
} else {
key = addr.hostport;
}
} else {
auto p = reinterpret_cast<uint8_t *>(&addr.addr.su);
key = StringRef{p, addr.addr.len};
}
rv = compute_affinity_hash(g.affinity_hash, idx, key);
if (rv != 0) {
return -1;
}

View File

@ -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_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;
@ -379,6 +384,7 @@ struct UpstreamAddr {
};
struct DownstreamAddrConfig {
// Resolved address if |dns| is false
Address addr;
// backend address. If |host_unix| is true, this is UNIX domain
// socket path. This must be NULL terminated string.
@ -397,6 +403,8 @@ struct DownstreamAddrConfig {
// true if |host| contains UNIX domain socket path.
bool host_unix;
bool tls;
// true if dynamic DNS is enabled
bool dns;
};
// Mapping hash to idx which is an index into
@ -777,6 +785,16 @@ struct APIConfig {
bool enabled;
};
struct DNSConfig {
struct {
ev_tstamp cache;
ev_tstamp lookup;
} timeout;
// The number of tries name resolver makes before abandoning
// request.
size_t max_try;
};
struct Config {
Config()
: balloc(4096, 4096),
@ -787,6 +805,7 @@ struct Config {
logging{},
conn{},
api{},
dns{},
num_worker{0},
padding{0},
rlimit_nofile{0},
@ -815,6 +834,7 @@ struct Config {
LoggingConfig logging;
ConnectionConfig conn;
APIConfig api;
DNSConfig dns;
StringRef pid_file;
StringRef conf_path;
StringRef user;
@ -891,6 +911,9 @@ enum {
SHRPX_OPTID_CONF,
SHRPX_OPTID_DAEMON,
SHRPX_OPTID_DH_PARAM_FILE,
SHRPX_OPTID_DNS_CACHE_TIMEOUT,
SHRPX_OPTID_DNS_LOOKUP_TIMEOUT,
SHRPX_OPTID_DNS_MAX_TRY,
SHRPX_OPTID_ECDH_CURVES,
SHRPX_OPTID_ERROR_PAGE,
SHRPX_OPTID_ERRORLOG_FILE,

344
src/shrpx_dns_resolver.cc Normal file
View 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

116
src/shrpx_dns_resolver.h Normal file
View File

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

325
src/shrpx_dns_tracker.cc Normal file
View File

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

116
src/shrpx_dns_tracker.h Normal file
View File

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

View File

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

View File

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

View File

@ -95,7 +95,7 @@ void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
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) {
delete http2session;
@ -202,6 +202,7 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
group_(group),
addr_(addr),
session_(nullptr),
raddr_(nullptr),
state_(DISCONNECTED),
connection_check_state_(CONNECTION_CHECK_NONE),
freelist_zone_(FREELIST_ZONE_NONE) {
@ -244,6 +245,11 @@ int Http2Session::disconnect(bool hard) {
wb_.reset();
if (dns_query_) {
auto dns_tracker = worker_->get_dns_tracker();
dns_tracker->cancel(dns_query_.get());
}
conn_.rlimit.stopw();
conn_.wlimit.stopw();
@ -302,12 +308,47 @@ int Http2Session::disconnect(bool hard) {
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 rv = 0;
auto worker_blocker = worker_->get_connect_blocker();
if (state_ == DISCONNECTED) {
if (state_ == DISCONNECTED || state_ == RESOLVING_NAME) {
if (worker_blocker->blocked()) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this)
@ -350,6 +391,8 @@ int Http2Session::initiate_connection() {
return -1;
}
raddr_ = &proxy.addr;
worker_blocker->on_success();
ev_io_set(&conn_.rev, conn_.fd, EV_READ);
@ -374,36 +417,68 @@ int Http2Session::initiate_connection() {
return 0;
}
if (state_ == DISCONNECTED || state_ == PROXY_CONNECTED) {
if (state_ == DISCONNECTED || state_ == PROXY_CONNECTED ||
state_ == RESOLVING_NAME) {
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) {
assert(ssl_ctx_);
auto ssl = ssl::create_ssl(ssl_ctx_);
if (!ssl) {
return -1;
if (state_ != RESOLVING_NAME) {
auto ssl = ssl::create_ssl(ssl_ctx_);
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);
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());
if (state_ == DISCONNECTED) {
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;
}
}
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);
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_ == PROXY_CONNECTED, we has connected to the proxy
@ -411,12 +486,11 @@ int Http2Session::initiate_connection() {
if (state_ == DISCONNECTED) {
assert(conn_.fd == -1);
conn_.fd =
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
if (conn_.fd == -1) {
auto error = errno;
SSLOG(WARN, this)
<< "socket() failed; addr=" << util::to_numeric_addr(&addr_->addr)
<< "socket() failed; addr=" << util::to_numeric_addr(raddr_)
<< ", errno=" << error;
worker_blocker->on_failure();
@ -427,15 +501,14 @@ int Http2Session::initiate_connection() {
rv = connect(conn_.fd,
// TODO maybe not thread-safe?
const_cast<sockaddr *>(&addr_->addr.su.sa),
addr_->addr.len);
const_cast<sockaddr *>(&raddr_->su.sa), raddr_->len);
if (rv != 0 && errno != EINPROGRESS) {
auto error = errno;
SSLOG(WARN, this) << "connect() failed; addr="
<< util::to_numeric_addr(&addr_->addr)
<< ", errno=" << error;
SSLOG(WARN, this)
<< "connect() failed; addr=" << util::to_numeric_addr(raddr_)
<< ", errno=" << error;
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return -1;
}
@ -445,17 +518,44 @@ int Http2Session::initiate_connection() {
conn_.prepare_client_handshake();
} 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) {
// Without TLS and proxy.
assert(conn_.fd == -1);
conn_.fd =
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family);
if (conn_.fd == -1) {
auto error = errno;
SSLOG(WARN, this)
<< "socket() failed; addr=" << util::to_numeric_addr(&addr_->addr)
<< "socket() failed; addr=" << util::to_numeric_addr(raddr_)
<< ", errno=" << error;
worker_blocker->on_failure();
@ -464,15 +564,15 @@ int Http2Session::initiate_connection() {
worker_blocker->on_success();
rv = connect(conn_.fd, const_cast<sockaddr *>(&addr_->addr.su.sa),
addr_->addr.len);
rv = connect(conn_.fd, const_cast<sockaddr *>(&raddr_->su.sa),
raddr_->len);
if (rv != 0 && errno != EINPROGRESS) {
auto error = errno;
SSLOG(WARN, this) << "connect() failed; addr="
<< util::to_numeric_addr(&addr_->addr)
<< ", errno=" << error;
SSLOG(WARN, this)
<< "connect() failed; addr=" << util::to_numeric_addr(raddr_)
<< ", errno=" << error;
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return -1;
}
@ -1544,7 +1644,7 @@ int Http2Session::connection_made() {
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
if (!next_proto) {
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return -1;
}
@ -1553,7 +1653,7 @@ int Http2Session::connection_made() {
SSLOG(INFO, this) << "Negotiated next protocol: " << proto;
}
if (!util::check_h2_is_selected(proto)) {
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return -1;
}
}
@ -1842,10 +1942,10 @@ int Http2Session::connected() {
auto sock_error = util::get_socket_error(conn_.fd);
if (sock_error != 0) {
SSLOG(WARN, this) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr)
<< util::to_numeric_addr(raddr_)
<< ": errno=" << sock_error;
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return -1;
}
@ -1955,7 +2055,7 @@ int Http2Session::tls_handshake() {
}
if (rv < 0) {
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return rv;
}
@ -1965,8 +2065,8 @@ int Http2Session::tls_handshake() {
}
if (!get_config()->tls.insecure &&
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
downstream_failure(addr_);
ssl::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
downstream_failure(addr_, raddr_);
return -1;
}
@ -1974,8 +2074,8 @@ int Http2Session::tls_handshake() {
if (!SSL_session_reused(conn_.tls.ssl)) {
auto tls_session = SSL_get0_session(conn_.tls.ssl);
if (tls_session) {
ssl::try_cache_tls_session(addr_->tls_session_cache, addr_->addr,
tls_session, ev_now(conn_.loop));
ssl::try_cache_tls_session(addr_->tls_session_cache, *raddr_, tls_session,
ev_now(conn_.loop));
}
}
@ -2262,9 +2362,9 @@ void Http2Session::on_timeout() {
}
case CONNECTING: {
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;
}
}
@ -2284,4 +2384,6 @@ void Http2Session::check_retire() {
signal_write();
}
const Address *Http2Session::get_raddr() const { return raddr_; }
} // namespace shrpx

View File

@ -50,6 +50,7 @@ class Http2DownstreamConnection;
class Worker;
struct DownstreamAddrGroup;
struct DownstreamAddr;
struct DNSQuery;
struct StreamData {
StreamData *dlnext, *dlprev;
@ -81,6 +82,7 @@ public:
// associated ClientHandlers will be deleted.
int disconnect(bool hard = false);
int initiate_connection();
int resolve_name();
void add_downstream_connection(Http2DownstreamConnection *dconn);
void remove_downstream_connection(Http2DownstreamConnection *dconn);
@ -203,6 +205,9 @@ public:
// shutdown the connection.
void check_retire();
// Returns address used to connect to backend. Could be nullptr.
const Address *get_raddr() const;
enum {
// Disconnected
DISCONNECTED,
@ -218,6 +223,8 @@ public:
CONNECTED,
// Connection is started to fail
CONNECT_FAILING,
// Resolving host name
RESOLVING_NAME,
};
enum {
@ -259,6 +266,13 @@ private:
// Address of remote endpoint
DownstreamAddr *addr_;
nghttp2_session *session_;
// Actual remote address used to contact backend. This is initially
// nullptr, and may point to either &addr_->addr,
// resolved_addr_.get(), or HTTP proxy's address structure.
const Address *raddr_;
// Resolved IP address if dns parameter is used
std::unique_ptr<Address> resolved_addr_;
std::unique_ptr<DNSQuery> dns_query_;
int state_;
int connection_check_state_;
int freelist_zone_;

View File

@ -75,11 +75,12 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto conn = static_cast<Connection *>(w->data);
auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
auto addr = dconn->get_addr();
auto raddr = dconn->get_raddr();
DCLOG(WARN, dconn) << "Connect time out; addr="
<< util::to_numeric_addr(&addr->addr);
<< util::to_numeric_addr(raddr);
downstream_failure(addr);
downstream_failure(addr, raddr);
auto downstream = dconn->get_downstream();
auto upstream = downstream->get_upstream();
@ -182,13 +183,34 @@ HttpDownstreamConnection::~HttpDownstreamConnection() {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Deleted";
}
if (dns_query_) {
auto dns_tracker = worker_->get_dns_tracker();
dns_tracker->cancel(dns_query_.get());
}
}
int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
int rv;
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
}
downstream_ = downstream;
rv = initiate_connection();
if (rv != 0) {
downstream_ = nullptr;
return rv;
}
return 0;
}
int HttpDownstreamConnection::initiate_connection() {
int rv;
auto worker_blocker = worker_->get_connect_blocker();
if (worker_blocker->blocked()) {
if (LOG_ENABLED(INFO)) {
@ -212,42 +234,136 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
shared_addr->affinity == AFFINITY_NONE ? shared_addr->next : temp_idx;
auto end = next_downstream;
for (;;) {
auto &addr = addrs[next_downstream];
auto check_dns_result = dns_query_.get() != nullptr;
if (++next_downstream >= addrs.size()) {
next_downstream = 0;
}
DownstreamAddr *addr;
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 (end == next_downstream) {
return SHRPX_ERR_NETWORK;
if (++next_downstream >= addrs.size()) {
next_downstream = 0;
}
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 (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Backend server "
<< util::to_numeric_addr(&addr.addr)
<< " was not available temporarily";
DCLOG(INFO, this) << "Backend server " << addr->host << ":"
<< addr->port << " was not available temporarily";
}
if (end == next_downstream) {
if (check_dns_result) {
dns_query_.reset();
} else if (end == next_downstream) {
return SHRPX_ERR_NETWORK;
}
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) {
auto error = errno;
DCLOG(WARN, this) << "socket() failed; addr="
<< util::to_numeric_addr(&addr.addr)
<< util::to_numeric_addr(raddr)
<< ", errno=" << error;
worker_blocker->on_failure();
@ -257,20 +373,19 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
worker_blocker->on_success();
int rv;
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) {
auto error = errno;
DCLOG(WARN, this) << "connect() failed; addr="
<< util::to_numeric_addr(&addr.addr)
<< util::to_numeric_addr(raddr)
<< ", errno=" << error;
downstream_failure(&addr);
downstream_failure(addr, raddr);
close(conn_.fd);
conn_.fd = -1;
if (end == next_downstream) {
if (!check_dns_result && end == next_downstream) {
return SHRPX_ERR_NETWORK;
}
@ -282,7 +397,8 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
DCLOG(INFO, this) << "Connecting to downstream server";
}
addr_ = &addr;
addr_ = addr;
raddr_ = raddr;
if (addr_->tls) {
assert(ssl_ctx_);
@ -334,8 +450,6 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
ev_set_cb(&conn_.rev, readcb);
}
downstream_ = downstream;
http_parser_init(&response_htp_, HTTP_RESPONSE);
response_htp_.data = downstream_;
@ -1031,7 +1145,7 @@ int HttpDownstreamConnection::tls_handshake() {
}
if (rv < 0) {
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return rv;
}
@ -1041,8 +1155,8 @@ int HttpDownstreamConnection::tls_handshake() {
}
if (!get_config()->tls.insecure &&
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
downstream_failure(addr_);
ssl::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
downstream_failure(addr_, raddr_);
return -1;
}
@ -1050,7 +1164,7 @@ int HttpDownstreamConnection::tls_handshake() {
if (!SSL_session_reused(conn_.tls.ssl)) {
auto session = SSL_get0_session(conn_.tls.ssl);
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));
}
}
@ -1215,10 +1329,10 @@ int HttpDownstreamConnection::connected() {
conn_.wlimit.stopw();
DCLOG(WARN, this) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr)
<< util::to_numeric_addr(raddr_)
<< ": errno=" << sock_error;
downstream_failure(addr_);
downstream_failure(addr_, raddr_);
return -1;
}
@ -1282,4 +1396,6 @@ DownstreamAddr *HttpDownstreamConnection::get_addr() const { return addr_; }
bool HttpDownstreamConnection::poolable() const { return !group_->retired; }
const Address *HttpDownstreamConnection::get_raddr() const { return raddr_; }
} // namespace shrpx

View File

@ -39,6 +39,7 @@ class DownstreamConnectionPool;
class Worker;
struct DownstreamAddrGroup;
struct DownstreamAddr;
struct DNSQuery;
class HttpDownstreamConnection : public DownstreamConnection {
public:
@ -68,6 +69,8 @@ public:
get_downstream_addr_group() const;
virtual DownstreamAddr *get_addr() const;
int initiate_connection();
int read_clear();
int write_clear();
int read_tls();
@ -80,6 +83,9 @@ public:
void signal_write();
int actual_signal_write();
// Returns address used to connect to backend. Could be nullptr.
const Address *get_raddr() const;
int noop();
private:
@ -92,6 +98,13 @@ private:
std::shared_ptr<DownstreamAddrGroup> group_;
// Address of remote endpoint
DownstreamAddr *addr_;
// Actual remote address used to contact backend. This is initially
// nullptr, and may point to either &addr_->addr, or
// resolved_addr_.get().
const Address *raddr_;
// Resolved IP address if dns parameter is used
std::unique_ptr<Address> resolved_addr_;
std::unique_ptr<DNSQuery> dns_query_;
IOControl ioctrl_;
http_parser response_htp_;
ssize_t initial_addr_idx_;

View File

@ -114,6 +114,7 @@ LiveCheck::LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
ssl_ctx_(ssl_ctx),
addr_(addr),
session_(nullptr),
raddr_(nullptr),
success_count_(0),
fail_count_(0),
settings_ack_received_(false),
@ -134,6 +135,16 @@ LiveCheck::~LiveCheck() {
}
void LiveCheck::disconnect() {
if (dns_query_) {
auto dns_tracker = worker_->get_dns_tracker();
dns_tracker->cancel(dns_query_.get());
}
dns_query_.reset();
// We can reuse resolved_addr_
raddr_ = nullptr;
conn_.rlimit.stopw();
conn_.wlimit.stopw();
@ -190,7 +201,7 @@ int LiveCheck::initiate_connection() {
return -1;
}
if (addr_->tls) {
if (!dns_query_ && addr_->tls) {
assert(ssl_ctx_);
auto ssl = ssl::create_ssl(ssl_ctx_);
@ -212,20 +223,71 @@ int LiveCheck::initiate_connection() {
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) {
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;
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) {
auto error = errno;
LOG(WARN) << "connect() failed; addr="
<< util::to_numeric_addr(&addr_->addr) << ", errno=" << error;
LOG(WARN) << "connect() failed; addr=" << util::to_numeric_addr(raddr_)
<< ", errno=" << error;
close(conn_.fd);
conn_.fd = -1;
@ -269,8 +331,7 @@ int LiveCheck::connected() {
if (sock_error != 0) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr)
<< ": errno=" << sock_error;
<< util::to_numeric_addr(raddr_) << ": errno=" << sock_error;
}
return -1;
@ -334,15 +395,15 @@ int LiveCheck::tls_handshake() {
}
if (!get_config()->tls.insecure &&
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
ssl::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) {
return -1;
}
if (!SSL_session_reused(conn_.tls.ssl)) {
auto tls_session = SSL_get0_session(conn_.tls.ssl);
if (tls_session) {
ssl::try_cache_tls_session(addr_->tls_session_cache, addr_->addr,
tls_session, ev_now(conn_.loop));
ssl::try_cache_tls_session(addr_->tls_session_cache, *raddr_, tls_session,
ev_now(conn_.loop));
}
}
@ -601,7 +662,7 @@ void LiveCheck::on_failure() {
++fail_count_;
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";
}
@ -615,7 +676,7 @@ void LiveCheck::on_success() {
fail_count_ = 0;
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";
}

View File

@ -42,6 +42,7 @@ namespace shrpx {
class Worker;
struct DownstreamAddr;
struct DNSQuery;
class LiveCheck {
public:
@ -102,6 +103,13 @@ private:
// Address of remote endpoint
DownstreamAddr *addr_;
nghttp2_session *session_;
// Actual remote address used to contact backend. This is initially
// nullptr, and may point to either &addr_->addr, or
// resolved_addr_.get().
const Address *raddr_;
// Resolved IP address if dns parameter is used
std::unique_ptr<Address> resolved_addr_;
std::unique_ptr<DNSQuery> dns_query_;
// The number of successful connect attempt in a row.
size_t success_count_;
// The number of unsuccessful connect attempt in a row.

View File

@ -1171,10 +1171,10 @@ int check_cert(SSL *ssl, const Address *addr, const StringRef &host) {
return 0;
}
int check_cert(SSL *ssl, const DownstreamAddr *addr) {
int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr) {
auto hostname =
addr->sni.empty() ? StringRef{addr->host} : StringRef{addr->sni};
return check_cert(ssl, &addr->addr, hostname);
return check_cert(ssl, raddr, hostname);
}
CertLookupTree::CertLookupTree() {}

View File

@ -104,7 +104,10 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
// 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 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 {
WildcardRevPrefix(const StringRef &prefix, size_t idx)

View File

@ -92,7 +92,7 @@ bool match_shared_downstream_addr(
auto &b = rhs->addrs[i];
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.fall == b.fall && a.rise == b.rise) {
a.fall == b.fall && a.rise == b.rise && a.dns == b.dns) {
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)
: randgen_(rd()),
worker_stat_{},
dns_tracker_(loop),
loop_(loop),
sv_ssl_ctx_(sv_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.fall = src_addr.fall;
dst_addr.rise = src_addr.rise;
dst_addr.dns = src_addr.dns;
auto shared_addr_ptr = shared_addr.get();
@ -490,6 +492,8 @@ ConnectionHandler *Worker::get_connection_handler() const {
return conn_handler_;
}
DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; }
namespace {
size_t match_downstream_addr_group_host(
const RouterConfig &routerconf, const StringRef &host,
@ -635,7 +639,7 @@ size_t match_downstream_addr_group(
catch_all, balloc);
}
void downstream_failure(DownstreamAddr *addr) {
void downstream_failure(DownstreamAddr *addr, const Address *raddr) {
const auto &connect_blocker = addr->connect_blocker;
if (connect_blocker->in_offline()) {
@ -651,8 +655,15 @@ void downstream_failure(DownstreamAddr *addr) {
auto fail_count = connect_blocker->get_fail_count();
if (fail_count >= addr->fall) {
LOG(WARN) << "Could not connect to " << util::to_numeric_addr(&addr->addr)
<< " " << fail_count << " times in a row; considered as offline";
if (raddr) {
LOG(WARN) << "Could not connect to " << util::to_numeric_addr(raddr)
<< " " << fail_count
<< " times in a row; considered as offline";
} else {
LOG(WARN) << "Could not connect to " << addr->host << ":" << addr->port
<< " " << fail_count
<< " times in a row; considered as offline";
}
connect_blocker->offline();

View File

@ -48,6 +48,7 @@
#include "shrpx_ssl.h"
#include "shrpx_live_check.h"
#include "shrpx_connect_blocker.h"
#include "shrpx_dns_tracker.h"
#include "allocator.h"
using namespace nghttp2;
@ -112,6 +113,8 @@ struct DownstreamAddr {
shrpx_proto proto;
// true if TLS is used in this backend
bool tls;
// true if dynamic DNS is enabled
bool dns;
};
// Simplified weighted fair queuing. Actually we don't use queue here
@ -263,6 +266,8 @@ public:
ConnectionHandler *get_connection_handler() const;
DNSTracker *get_dns_tracker();
private:
#ifndef NOTHREADS
std::future<void> fut_;
@ -275,6 +280,7 @@ private:
ev_timer proc_wev_timer_;
MemchunkPool mcpool_;
WorkerStat worker_stat_;
DNSTracker dns_tracker_;
std::shared_ptr<DownstreamConfig> downstreamconf_;
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,
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

View File

@ -39,6 +39,8 @@
#include <ev.h>
#include <ares.h>
#include "shrpx_config.h"
#include "shrpx_connection_handler.h"
#include "shrpx_log_config.h"
@ -392,6 +394,7 @@ std::random_device rd;
} // namespace
int worker_process_event_loop(WorkerProcessConfig *wpconf) {
int rv;
std::array<char, STRERROR_BUFSIZE> errbuf;
(void)errbuf;
@ -400,6 +403,12 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
return -1;
}
rv = ares_library_init(ARES_LIB_INIT_ALL);
if (rv != 0) {
LOG(FATAL) << "ares_library_init failed: " << ares_strerror(rv);
return -1;
}
auto loop = EV_DEFAULT;
auto gen = std::mt19937(rd());
@ -494,8 +503,6 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
}
}
int rv;
if (config->num_worker == 1) {
rv = conn_handler.create_single_worker();
if (rv != 0) {
@ -575,6 +582,8 @@ int worker_process_event_loop(WorkerProcessConfig *wpconf) {
}
#endif // HAVE_NEVERBLEED
ares_library_cleanup();
return 0;
}

View File

@ -666,6 +666,17 @@ std::string to_numeric_addr(const Address *addr) {
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 STDOUT_COPY = -1;

View File

@ -478,6 +478,9 @@ std::string numeric_name(const struct sockaddr *sa, socklen_t salen);
// IPv6 address, address is enclosed by square brackets ([]).
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),
// which is then used as pointer to /dev/stderr or /proc/self/fd/2
void store_original_fds();