From 38b5cad4e346240cb5c34c51c7adf6e59c2faeed Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 4 Dec 2016 23:43:41 +0900 Subject: [PATCH 01/15] nghttpx: Lookup backend host name dynamically We have added "dns" parameter to backend option. If specified, name lookup is done dynamically. If not, name lookup is done at start up, or configuration reloading. nghttpx caches DNS result including error case in 30 seconds in this commit. Later commit makes this configurable. DNS resolution is done asynchronously using c-ares library. --- configure.ac | 35 ++- src/Makefile.am | 5 + src/shrpx.cc | 22 +- src/shrpx_config.cc | 45 +++- src/shrpx_config.h | 3 + src/shrpx_dns_resolver.cc | 334 ++++++++++++++++++++++++ src/shrpx_dns_resolver.h | 116 ++++++++ src/shrpx_dns_tracker.cc | 262 +++++++++++++++++++ src/shrpx_dns_tracker.h | 108 ++++++++ src/shrpx_dual_dns_resolver.cc | 87 ++++++ src/shrpx_dual_dns_resolver.h | 63 +++++ src/shrpx_http2_session.cc | 206 +++++++++++---- src/shrpx_http2_session.h | 14 + src/shrpx_http_downstream_connection.cc | 178 ++++++++++--- src/shrpx_http_downstream_connection.h | 13 + src/shrpx_live_check.cc | 87 +++++- src/shrpx_live_check.h | 8 + src/shrpx_ssl.cc | 4 +- src/shrpx_ssl.h | 5 +- src/shrpx_worker.cc | 19 +- src/shrpx_worker.h | 11 +- src/util.cc | 11 + src/util.h | 3 + 23 files changed, 1514 insertions(+), 125 deletions(-) create mode 100644 src/shrpx_dns_resolver.cc create mode 100644 src/shrpx_dns_resolver.h create mode 100644 src/shrpx_dns_tracker.cc create mode 100644 src/shrpx_dns_tracker.h create mode 100644 src/shrpx_dual_dns_resolver.cc create mode 100644 src/shrpx_dual_dns_resolver.h diff --git a/configure.ac b/configure.ac index 35743647..e18efbef 100644 --- a/configure.ac +++ b/configure.ac @@ -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.11.0], [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 ]]) +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 +#include +#include +]]) + +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 +#include +#include +]]) + 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}') diff --git a/src/Makefile.am b/src/Makefile.am index ceaa04a8..6f60faed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/shrpx.cc b/src/shrpx.cc index 8e1a7273..4f6f28a3 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1587,13 +1587,13 @@ Connections: Several parameters are accepted after . The parameters are delimited by ";". The available parameters are: "proto=", "tls", - "sni=", "fall=", "rise=", and - "affinity=". 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=", "fall=", "rise=", + "affinity=", 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 +1642,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, must not contain these characters. Since ";" has special meaning in shell, the option value must be quoted. diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 94cb1227..b4b1b8ad 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -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; @@ -3442,24 +3451,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(&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(&addr.addr.su); + key = StringRef{p, addr.addr.len}; + } + rv = compute_affinity_hash(g.affinity_hash, idx, key); if (rv != 0) { return -1; } diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 9020ef83..d6ed19d6 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -379,6 +379,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 +398,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 diff --git a/src/shrpx_dns_resolver.cc b/src/shrpx_dns_resolver.cc new file mode 100644 index 00000000..a72b3364 --- /dev/null +++ b/src/shrpx_dns_resolver.cc @@ -0,0 +1,334 @@ +/* + * 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 +#include + +#include "shrpx_log.h" +#include "shrpx_connection.h" + +namespace shrpx { + +namespace { +void sock_state_cb(void *data, int s, int read, int write) { + auto resolv = static_cast(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(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(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(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(w->data); + resolv->on_timeout(); +} +} // namespace + +namespace { +void stop_ev(struct ev_loop *loop, + const std::vector> &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; + + ares_options opts{}; + opts.sock_state_cb = sock_state_cb; + opts.sock_state_cb_data = this; + + auto optmask = ARES_OPT_SOCK_STATE_CB; + + 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> &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_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> &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) << "Address lookup for " << name_ + << " failed: " << ares_strerror(status); + } + status_ = DNS_STATUS_ERROR; + return; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Address lookup for " << name_ << " succeeded"; + } + + status_ = DNS_STATUS_OK; + + switch (hostent->h_addrtype) { + case AF_INET: + for (auto ap = hostent->h_addr_list; *ap; ++ap) { + 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)); + return; + } + break; + case AF_INET6: + for (auto ap = hostent->h_addr_list; *ap; ++ap) { + 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)); + return; + } + break; + } + + // Somehow we got unsupported address family + 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 diff --git a/src/shrpx_dns_resolver.h b/src/shrpx_dns_resolver.h new file mode 100644 index 00000000..268547e3 --- /dev/null +++ b/src/shrpx_dns_resolver.h @@ -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 +#include + +#include + +#include +#include + +#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; + +// 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> 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 diff --git a/src/shrpx_dns_tracker.cc b/src/shrpx_dns_tracker.cc new file mode 100644 index 00000000..3acb100a --- /dev/null +++ b/src/shrpx_dns_tracker.cc @@ -0,0 +1,262 @@ +/* + * 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 "util.h" + +namespace shrpx { + +DNSTracker::DNSTracker(struct ev_loop *loop) : loop_(loop) {} + +DNSTracker::~DNSTracker() { + 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. + } + } +} + +namespace { +constexpr auto DNS_TTL = 30_s; +} // namespace + +ResolverEntry DNSTracker::make_entry(std::unique_ptr resolv, + ImmutableString host, int status, + const Address *result) { + auto ent = ResolverEntry{}; + ent.resolv = std::move(resolv); + ent.host = std::move(host); + ent.status = status; + ent.expiry = ev_now(loop_) + DNS_TTL; + if (result) { + ent.result = *result; + } + return ent; +} + +void DNSTracker::update_entry(ResolverEntry &ent, + std::unique_ptr resolv, + int status, const Address *result) { + ent.resolv = std::move(resolv); + ent.status = status; + 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(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)); + + 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)); + + 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)); + + 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)); + + 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(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); + } + + ent.resolv.reset(); + ent.status = status; + ent.expiry = ev_now(loop) + DNS_TTL; + 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; +} + +} // namespace shrpx diff --git a/src/shrpx_dns_tracker.h b/src/shrpx_dns_tracker.h new file mode 100644 index 00000000..455026ec --- /dev/null +++ b/src/shrpx_dns_tracker.h @@ -0,0 +1,108 @@ +/* + * 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 + +#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 resolv; + // DNSQuery interested in this name lookup result. The result is + // notified to them all. + DList 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); + +private: + ResolverEntry make_entry(std::unique_ptr resolv, + ImmutableString host, int status, + const Address *result); + + void update_entry(ResolverEntry &ent, std::unique_ptr resolv, + int status, const Address *result); + + void add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq); + + std::map ents_; + struct ev_loop *loop_; +}; + +} // namespace shrpx + +#endif // SHRPX_DNS_TRACKER_H diff --git a/src/shrpx_dual_dns_resolver.cc b/src/shrpx_dual_dns_resolver.cc new file mode 100644 index 00000000..82145628 --- /dev/null +++ b/src/shrpx_dual_dns_resolver.cc @@ -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 diff --git a/src/shrpx_dual_dns_resolver.h b/src/shrpx_dual_dns_resolver.h new file mode 100644 index 00000000..a8e08264 --- /dev/null +++ b/src/shrpx_dual_dns_resolver.h @@ -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 + +#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 diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 111e5290..e2b4608c 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -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( + 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
(); + 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(&addr_->addr.su.sa), - addr_->addr.len); + const_cast(&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(&addr_->addr.su.sa), - addr_->addr.len); + rv = connect(conn_.fd, const_cast(&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 diff --git a/src/shrpx_http2_session.h b/src/shrpx_http2_session.h index 33ff5cf9..3dce8084 100644 --- a/src/shrpx_http2_session.h +++ b/src/shrpx_http2_session.h @@ -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
resolved_addr_; + std::unique_ptr dns_query_; int state_; int connection_check_state_; int freelist_zone_; diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 16435a84..d0fbe82b 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -75,11 +75,12 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { auto conn = static_cast(w->data); auto dconn = static_cast(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( + 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
(); + } + 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 diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h index 03050d8a..3001c4ea 100644 --- a/src/shrpx_http_downstream_connection.h +++ b/src/shrpx_http_downstream_connection.h @@ -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 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
resolved_addr_; + std::unique_ptr dns_query_; IOControl ioctrl_; http_parser response_htp_; ssize_t initial_addr_idx_; diff --git a/src/shrpx_live_check.cc b/src/shrpx_live_check.cc index 4065ff7c..1af88c4c 100644 --- a/src/shrpx_live_check.cc +++ b/src/shrpx_live_check.cc @@ -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( + 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
(); + } + + 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"; } diff --git a/src/shrpx_live_check.h b/src/shrpx_live_check.h index 3cd83a55..b65ecdcd 100644 --- a/src/shrpx_live_check.h +++ b/src/shrpx_live_check.h @@ -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
resolved_addr_; + std::unique_ptr dns_query_; // The number of successful connect attempt in a row. size_t success_count_; // The number of unsuccessful connect attempt in a row. diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index 5c9f72bd..fa2f39e2 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -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() {} diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index 6f44a058..08f07a24 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -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) diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 2b9ae1b2..30db93a7 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -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 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(); diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index c68c2485..44a29f09 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -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 fut_; @@ -275,6 +280,7 @@ private: ev_timer proc_wev_timer_; MemchunkPool mcpool_; WorkerStat worker_stat_; + DNSTracker dns_tracker_; std::shared_ptr downstreamconf_; std::unique_ptr session_cache_memcached_dispatcher_; @@ -314,7 +320,10 @@ size_t match_downstream_addr_group( const std::vector> &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 diff --git a/src/util.cc b/src/util.cc index 3153f34b..62442cb4 100644 --- a/src/util.cc +++ b/src/util.cc @@ -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; diff --git a/src/util.h b/src/util.h index 8a767a0a..9bb63ef7 100644 --- a/src/util.h +++ b/src/util.h @@ -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(); From 77a324fa4672987a1fde5a8d8339ed86b97757e1 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 17:24:15 +0900 Subject: [PATCH 02/15] nghttpx: Backend API call allows non-numeric host with dns parameter --- doc/nghttpx.1.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/nghttpx.1.rst b/doc/nghttpx.1.rst index 6e9b4c2f..eba52dfe 100644 --- a/doc/nghttpx.1.rst +++ b/doc/nghttpx.1.rst @@ -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 -------- From d66377d4b66d67da22ae60937904d71ce36313c1 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 17:48:28 +0900 Subject: [PATCH 03/15] nghttpx: Add dns-cache-timeout option This option controls how long cached DNS entries remain valid. --- gennghttpxfun.py | 1 + src/shrpx.cc | 18 ++++++++++++++++++ src/shrpx_config.cc | 5 +++++ src/shrpx_config.h | 11 +++++++++++ src/shrpx_dns_tracker.cc | 13 +++++++------ 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/gennghttpxfun.py b/gennghttpxfun.py index ec1c015a..f57e2c06 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -149,6 +149,7 @@ OPTIONS = [ "ecdh-curves", "tls-sct-dir", "backend-connect-timeout", + "dns-cache-timeout", ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index 4f6f28a3..1c7f918f 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1486,6 +1486,12 @@ 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; + } } } // namespace @@ -2376,6 +2382,13 @@ API: Default: )" << util::utos_unit(config->api.max_request_body) << R"( +DNS: + --dns-cache-timeout= + 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"( + Debug: --frontend-http2-dump-request-header= Dumps request headers received by HTTP/2 frontend to the @@ -3035,6 +3048,7 @@ 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}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -3708,6 +3722,10 @@ 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; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index b4b1b8ad..e4a3e530 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -1531,6 +1531,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; } @@ -3099,6 +3102,8 @@ 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_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index d6ed19d6..b757dac0 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -312,6 +312,8 @@ 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 size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -780,6 +782,12 @@ struct APIConfig { bool enabled; }; +struct DNSConfig { + struct { + ev_tstamp cache; + } timeout; +}; + struct Config { Config() : balloc(4096, 4096), @@ -790,6 +798,7 @@ struct Config { logging{}, conn{}, api{}, + dns{}, num_worker{0}, padding{0}, rlimit_nofile{0}, @@ -818,6 +827,7 @@ struct Config { LoggingConfig logging; ConnectionConfig conn; APIConfig api; + DNSConfig dns; StringRef pid_file; StringRef conf_path; StringRef user; @@ -894,6 +904,7 @@ enum { SHRPX_OPTID_CONF, SHRPX_OPTID_DAEMON, SHRPX_OPTID_DH_PARAM_FILE, + SHRPX_OPTID_DNS_CACHE_TIMEOUT, SHRPX_OPTID_ECDH_CURVES, SHRPX_OPTID_ERROR_PAGE, SHRPX_OPTID_ERRORLOG_FILE, diff --git a/src/shrpx_dns_tracker.cc b/src/shrpx_dns_tracker.cc index 3acb100a..53d2c733 100644 --- a/src/shrpx_dns_tracker.cc +++ b/src/shrpx_dns_tracker.cc @@ -23,6 +23,7 @@ * 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 { @@ -43,18 +44,16 @@ DNSTracker::~DNSTracker() { } } -namespace { -constexpr auto DNS_TTL = 30_s; -} // namespace - ResolverEntry DNSTracker::make_entry(std::unique_ptr 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; - ent.expiry = ev_now(loop_) + DNS_TTL; + ent.expiry = ev_now(loop_) + dnsconf.timeout.cache; if (result) { ent.result = *result; } @@ -233,9 +232,11 @@ void DNSTracker::add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq) { cb(status, result); } + auto &dnsconf = get_config()->dns; + ent.resolv.reset(); ent.status = status; - ent.expiry = ev_now(loop) + DNS_TTL; + ent.expiry = ev_now(loop) + dnsconf.timeout.cache; if (ent.status == DNS_STATUS_OK) { ent.result = *result; } From e7da2a669e81a8c13bca4d90b65fe279669e1c86 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 21:04:17 +0900 Subject: [PATCH 04/15] .travis.yml: Add libc-ares-dev --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 9d8395da..7462f03a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ addons: - libevent-dev - libjansson-dev - libjemalloc-dev + - libc-ares-dev - cmake - cmake-data before_install: From 5ed9e4c83bf696c0a6653b86d868057e997a327d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 21:08:57 +0900 Subject: [PATCH 05/15] Document that c-ares is a required library for nghttpx --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 11f2c1ea..835aab76 100644 --- a/README.rst +++ b/README.rst @@ -70,6 +70,7 @@ are required: * OpenSSL >= 1.0.1 * libev >= 4.11 * zlib >= 1.2.3 +* libc-ares >= 1.10.0 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 From b58d7b406f32e44a1d3302f33b17b32548b502a0 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 21:32:37 +0900 Subject: [PATCH 06/15] Try c-ares 1.7.5 because it is the latest version travis offers --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index e18efbef..49b66071 100644 --- a/configure.ac +++ b/configure.ac @@ -371,7 +371,7 @@ if test "x${have_openssl}" = "xno"; then fi # c-ares (for src) -PKG_CHECK_MODULES([LIBCARES], [libcares >= 1.11.0], [have_libcares=yes], +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) From 7c11d2d9bb95586d87d5b0a8f2bd9bb26ddd04b9 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 21:40:50 +0900 Subject: [PATCH 07/15] Require c-ares >= 1.7.5 --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 835aab76..5ff4bbbe 100644 --- a/README.rst +++ b/README.rst @@ -70,7 +70,7 @@ are required: * OpenSSL >= 1.0.1 * libev >= 4.11 * zlib >= 1.2.3 -* libc-ares >= 1.10.0 +* 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 From 264a98d1064317ea06297a843be479382da6f936 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 21:41:03 +0900 Subject: [PATCH 08/15] nghttpx: Call c-ares initialization/cleanup functions --- src/shrpx_worker_process.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/shrpx_worker_process.cc b/src/shrpx_worker_process.cc index 03cef095..aa1239c7 100644 --- a/src/shrpx_worker_process.cc +++ b/src/shrpx_worker_process.cc @@ -39,6 +39,8 @@ #include +#include + #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 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; } From d66d34f9b95537af0c9120f0de1c10704c65998d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 22:16:35 +0900 Subject: [PATCH 09/15] Add libc-ares detection to cmake --- CMakeLists.txt | 10 ++++++++++ Makefile.am | 3 ++- cmake/FindLibcares.cmake | 40 ++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 5 +++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 cmake/FindLibcares.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b3bfcfe..1ad8b561 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}') diff --git a/Makefile.am b/Makefile.am index a66561f3..af0e1858 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/cmake/FindLibcares.cmake b/cmake/FindLibcares.cmake new file mode 100644 index 00000000..1fe56ce7 --- /dev/null +++ b/cmake/FindLibcares.cmake @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92b2ab40..0648f6f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) From 0967ee9cb9c023c6157b8d301c8e1901bfbe9fb0 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 10 Dec 2016 23:10:18 +0900 Subject: [PATCH 10/15] nghttpx: Better logging for DNS resolver --- src/shrpx_dns_resolver.cc | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/shrpx_dns_resolver.cc b/src/shrpx_dns_resolver.cc index a72b3364..9a26a2ed 100644 --- a/src/shrpx_dns_resolver.cc +++ b/src/shrpx_dns_resolver.cc @@ -281,22 +281,17 @@ void DNSResolver::on_result(int status, hostent *hostent) { if (status != ARES_SUCCESS) { if (LOG_ENABLED(INFO)) { - LOG(INFO) << "Address lookup for " << name_ + LOG(INFO) << "Name lookup for " << name_ << " failed: " << ares_strerror(status); } status_ = DNS_STATUS_ERROR; return; } - if (LOG_ENABLED(INFO)) { - LOG(INFO) << "Address lookup for " << name_ << " succeeded"; - } - - status_ = DNS_STATUS_OK; - 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; @@ -304,11 +299,12 @@ void DNSResolver::on_result(int status, hostent *hostent) { 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)); - return; + 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; @@ -316,12 +312,21 @@ void DNSResolver::on_result(int status, hostent *hostent) { 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)); - return; + 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; } - // Somehow we got unsupported address family status_ = DNS_STATUS_ERROR; } From a06a8c36a49d16e1df2b45c0e60b2cbb10e536f8 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 11 Dec 2016 00:50:16 +0900 Subject: [PATCH 11/15] nghttpx: Add --dns-lookup-timeout and --dns-max-try options --- gennghttpxfun.py | 2 ++ src/shrpx.cc | 24 ++++++++++++++++++++++++ src/shrpx_config.cc | 22 ++++++++++++++++++++++ src/shrpx_config.h | 9 +++++++++ src/shrpx_dns_resolver.cc | 7 ++++++- 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/gennghttpxfun.py b/gennghttpxfun.py index f57e2c06..d85eb139 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -150,6 +150,8 @@ OPTIONS = [ "tls-sct-dir", "backend-connect-timeout", "dns-cache-timeout", + "dns-lookup-timeout", + "dns-max-try", ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index 1c7f918f..9fe36c64 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1491,7 +1491,9 @@ void fill_default_config(Config *config) { { auto &timeoutconf = dnsconf.timeout; timeoutconf.cache = 10_s; + timeoutconf.lookup = 5_s; } + dnsconf.max_try = 2; } } // namespace @@ -2388,6 +2390,18 @@ DNS: that nghttpx caches the unsuccessful results as well. Default: )" << util::duration_str(config->dns.timeout.cache) << R"( + --dns-lookup-timeout= + 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= + Set the number of DNS query before nghttpx gives up name + lookup. + Default: )" + << config->dns.max_try << R"( Debug: --frontend-http2-dump-request-header= @@ -3049,6 +3063,8 @@ int main(int argc, char **argv) { {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; @@ -3726,6 +3742,14 @@ int main(int argc, char **argv) { // --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; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index e4a3e530..a963d3b6 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -1370,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; } @@ -1548,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; } @@ -3104,6 +3110,22 @@ int parse_config(Config *config, int optid, const StringRef &opt, #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"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index b757dac0..95c39f9c 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -314,6 +314,9 @@ 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; @@ -785,7 +788,11 @@ struct APIConfig { 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 { @@ -905,6 +912,8 @@ enum { 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, diff --git a/src/shrpx_dns_resolver.cc b/src/shrpx_dns_resolver.cc index 9a26a2ed..104ba633 100644 --- a/src/shrpx_dns_resolver.cc +++ b/src/shrpx_dns_resolver.cc @@ -29,6 +29,7 @@ #include "shrpx_log.h" #include "shrpx_connection.h" +#include "shrpx_config.h" namespace shrpx { @@ -145,11 +146,15 @@ int DNSResolver::resolve(const StringRef &name, int 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(dnsconf.timeout.lookup * 1000); + opts.tries = dnsconf.max_try; - auto optmask = ARES_OPT_SOCK_STATE_CB; + auto optmask = ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES; ares_channel chan; rv = ares_init_options(&chan, &opts, optmask); From fd403a85c8f2b4ce18a8fb62f98b694b36f1acf6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 11 Dec 2016 10:39:19 +0900 Subject: [PATCH 12/15] nghttpx: Just return DNS_STATUS_ERROR At the moment, we use both resolvers, and if either one is not DNS_STATUS_IDLE, the other one is also not DNS_STATUS_IDLE. This may change if we are going to configure DNS so that either A or AAAA lookup is done. In that case, it is better to just return DNS_STATUS_ERROR in the diff. This is because the calling side does not expect DNS_STATUS_IDLE in that case. --- src/shrpx_dual_dns_resolver.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shrpx_dual_dns_resolver.cc b/src/shrpx_dual_dns_resolver.cc index 82145628..5f75ef4a 100644 --- a/src/shrpx_dual_dns_resolver.cc +++ b/src/shrpx_dual_dns_resolver.cc @@ -78,7 +78,7 @@ int DualDNSResolver::get_status(Address *result) const { if (rv4 == DNS_STATUS_RUNNING || rv6 == DNS_STATUS_RUNNING) { return DNS_STATUS_RUNNING; } - if (rv4 == DNS_STATUS_ERROR && rv6 == DNS_STATUS_ERROR) { + if (rv4 == DNS_STATUS_ERROR || rv6 == DNS_STATUS_ERROR) { return DNS_STATUS_ERROR; } return DNS_STATUS_IDLE; From c487cd888f62cca7971a0e122f2f38198b5a6c3b Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 11 Dec 2016 10:42:54 +0900 Subject: [PATCH 13/15] nghttpx: Periodically remove expired DNS cache entries --- src/shrpx_dns_tracker.cc | 51 +++++++++++++++++++++++++++++++++++++++- src/shrpx_dns_tracker.h | 8 +++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/shrpx_dns_tracker.cc b/src/shrpx_dns_tracker.cc index 53d2c733..672917c4 100644 --- a/src/shrpx_dns_tracker.cc +++ b/src/shrpx_dns_tracker.cc @@ -28,9 +28,21 @@ namespace shrpx { -DNSTracker::DNSTracker(struct ev_loop *loop) : loop_(loop) {} +namespace { +void gccb(struct ev_loop *loop, ev_timer *w, int revents) { + auto dns_tracker = static_cast(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()) { @@ -94,6 +106,8 @@ int DNSTracker::resolve(Address *result, DNSQuery *dnsq) { ents_.emplace(host, make_entry(nullptr, std::move(host_copy), DNS_STATUS_ERROR, nullptr)); + start_gc_timer(); + return DNS_STATUS_ERROR; } @@ -107,6 +121,8 @@ int DNSTracker::resolve(Address *result, DNSQuery *dnsq) { 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: { @@ -118,6 +134,8 @@ int DNSTracker::resolve(Address *result, DNSQuery *dnsq) { 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: { @@ -127,6 +145,8 @@ int DNSTracker::resolve(Address *result, DNSQuery *dnsq) { 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); @@ -260,4 +280,33 @@ void DNSTracker::cancel(DNSQuery *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 diff --git a/src/shrpx_dns_tracker.h b/src/shrpx_dns_tracker.h index 455026ec..89100ea2 100644 --- a/src/shrpx_dns_tracker.h +++ b/src/shrpx_dns_tracker.h @@ -88,6 +88,10 @@ public: 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 resolv, @@ -100,6 +104,10 @@ private: void add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq); std::map 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_; }; From e007b6b031bf5581625967c396145ecd8727cbc7 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 11 Dec 2016 11:32:38 +0900 Subject: [PATCH 14/15] Add DNS integration tests --- integration-tests/nghttpx_http2_test.go | 72 +++++++++++++++++++++++++ integration-tests/server_tester.go | 27 ++++++++-- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go index 2c5e58bd..a0208329 100644 --- a/integration-tests/nghttpx_http2_test.go +++ b/integration-tests/nghttpx_http2_test.go @@ -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) { diff --git a/integration-tests/server_tester.go b/integration-tests/server_tester.go index bccf314b..69de18a7 100644 --- a/integration-tests/server_tester.go +++ b/integration-tests/server_tester.go @@ -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" From 22bd9fb530bfe265071fb178dbfc4b2566411552 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 11 Dec 2016 11:49:24 +0900 Subject: [PATCH 15/15] nghttpx: Set DNS cache expire date for error and ok statuses only --- src/shrpx_dns_tracker.cc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/shrpx_dns_tracker.cc b/src/shrpx_dns_tracker.cc index 672917c4..74ab83b0 100644 --- a/src/shrpx_dns_tracker.cc +++ b/src/shrpx_dns_tracker.cc @@ -65,7 +65,12 @@ ResolverEntry DNSTracker::make_entry(std::unique_ptr resolv, ent.resolv = std::move(resolv); ent.host = std::move(host); ent.status = status; - ent.expiry = ev_now(loop_) + dnsconf.timeout.cache; + switch (status) { + case DNS_STATUS_ERROR: + case DNS_STATUS_OK: + ent.expiry = ev_now(loop_) + dnsconf.timeout.cache; + break; + } if (result) { ent.result = *result; } @@ -75,8 +80,16 @@ ResolverEntry DNSTracker::make_entry(std::unique_ptr resolv, void DNSTracker::update_entry(ResolverEntry &ent, std::unique_ptr 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; }