diff options
Diffstat (limited to 'src/shrpx_dns_resolver.cc')
-rw-r--r-- | src/shrpx_dns_resolver.cc | 353 |
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 |