summaryrefslogtreecommitdiffstats
path: root/src/shrpx_dns_resolver.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/shrpx_dns_resolver.cc')
-rw-r--r--src/shrpx_dns_resolver.cc353
1 files changed, 353 insertions, 0 deletions
diff --git a/src/shrpx_dns_resolver.cc b/src/shrpx_dns_resolver.cc
new file mode 100644
index 0000000..f83ecb7
--- /dev/null
+++ b/src/shrpx_dns_resolver.cc
@@ -0,0 +1,353 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2016 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_dns_resolver.h"
+
+#include <cstring>
+#include <sys/time.h>
+
+#include "shrpx_log.h"
+#include "shrpx_connection.h"
+#include "shrpx_config.h"
+
+namespace shrpx {
+
+namespace {
+void sock_state_cb(void *data, int s, int read, int write) {
+ auto resolv = static_cast<DNSResolver *>(data);
+
+ if (resolv->get_status(nullptr) != DNSResolverStatus::RUNNING) {
+ return;
+ }
+
+ if (read) {
+ resolv->start_rev(s);
+ } else {
+ resolv->stop_rev(s);
+ }
+ if (write) {
+ resolv->start_wev(s);
+ } else {
+ resolv->stop_wev(s);
+ }
+}
+} // namespace
+
+namespace {
+void host_cb(void *arg, int status, int timeouts, hostent *hostent) {
+ auto resolv = static_cast<DNSResolver *>(arg);
+ resolv->on_result(status, hostent);
+}
+} // namespace
+
+namespace {
+void process_result(DNSResolver *resolv) {
+ auto cb = resolv->get_complete_cb();
+ if (!cb) {
+ return;
+ }
+ Address result;
+ auto status = resolv->get_status(&result);
+ switch (status) {
+ case DNSResolverStatus::OK:
+ case DNSResolverStatus::ERROR:
+ cb(status, &result);
+ break;
+ default:
+ break;
+ }
+ // resolv may be deleted here.
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_read(w->fd);
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_write(w->fd);
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto resolv = static_cast<DNSResolver *>(w->data);
+ resolv->on_timeout();
+ process_result(resolv);
+}
+} // namespace
+
+namespace {
+void stop_ev(struct ev_loop *loop,
+ const std::vector<std::unique_ptr<ev_io>> &evs) {
+ for (auto &w : evs) {
+ ev_io_stop(loop, w.get());
+ }
+}
+} // namespace
+
+DNSResolver::DNSResolver(struct ev_loop *loop)
+ : result_{},
+ loop_(loop),
+ channel_(nullptr),
+ family_(AF_UNSPEC),
+ status_(DNSResolverStatus::IDLE) {
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+}
+
+DNSResolver::~DNSResolver() {
+ if (channel_) {
+ ares_destroy(channel_);
+ }
+
+ stop_ev(loop_, revs_);
+ stop_ev(loop_, wevs_);
+
+ ev_timer_stop(loop_, &timer_);
+}
+
+int DNSResolver::resolve(const StringRef &name, int family) {
+ if (status_ != DNSResolverStatus::IDLE) {
+ return -1;
+ }
+
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Start resolving host " << name << " in IPv"
+ << (family == AF_INET ? "4" : "6");
+ }
+
+ name_ = name;
+ family_ = family;
+
+ int rv;
+
+ auto &dnsconf = get_config()->dns;
+
+ ares_options opts{};
+ opts.sock_state_cb = sock_state_cb;
+ opts.sock_state_cb_data = this;
+ opts.timeout = static_cast<int>(dnsconf.timeout.lookup * 1000);
+ opts.tries = dnsconf.max_try;
+
+ auto optmask = ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES;
+
+ ares_channel chan;
+ rv = ares_init_options(&chan, &opts, optmask);
+ if (rv != ARES_SUCCESS) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "ares_init_options failed: " << ares_strerror(rv);
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return -1;
+ }
+
+ channel_ = chan;
+ status_ = DNSResolverStatus::RUNNING;
+
+ ares_gethostbyname(channel_, name_.c_str(), family_, host_cb, this);
+ reset_timeout();
+
+ return 0;
+}
+
+int DNSResolver::on_read(int fd) { return handle_event(fd, ARES_SOCKET_BAD); }
+
+int DNSResolver::on_write(int fd) { return handle_event(ARES_SOCKET_BAD, fd); }
+
+int DNSResolver::on_timeout() {
+ return handle_event(ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+}
+
+int DNSResolver::handle_event(int rfd, int wfd) {
+ if (status_ == DNSResolverStatus::IDLE) {
+ return -1;
+ }
+
+ ares_process_fd(channel_, rfd, wfd);
+
+ switch (status_) {
+ case DNSResolverStatus::RUNNING:
+ reset_timeout();
+ return 0;
+ case DNSResolverStatus::OK:
+ return 0;
+ case DNSResolverStatus::ERROR:
+ return -1;
+ default:
+ // Unreachable
+ assert(0);
+ abort();
+ }
+}
+
+void DNSResolver::reset_timeout() {
+ if (status_ != DNSResolverStatus::RUNNING) {
+ return;
+ }
+ timeval tvout;
+ auto tv = ares_timeout(channel_, nullptr, &tvout);
+ if (tv == nullptr) {
+ return;
+ }
+ // To avoid that timer_.repeat becomes 0, which makes ev_timer_again
+ // useless, add tiny fraction of time.
+ timer_.repeat = tv->tv_sec + tv->tv_usec / 1000000. + 1e-9;
+ ev_timer_again(loop_, &timer_);
+}
+
+DNSResolverStatus DNSResolver::get_status(Address *result) const {
+ if (status_ != DNSResolverStatus::OK) {
+ return status_;
+ }
+
+ if (result) {
+ memcpy(result, &result_, sizeof(result_));
+ }
+
+ return status_;
+}
+
+namespace {
+void start_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
+ int fd, int event, IOCb cb, void *data) {
+ for (auto &w : evs) {
+ if (w->fd == fd) {
+ return;
+ }
+ }
+ for (auto &w : evs) {
+ if (w->fd == -1) {
+ ev_io_set(w.get(), fd, event);
+ ev_io_start(loop, w.get());
+ return;
+ }
+ }
+
+ auto w = std::make_unique<ev_io>();
+ ev_io_init(w.get(), cb, fd, event);
+ w->data = data;
+ ev_io_start(loop, w.get());
+ evs.emplace_back(std::move(w));
+}
+} // namespace
+
+namespace {
+void stop_ev(std::vector<std::unique_ptr<ev_io>> &evs, struct ev_loop *loop,
+ int fd, int event) {
+ for (auto &w : evs) {
+ if (w->fd == fd) {
+ ev_io_stop(loop, w.get());
+ ev_io_set(w.get(), -1, event);
+ return;
+ }
+ }
+}
+} // namespace
+
+void DNSResolver::start_rev(int fd) {
+ start_ev(revs_, loop_, fd, EV_READ, readcb, this);
+}
+
+void DNSResolver::stop_rev(int fd) { stop_ev(revs_, loop_, fd, EV_READ); }
+
+void DNSResolver::start_wev(int fd) {
+ start_ev(wevs_, loop_, fd, EV_WRITE, writecb, this);
+}
+
+void DNSResolver::stop_wev(int fd) { stop_ev(wevs_, loop_, fd, EV_WRITE); }
+
+void DNSResolver::on_result(int status, hostent *hostent) {
+ stop_ev(loop_, revs_);
+ stop_ev(loop_, wevs_);
+ ev_timer_stop(loop_, &timer_);
+
+ if (status != ARES_SUCCESS) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup for " << name_
+ << " failed: " << ares_strerror(status);
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return;
+ }
+
+ auto ap = *hostent->h_addr_list;
+ if (!ap) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup for " << name_ << "failed: no address returned";
+ }
+ status_ = DNSResolverStatus::ERROR;
+ return;
+ }
+
+ switch (hostent->h_addrtype) {
+ case AF_INET:
+ status_ = DNSResolverStatus::OK;
+ result_.len = sizeof(result_.su.in);
+ result_.su.in = {};
+ result_.su.in.sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_IN_SIN_LEN
+ result_.su.in.sin_len = sizeof(result_.su.in);
+#endif // HAVE_SOCKADDR_IN_SIN_LEN
+ memcpy(&result_.su.in.sin_addr, ap, sizeof(result_.su.in.sin_addr));
+ break;
+ case AF_INET6:
+ status_ = DNSResolverStatus::OK;
+ result_.len = sizeof(result_.su.in6);
+ result_.su.in6 = {};
+ result_.su.in6.sin6_family = AF_INET6;
+#ifdef HAVE_SOCKADDR_IN6_SIN6_LEN
+ result_.su.in6.sin6_len = sizeof(result_.su.in6);
+#endif // HAVE_SOCKADDR_IN6_SIN6_LEN
+ memcpy(&result_.su.in6.sin6_addr, ap, sizeof(result_.su.in6.sin6_addr));
+ break;
+ default:
+ assert(0);
+ }
+
+ if (status_ == DNSResolverStatus::OK) {
+ if (LOG_ENABLED(INFO)) {
+ LOG(INFO) << "Name lookup succeeded: " << name_ << " -> "
+ << util::numeric_name(&result_.su.sa, result_.len);
+ }
+ return;
+ }
+
+ status_ = DNSResolverStatus::ERROR;
+}
+
+void DNSResolver::set_complete_cb(CompleteCb cb) {
+ completeCb_ = std::move(cb);
+}
+
+CompleteCb DNSResolver::get_complete_cb() const { return completeCb_; }
+
+} // namespace shrpx