text: /** @file
  
    A brief file description
  
    @section license License
  
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at
  
        http://www.apache.org/licenses/LICENSE-2.0
  
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
   */
  
  #include "P_DNS.h" /* MAGIC_EDITING_TAG */
  #include "tscore/ink_inet.h"
  
  #ifdef SPLIT_DNS
  #include "I_SplitDNS.h"
  #endif
  
  #define SRV_COST (RRFIXEDSZ + 0)
  #define SRV_WEIGHT (RRFIXEDSZ + 2)
  #define SRV_PORT (RRFIXEDSZ + 4)
  #define SRV_SERVER (RRFIXEDSZ + 6)
  #define SRV_FIXEDSZ (RRFIXEDSZ + 6)
  
  EventType ET_DNS = ET_CALL;
  
  //
  // Config
  //
  int dns_timeout                      = DEFAULT_DNS_TIMEOUT;
  int dns_retries                      = DEFAULT_DNS_RETRIES;
  int dns_search                       = DEFAULT_DNS_SEARCH;
  int dns_failover_number              = DEFAULT_FAILOVER_NUMBER;
  int dns_failover_period              = DEFAULT_FAILOVER_PERIOD;
  int dns_failover_try_period          = DEFAULT_FAILOVER_TRY_PERIOD;
  int dns_max_dns_in_flight            = MAX_DNS_IN_FLIGHT;
  int dns_validate_qname               = 0;
  unsigned int dns_handler_initialized = 0;
  int dns_ns_rr                        = 0;
  int dns_ns_rr_init_down              = 1;
  char *dns_ns_list                    = nullptr;
  char *dns_resolv_conf                = nullptr;
  char *dns_local_ipv6                 = nullptr;
  char *dns_local_ipv4                 = nullptr;
  int dns_thread                       = 0;
  int dns_prefer_ipv6                  = 0;
  DNS_CONN_MODE dns_conn_mode          = DNS_CONN_MODE::UDP_ONLY;
  
  namespace
  {
  const int tcp_data_length_offset = 2;
  
  // Currently only used for A and AAAA.
  inline const char *
  QtypeName(int qtype)
  {
    return T_AAAA == qtype ? "AAAA" : T_A == qtype ? "A" : "*";
  }
  inline bool
  is_addr_query(int qtype)
  {
    return qtype == T_A || qtype == T_AAAA;
  }
  } // namespace
  
  DNSProcessor dnsProcessor;
  ClassAllocator<DNSEntry> dnsEntryAllocator("dnsEntryAllocator");
  // Users are expected to free these entries in short order!
  // We could page align this buffer to enable page flipping for recv...
  ClassAllocator<HostEnt> dnsBufAllocator("dnsBufAllocator", 2);
  
  //
  // Function Prototypes
  //
  static bool dns_process(DNSHandler *h, HostEnt *ent, int len);
  static DNSEntry *get_dns(DNSHandler *h, uint16_t id);
  // returns true when e is done
  static void dns_result(DNSHandler *h, DNSEntry *e, HostEnt *ent, bool retry, bool tcp_retry = false);
  static void write_dns(DNSHandler *h, bool tcp_retry = false);
  static bool write_dns_event(DNSHandler *h, DNSEntry *e, bool over_tcp = false);
  // "reliable" name to try. need to build up first.
  static int try_servers         = 0;
  static int local_num_entries   = 1;
  static int attempt_num_entries = 1;
  char try_server_names[DEFAULT_NUM_TRY_SERVER][MAXDNAME];
  
  static inline char *
  strnchr(char *s, char c, int len)
  {
    while (*s && *s != c && len) {
      ++s, --len;
    }
    return *s == c ? s : (char *)nullptr;
  }
  
  static inline uint16_t
  ink_get16(const uint8_t *src)
  {
    uint16_t dst;
  
    NS_GET16(dst, src);
    return dst;
  }
  
  static inline unsigned int
  get_rcode(char *buff)
  {
    return reinterpret_cast<HEADER *>(buff)->rcode;
  }
  
  static inline unsigned int
  get_rcode(HostEnt *ent)
  {
    return get_rcode(reinterpret_cast<char *>(ent->buf));
  }
  
  bool
  HostEnt::isNameError()
  {
    return get_rcode(this) == NXDOMAIN;
  }
  
  void
  HostEnt::free()
  {
    dnsBufAllocator.free(this);
  }
  
  void
  make_ipv4_ptr(in_addr_t addr, char *buffer)
  {
    char *p          = buffer;
    uint8_t const *u = reinterpret_cast<uint8_t *>(&addr);
  
    if (u[3] > 99) {
      *p++ = (u[3] / 100) + '0';
    }
    if (u[3] > 9) {
      *p++ = ((u[3] / 10) % 10) + '0';
    }
    *p++ = u[3] % 10 + '0';
    *p++ = '.';
    if (u[2] > 99) {
      *p++ = (u[2] / 100) + '0';
    }
    if (u[2] > 9) {
      *p++ = ((u[2] / 10) % 10) + '0';
    }
    *p++ = u[2] % 10 + '0';
    *p++ = '.';
    if (u[1] > 99) {
      *p++ = (u[1] / 100) + '0';
    }
    if (u[1] > 9) {
      *p++ = ((u[1] / 10) % 10) + '0';
    }
    *p++ = u[1] % 10 + '0';
    *p++ = '.';
    if (u[0] > 99) {
      *p++ = (u[0] / 100) + '0';
    }
    if (u[0] > 9) {
      *p++ = ((u[0] / 10) % 10) + '0';
    }
    *p++ = u[0] % 10 + '0';
    *p++ = '.';
    ink_strlcpy(p, "in-addr.arpa", MAXDNAME - (p - buffer + 1));
  }
  
  void
  make_ipv6_ptr(in6_addr const *addr, char *buffer)
  {
    const char hex_digit[] = "0123456789abcdef";
    char *p                = buffer;
    uint8_t const *src     = addr->s6_addr;
    int i;
  
    for (i = TS_IP6_SIZE - 1; i >= 0; --i) {
      *p++ = hex_digit[src[i] & 0x0f];
      *p++ = '.';
      *p++ = hex_digit[src[i] >> 4];
      *p++ = '.';
    }
  
    ink_strlcpy(p, "ip6.arpa", MAXDNAME - (p - buffer + 1));
  }
  
  //  Public functions
  //
  //  See documentation is header files and Memos
  //
  int
  DNSProcessor::start(int, size_t stacksize)
  {
    //
    // Read configuration
    //
    REC_EstablishStaticConfigInt32(dns_retries, "proxy.config.dns.retries");
    REC_EstablishStaticConfigInt32(dns_timeout, "proxy.config.dns.lookup_timeout");
    REC_EstablishStaticConfigInt32(dns_search, "proxy.config.dns.search_default_domains");
    REC_EstablishStaticConfigInt32(dns_failover_number, "proxy.config.dns.failover_number");
    REC_EstablishStaticConfigInt32(dns_failover_period, "proxy.config.dns.failover_period");
    REC_EstablishStaticConfigInt32(dns_max_dns_in_flight, "proxy.config.dns.max_dns_in_flight");
    REC_EstablishStaticConfigInt32(dns_validate_qname, "proxy.config.dns.validate_query_name");
    REC_EstablishStaticConfigInt32(dns_ns_rr, "proxy.config.dns.round_robin_nameservers");
    REC_ReadConfigStringAlloc(dns_ns_list, "proxy.config.dns.nameservers");
    REC_ReadConfigStringAlloc(dns_local_ipv4, "proxy.config.dns.local_ipv4");
    REC_ReadConfigStringAlloc(dns_local_ipv6, "proxy.config.dns.local_ipv6");
    REC_ReadConfigStringAlloc(dns_resolv_conf, "proxy.config.dns.resolv_conf");
    REC_EstablishStaticConfigInt32(dns_thread, "proxy.config.dns.dedicated_thread");
    int dns_conn_mode_i = 0;
    REC_EstablishStaticConfigInt32(dns_conn_mode_i, "proxy.config.dns.connection.mode");
    dns_conn_mode = static_cast<DNS_CONN_MODE>(dns_conn_mode_i);
  
    if (dns_thread > 0) {
      // TODO: Hmmm, should we just get a single thread some other way?
      ET_DNS                                  = eventProcessor.register_event_type("ET_DNS");
      NetHandler::active_thread_types[ET_DNS] = true;
      eventProcessor.schedule_spawn(&initialize_thread_for_net, ET_DNS);
      eventProcessor.spawn_event_threads(ET_DNS, 1, stacksize);
    } else {
      // Initialize the first event thread for DNS.
      ET_DNS = ET_CALL;
    }
    thread = eventProcessor.thread_group[ET_DNS]._thread[0];
  
    dns_failover_try_period = dns_timeout + 1; // Modify the "default" accordingly
  
    if (SplitDNSConfig::gsplit_dns_enabled) {
      // reconfigure after threads start
      SplitDNSConfig::reconfigure();
    }
  
    // Setup the default DNSHandler, it's used both by normal DNS, and SplitDNS (for PTR lookups etc.)
    dns_init();
    open();
  
    return 0;
  }
  
  void
  DNSProcessor::open(sockaddr const *target)
  {
    DNSHandler *h = new DNSHandler;
  
    h->mutex = thread->mutex;
    h->m_res = &l_res;
    ats_ip_copy(&h->local_ipv4.sa, &local_ipv4.sa);
    ats_ip_copy(&h->local_ipv6.sa, &local_ipv6.sa);
  
    if (target) {
      ats_ip_copy(&h->ip, target);
    } else {
      ats_ip_invalidate(&h->ip); // marked to use default.
    }
  
    if (!dns_handler_initialized) {
      handler = h;
    }
  
    SET_CONTINUATION_HANDLER(h, &DNSHandler::startEvent);
    thread->schedule_imm(h);
  }
  
  //
  // Initialization
  //
  void
  DNSProcessor::dns_init()
  {
    gethostname(try_server_names[0], 255);
    Debug("dns", "localhost=%s", try_server_names[0]);
    Debug("dns", "Round-robin nameservers = %d", dns_ns_rr);
  
    IpEndpoint nameserver[MAX_NAMED];
    size_t nserv = 0;
  
    if (dns_ns_list) {
      Debug("dns", "Nameserver list specified \"%s\"", dns_ns_list);
      int i;
      char *last;
      char *ns_list = ats_strdup(dns_ns_list);
      char *ns      = (char *)strtok_r(ns_list, " ,;\t\r", &last);
  
      for (i = 0, nserv = 0; (i < MAX_NAMED) && ns; ++i) {
        Debug("dns", "Nameserver list - parsing \"%s\"", ns);
        bool err    = false;
        int prt     = DOMAIN_SERVICE_PORT;
        char *colon = nullptr; // where the port colon is.
        // Check for IPv6 notation.
        if ('[' == *ns) {
          char *ndx = strchr(ns + 1, ']');
          if (ndx) {
            if (':' == ndx[1]) {
              colon = ndx + 1;
            }
          } else {
            err = true;
            Warning("Unmatched '[' in address for nameserver '%s', discarding.", ns);
          }
        } else {
          colon = strchr(ns, ':');
        }
  
        if (!err && colon) {
          *colon = '\0';
          // coverity[secure_coding]
          if (sscanf(colon + 1, "%d%*s", &prt) != 1) {
            Debug("dns", "Unable to parse port number '%s' for nameserver '%s', discardin.", colon + 1, ns);
            Warning("Unable to parse port number '%s' for nameserver '%s', discarding.", colon + 1, ns);
            err = true;
          }
        }
  
        if (!err && 0 != ats_ip_pton(ns, &nameserver[nserv].sa)) {
          Debug("dns", "Invalid IP address given for nameserver '%s', discarding", ns);
          Warning("Invalid IP address given for nameserver '%s', discarding", ns);
          err = true;
        }
  
        if (!err) {
          ip_port_text_buffer buff;
  
          ats_ip_port_cast(&nameserver[nserv].sa) = htons(prt);
  
          Debug("dns", "Adding nameserver %s to nameserver list", ats_ip_nptop(&nameserver[nserv].sa, buff, sizeof(buff)));
          ++nserv;
        }
  
        ns = (char *)strtok_r(nullptr, " ,;\t\r", &last);
      }
      ats_free(ns_list);
    }
    // The default domain (5th param) and search list (6th param) will
    // come from /etc/resolv.conf.
    if (ink_res_init(&l_res, nameserver, nserv, dns_search, nullptr, nullptr, dns_resolv_conf) < 0) {
      Warning("Failed to build DNS res records for the servers (%s).  Using resolv.conf.", dns_ns_list);
    }
  
    // Check for local forced bindings.
  
    if (dns_local_ipv6) {
      if (0 != ats_ip_pton(dns_local_ipv6, &local_ipv6)) {
        ats_ip_invalidate(&local_ipv6);
        Warning("Invalid IP address '%s' for dns.local_ipv6 value, discarding.", dns_local_ipv6);
      } else if (!ats_is_ip6(&local_ipv6.sa)) {
        ats_ip_invalidate(&local_ipv6);
        Warning("IP address '%s' for dns.local_ipv6 value was not IPv6, discarding.", dns_local_ipv6);
      }
    }
  
    if (dns_local_ipv4) {
      if (0 != ats_ip_pton(dns_local_ipv4, &local_ipv4)) {
        ats_ip_invalidate(&local_ipv4);
        Warning("Invalid IP address '%s' for dns.local_ipv4 value, discarding.", dns_local_ipv4);
      } else if (!ats_is_ip4(&local_ipv4.sa)) {
        ats_ip_invalidate(&local_ipv4);
        Warning("IP address '%s' for dns.local_ipv4 value was not IPv4, discarding.", dns_local_ipv4);
      }
    }
  }
  
  /**
    Inter-OS portability for dn_expand.  dn_expand() expands the compressed
    domain name comp_dn to a full domain name. Expanded names are converted
    to upper case. msg is a pointer to the beginning of the message,
    exp_dn is a pointer to a buffer of size length for the result. The
    size of compressed name is returned or -1 if there was an error.
  
  */
  inline int
  ink_dn_expand(const u_char *msg, const u_char *eom, const u_char *comp_dn, u_char *exp_dn, int length)
  {
    return ::dn_expand((unsigned char *)msg, (unsigned char *)eom, (unsigned char *)comp_dn, (char *)exp_dn, length);
  }
  
  DNSProcessor::DNSProcessor() : thread(nullptr), handler(nullptr)
  {
    ink_zero(l_res);
    ink_zero(local_ipv6);
    ink_zero(local_ipv4);
  }
  
  void
  DNSEntry::init(const char *x, int len, int qtype_arg, Continuation *acont, DNSProcessor::Options const &opt)
  {
    qtype          = qtype_arg;
    host_res_style = opt.host_res_style;
    if (is_addr_query(qtype)) {
      // adjust things based on family preference.
      if (HOST_RES_IPV4 == host_res_style || HOST_RES_IPV4_ONLY == host_res_style) {
        qtype = T_A;
      } else if (HOST_RES_IPV6 == host_res_style || HOST_RES_IPV6_ONLY == host_res_style) {
        qtype = T_AAAA;
      }
    }
    submit_time   = Thread::get_hrtime();
    action        = acont;
    submit_thread = acont->mutex->thread_holding;
  
  #ifdef SPLIT_DNS
    if (SplitDNSConfig::gsplit_dns_enabled) {
      dnsH = opt.handler ? opt.handler : dnsProcessor.handler;
    } else {
      dnsH = dnsProcessor.handler;
    }
  #else
    dnsH = dnsProcessor.handler;
  #endif // SPLIT_DNS
  
    dnsH->txn_lookup_timeout = opt.timeout;
  
    mutex = dnsH->mutex;
  
    if (is_addr_query(qtype) || qtype == T_SRV) {
      if (len) {
        len = len > (MAXDNAME - 1) ? (MAXDNAME - 1) : len;
        memcpy(qname, x, len);
        qname[len]     = 0;
        orig_qname_len = qname_len = len;
      } else {
        qname_len      = ink_strlcpy(qname, x, MAXDNAME);
        orig_qname_len = qname_len;
      }
    } else { // T_PTR
      IpAddr const *ip = reinterpret_cast<IpAddr const *>(x);
      if (ip->isIp6()) {
        make_ipv6_ptr(&ip->_addr._ip6, qname);
      } else if (ip->isIp4()) {
        make_ipv4_ptr(ip->_addr._ip4, qname);
      } else {
        ink_assert(!"T_PTR query to DNS must be IP address.");
      }
    }
  
    SET_HANDLER((DNSEntryHandler)&DNSEntry::mainEvent);
  }
  
  /**
   Open UDP and/or TCP connections based on dns_conn_mode
   */
  
  void
  DNSHandler::open_cons(sockaddr const *target, bool failed, int icon)
  {
    if (dns_conn_mode != DNS_CONN_MODE::TCP_ONLY) {
      open_con(target, failed, icon, false);
    }
    if (dns_conn_mode != DNS_CONN_MODE::UDP_ONLY) {
      open_con(target, failed, icon, true);
    }
  }
  
  /**
    Open (and close) connections as necessary and also assures that the
    epoll fd struct is properly updated.
  
  */
  void
  DNSHandler::open_con(sockaddr const *target, bool failed, int icon, bool over_tcp)
  {
    ip_port_text_buffer ip_text;
    PollDescriptor *pd = get_PollDescriptor(dnsProcessor.thread);
  
    if (!icon && target) {
      ats_ip_copy(&ip, target);
    } else if (!target) {
      target = &ip.sa;
    }
    DNSConnection &cur_con = over_tcp ? tcpcon[icon] : udpcon[icon];
  
    Debug("dns", "open_con: opening connection %s", ats_ip_nptop(target, ip_text, sizeof ip_text));
  
    if (cur_con.fd != NO_FD) { // Remove old FD from epoll fd
      cur_con.close();
    }
  
    if (cur_con.connect(target, DNSConnection::Options()
                                  .setNonBlockingConnect(true)
                                  .setNonBlockingIo(true)
                                  .setUseTcp(over_tcp)
                                  .setBindRandomPort(true)
                                  .setLocalIpv6(&local_ipv6.sa)
                                  .setLocalIpv4(&local_ipv4.sa)) < 0) {
      Debug("dns", "opening connection %s FAILED for %d", ip_text, icon);
      if (!failed) {
        if (dns_ns_rr) {
          rr_failure(icon);
        } else {
          failover();
        }
      }
      return;
    } else {
      ns_down[icon] = 0;
      if (cur_con.eio.start(pd, &cur_con, EVENTIO_READ) < 0) {
        Error("[iocore_dns] open_con: Failed to add %d server to epoll list\n", icon);
      } else {
        cur_con.num = icon;
        Debug("dns", "opening connection %s SUCCEEDED for %d", ip_text, icon);
      }
    }
  }
  
  void
  DNSHandler::validate_ip()
  {
    if (!ip.isValid()) {
      // Invalid, switch to default.
      // seems that res_init always sets m_res.nscount to at least 1!
      if (!m_res->nscount || !ats_ip_copy(&ip.sa, &m_res->nsaddr_list[0].sa)) {
        Warning("bad nameserver config, fallback to loopback");
        ip.setToLoopback(AF_INET);
      }
    }
  }
  /**
    Initial state of the DNSHandler. Can reinitialize the running DNS
    handler to a new nameserver.
  
  */
  int
  DNSHandler::startEvent(int /* event ATS_UNUSED */, Event *e)
  {
    //
    // If this is for the default server, get it
    Debug("dns", "DNSHandler::startEvent: on thread %d", e->ethread->id);
  
    this->validate_ip();
  
    if (!dns_handler_initialized) {
      //
      // If we are THE handler, open connection and configure for
      // periodic execution.
      //
      dns_handler_initialized = 1;
      SET_HANDLER(&DNSHandler::mainEvent);
      if (dns_ns_rr) {
        int max_nscount = m_res->nscount;
        if (max_nscount > MAX_NAMED) {
          max_nscount = MAX_NAMED;
        }
        n_con = 0;
        for (int i = 0; i < max_nscount; i++) {
          ip_port_text_buffer buff;
          sockaddr *sa = &m_res->nsaddr_list[i].sa;
          if (ats_is_ip(sa)) {
            open_cons(sa, false, n_con);
            ++n_con;
            Debug("dns_pas", "opened connection to %s, n_con = %d", ats_ip_nptop(sa, buff, sizeof(buff)), n_con);
          }
        }
        dns_ns_rr_init_down = 0;
      } else {
        open_cons(nullptr); // use current target address.
        n_con = 1;
      }
  
      return EVENT_CONT;
    } else {
      ink_assert(false); // I.e. this should never really happen
      return EVENT_DONE;
    }
  }
  
  /**
    Initial state of the DSNHandler. Can reinitialize the running DNS
    hander to a new nameserver.
  */
  int
  DNSHandler::startEvent_sdns(int /* event ATS_UNUSED */, Event *e)
  {
    Debug("dns", "DNSHandler::startEvent_sdns: on thread %d", e->ethread->id);
    this->validate_ip();
  
    SET_HANDLER(&DNSHandler::mainEvent);
    open_cons(&ip.sa, false, n_con);
    ++n_con; // TODO should n_con be zeroed?
  
    return EVENT_CONT;
  }
  
  static inline int
  _ink_res_mkquery(ink_res_state res, char *qname, int qtype, unsigned char *buffer, bool over_tcp = false)
  {
    int offset = over_tcp ? tcp_data_length_offset : 0;
    int r      = ink_res_mkquery(res, QUERY, qname, C_IN, qtype, nullptr, 0, nullptr, buffer + offset, MAX_DNS_PACKET_LEN);
    if (over_tcp) {
      NS_PUT16(r, buffer);
    }
    return r + offset;
  }
  
  void
  DNSHandler::recover()
  {
    ip_text_buffer buff;
    Warning("connection to DNS server %s restored", ats_ip_ntop(&ip.sa, buff, sizeof(buff)));
    name_server = 0;
    switch_named(name_server);
  }
  
  void
  DNSHandler::retry_named(int ndx, ink_hrtime t, bool reopen)
  {
    if (reopen && ((t - last_primary_reopen) > DNS_PRIMARY_REOPEN_PERIOD)) {
      Debug("dns", "retry_named: reopening DNS connection for index %d", ndx);
      last_primary_reopen = t;
      if (dns_conn_mode != DNS_CONN_MODE::TCP_ONLY) {
        udpcon[ndx].close();
      }
      if (dns_conn_mode != DNS_CONN_MODE::UDP_ONLY) {
        tcpcon[ndx].close();
      }
      open_cons(&m_res->nsaddr_list[ndx].sa, true, ndx);
    }
    bool over_tcp = dns_conn_mode == DNS_CONN_MODE::TCP_ONLY;
    int con_fd    = over_tcp ? tcpcon[ndx].fd : udpcon[ndx].fd;
    unsigned char buffer[MAX_DNS_PACKET_LEN];
    Debug("dns", "trying to resolve '%s' from DNS connection, ndx %d", try_server_names[try_servers], ndx);
    int r       = _ink_res_mkquery(m_res, try_server_names[try_servers], T_A, buffer, over_tcp);
    try_servers = (try_servers + 1) % countof(try_server_names);
    ink_assert(r >= 0);
    if (r >= 0) { // looking for a bounce
      int res = socketManager.send(con_fd, buffer, r, 0);
      Debug("dns", "ping result = %d", res);
    }
  }
  
  void
  DNSHandler::try_primary_named(bool reopen)
  {
    ink_hrtime t = Thread::get_hrtime();
    if (reopen && ((t - last_primary_reopen) > DNS_PRIMARY_REOPEN_PERIOD)) {
      Debug("dns", "try_primary_named: reopening primary DNS connection");
      last_primary_reopen = t;
      open_cons(&ip.sa, true, 0);
    }
    if ((t - last_primary_retry) > DNS_PRIMARY_RETRY_PERIOD) {
      unsigned char buffer[MAX_DNS_PACKET_LEN];
      bool over_tcp      = dns_conn_mode == DNS_CONN_MODE::TCP_ONLY;
      int con_fd         = over_tcp ? tcpcon[0].fd : udpcon[0].fd;
      last_primary_retry = t;
      Debug("dns", "trying to resolve '%s' from primary DNS connection", try_server_names[try_servers]);
      int r = _ink_res_mkquery(m_res, try_server_names[try_servers], T_A, buffer, over_tcp);
      // if try_server_names[] is not full, round-robin within the
      // filled entries.
      if (local_num_entries < DEFAULT_NUM_TRY_SERVER) {
        try_servers = (try_servers + 1) % local_num_entries;
      } else {
        try_servers = (try_servers + 1) % countof(try_server_names);
      }
      ink_assert(r >= 0);
      if (r >= 0) { // looking for a bounce
        int res = socketManager.send(con_fd, buffer, r, 0);
        Debug("dns", "ping result = %d", res);
      }
    }
  }
  
  void
  DNSHandler::switch_named(int ndx)
  {
    for (DNSEntry *e = entries.head; e; e = (DNSEntry *)e->link.next) {
      e->written_flag = false;
      if (e->retries < dns_retries) {
        ++(e->retries); // give them another chance
      }
    }
    in_flight = 0;
    received_one(ndx); // reset failover counters
  }
  
  /** Fail over to another name server. */
  void
  DNSHandler::failover()
  {
    Debug("dns", "failover: initiating failover attempt, current name_server=%d", name_server);
    // no hope, if we have only one server
    if (m_res->nscount > 1) {
      ip_text_buffer buff1, buff2;
      int max_nscount = m_res->nscount;
  
      if (max_nscount > MAX_NAMED) {
        max_nscount = MAX_NAMED;
      }
      sockaddr const *old_addr = &m_res->nsaddr_list[name_server].sa;
      name_server              = (name_server + 1) % max_nscount;
      Debug("dns", "failover: failing over to name_server=%d", name_server);
  
      IpEndpoint target;
      ats_ip_copy(&target.sa, &m_res->nsaddr_list[name_server].sa);
  
      Warning("failover: connection to DNS server %s lost, move to %s", ats_ip_ntop(old_addr, buff1, sizeof(buff1)),
              ats_ip_ntop(&target.sa, buff2, sizeof(buff2)));
  
      if (!target.isValid()) {
        target.setToLoopback(AF_INET);
      }
  
      open_cons(&target.sa, true, name_server);
      if (n_con <= name_server) {
        n_con = name_server + 1;
      }
      switch_named(name_server);
    } else {
      if (dns_conn_mode != DNS_CONN_MODE::TCP_ONLY) {
        udpcon[0].close();
      }
      if (dns_conn_mode != DNS_CONN_MODE::UDP_ONLY) {
        tcpcon[0].close();
      }
      ip_text_buffer buff;
      Warning("failover: connection to DNS server %s lost, retrying", ats_ip_ntop(&ip.sa, buff, sizeof(buff)));
    }
  }
  
  /** Mark one of the nameservers as down. */
  void
  DNSHandler::rr_failure(int ndx)
  {
    // no hope, if we have only one server
    if (!ns_down[ndx]) {
      ip_text_buffer buff;
      // mark this nameserver as down
      Debug("dns", "rr_failure: Marking nameserver %d as down", ndx);
      ns_down[ndx] = 1;
      Warning("connection to DNS server %s lost, marking as down", ats_ip_ntop(&m_res->nsaddr_list[ndx].sa, buff, sizeof(buff)));
    }
  
    int nscount = m_res->nscount;
    if (nscount > MAX_NAMED) {
      nscount = MAX_NAMED;
    }
  
    // See if all nameservers are down
    int all_down = 1;
  
    for (int i = 0; i < nscount && all_down; i++) {
      Debug("dns", "nsdown[%d]=%d", i, ns_down[i]);
      if (!ns_down[i]) {
        all_down = 0;
      }
    }
  
    if (all_down && !dns_ns_rr_init_down) {
      Warning("connection to all DNS servers lost, retrying");
      // actual retries will be done in retry_named called from mainEvent
      // mark any outstanding requests as not sent for later retry
      for (DNSEntry *e = entries.head; e; e = (DNSEntry *)e->link.next) {
        e->written_flag = false;
        if (e->retries < dns_retries) {
          ++(e->retries); // give them another chance
        }
        --in_flight;
        DNS_DECREMENT_DYN_STAT(dns_in_flight_stat);
      }
    } else {
      // move outstanding requests that were sent to this nameserver to another
      for (DNSEntry *e = entries.head; e; e = (DNSEntry *)e->link.next) {
        if (e->which_ns == ndx) {
          e->written_flag = false;
          if (e->retries < dns_retries) {
            ++(e->retries); // give them another chance
          }
          --in_flight;
          DNS_DECREMENT_DYN_STAT(dns_in_flight_stat);
        }
      }
    }
  }
  
  static bool
  good_rcode(char *buff)
  {
    unsigned int r = get_rcode(buff);
    return NOERROR == r || NXDOMAIN == r;
  }
  
  void
  DNSHandler::recv_dns(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
  {
    DNSConnection *dnsc = nullptr;
    ip_text_buffer ipbuff1, ipbuff2;
    Ptr<HostEnt> buf;
    while ((dnsc = (DNSConnection *)triggered.dequeue())) {
      while (true) {
        int res;
        IpEndpoint from_ip;
        socklen_t from_length = sizeof(from_ip);
        if (dnsc->opt._use_tcp) {
          if (dnsc->tcp_data.buf_ptr == nullptr) {
            dnsc->tcp_data.buf_ptr = make_ptr(dnsBufAllocator.alloc());
          }
          if (dnsc->tcp_data.total_length == 0) {
            // see if TS gets a two-byte size
            uint16_t tmp = 0;
            res          = socketManager.recv(dnsc->fd, &tmp, sizeof(tmp), MSG_PEEK);
            if (res == -EAGAIN || res == 0 || res == 1) {
              break;
            }
            if (res < 0) {
              goto Lerror;
            }
            // reading total size
            res = socketManager.recv(dnsc->fd, &(dnsc->tcp_data.total_length), sizeof(dnsc->tcp_data.total_length), 0);
            if (res == -EAGAIN) {
              break;
            }
            if (res <= 0) {
              goto Lerror;
            }
            dnsc->tcp_data.total_length = ntohs(dnsc->tcp_data.total_length);
            if (res != sizeof(dnsc->tcp_data.total_length) || dnsc->tcp_data.total_length > MAX_DNS_PACKET_LEN) {
              goto Lerror;
            }
          }
          // continue reading data
          void *buf_start = (char *)dnsc->tcp_data.buf_ptr->buf + dnsc->tcp_data.done_reading;
          res             = socketManager.recv(dnsc->fd, buf_start, dnsc->tcp_data.total_length - dnsc->tcp_data.done_reading, 0);
          if (res == -EAGAIN || res == 0) {
            break;
          }
          if (res < 0) {
            goto Lerror;
          }
          Debug("dns", "received packet size = %d over TCP", res);
          dnsc->tcp_data.done_reading += res;
          if (dnsc->tcp_data.done_reading < dnsc->tcp_data.total_length) {
            break;
          }
          buf = dnsc->tcp_data.buf_ptr;
          res = dnsc->tcp_data.total_length;
          dnsc->tcp_data.reset();
          goto Lsuccess;
        }
  
        if (!hostent_cache) {
          hostent_cache = dnsBufAllocator.alloc();
        }
  
        res = socketManager.recvfrom(dnsc->fd, hostent_cache->buf, MAX_DNS_PACKET_LEN, 0, &from_ip.sa, &from_length);
        Debug("dns", "DNSHandler::recv_dns res = [%d]", res);
        if (res == -EAGAIN) {
          break;
        }
        if (res <= 0) {
        Lerror:
          Debug("dns", "named error: %d", res);
          if (dns_ns_rr) {
            rr_failure(dnsc->num);
          } else if (dnsc->num == name_server) {
            failover();
          }
          break;
        }
  
        // verify that this response came from the correct server
        if (!ats_ip_addr_eq(&dnsc->ip.sa, &from_ip.sa)) {
          Warning("unexpected DNS response from %s (expected %s)", ats_ip_ntop(&from_ip.sa, ipbuff1, sizeof ipbuff1),
                  ats_ip_ntop(&dnsc->ip.sa, ipbuff2, sizeof ipbuff2));
          continue;
        }
        buf              = hostent_cache;
        hostent_cache    = nullptr;
        buf->packet_size = res;
        Debug("dns", "received packet size = %d", res);
      Lsuccess:
        if (dns_ns_rr) {
          Debug("dns", "round-robin: nameserver %d DNS response code = %d", dnsc->num, get_rcode(buf->buf));
          if (good_rcode(buf->buf)) {
            received_one(dnsc->num);
            if (ns_down[dnsc->num]) {
              Warning("connection to DNS server %s restored",
                      ats_ip_ntop(&m_res->nsaddr_list[dnsc->num].sa, ipbuff1, sizeof ipbuff1));
              ns_down[dnsc->num] = 0;
            }
          }
        } else {
          if (!dnsc->num) {
            Debug("dns", "primary DNS response code = %d", get_rcode(buf->buf));
            if (good_rcode(buf->buf)) {
              if (name_server) {
                recover();
              } else {
                received_one(name_server);
              }
            }
          }
        }
        if (dns_process(this, buf.get(), res)) {
          if (dnsc->num == name_server) {
            received_one(name_server);
          }
        }
      }
    }
  }
  
  /** Main event for the DNSHandler. Attempt to read from and write to named. */
  int
  DNSHandler::mainEvent(int event, Event *e)
  {
    recv_dns(event, e);
    if (dns_ns_rr) {
      ink_hrtime t = Thread::get_hrtime();
      if (t - last_primary_retry > DNS_PRIMARY_RETRY_PERIOD) {
        for (int i = 0; i < n_con; i++) {
          if (ns_down[i]) {
            Debug("dns", "mainEvent: nameserver = %d is down", i);
            retry_named(i, t, true);
          }
        }
        last_primary_retry = t;
      }
      for (int i = 0; i < n_con; i++) {
        if (!ns_down[i] && failover_soon(i)) {
          Debug("dns", "mainEvent: nameserver = %d failover soon", name_server);
          if (failover_now(i)) {
            rr_failure(i);
          } else {
            Debug("dns", "mainEvent: nameserver = %d no failover now - retrying", i);
            retry_named(i, t, false);
            ++failover_soon_number[i];
          }
        }
      }
    } else {
      if (failover_soon(name_server)) {
        Debug("dns", "mainEvent: will failover soon");
        if (failover_now(name_server)) {
          Debug("dns", "mainEvent: failing over now to another nameserver");
          failover();
        } else {
          try_primary_named(false);
          ++failover_soon_number[name_server];
        }
      } else if (name_server) { // not on the primary named
        try_primary_named(true);
      }
    }
  
    if (entries.head) {
      write_dns(this);
    }
  
    if (std::any_of(ns_down, ns_down + n_con, [](int f) { return f != 0; })) {
      this_ethread()->schedule_at(this, DNS_PRIMARY_RETRY_PERIOD);
    }
  
    return EVENT_CONT;
  }
  
  /** Find a DNSEntry by id. */
  inline static DNSEntry *
  get_dns(DNSHandler *h, uint16_t id)
  {
    for (DNSEntry *e = h->entries.head; e; e = (DNSEntry *)e->link.next) {
      if (e->once_written_flag) {
        for (int j : e->id) {
          if (j == id) {
            return e;
          } else if (j < 0) {
            goto Lnext;
          }
        }
      }
    Lnext:;
    }
    return nullptr;
  }
  
  /** Find a DNSEntry by query name and type. */
  inline static DNSEntry *
  get_entry(DNSHandler *h, char *qname, int qtype)
  {
    for (DNSEntry *e = h->entries.head; e; e = (DNSEntry *)e->link.next) {
      if (e->qtype == qtype) {
        if (is_addr_query(qtype)) {
          if (!strcmp(qname, e->qname)) {
            return e;
          }
        } else if (0 == memcmp(qname, e->qname, e->qname_len)) {
          return e;
        }
      }
    }
    return nullptr;
  }
  
  /** Write up to dns_max_dns_in_flight entries. */
  static void
  write_dns(DNSHandler *h, bool tcp_retry)
  {
    ProxyMutex *mutex = h->mutex.get();
    DNS_INCREMENT_DYN_STAT(dns_total_lookups_stat);
    int max_nscount = h->m_res->nscount;
    if (max_nscount > MAX_NAMED) {
      max_nscount = MAX_NAMED;
    }
    if (max_nscount <= 0) {
      Warning("There is no name server found in the resolv.conf");
      if (h->entries.head) {
        dns_result(h, h->entries.head, nullptr, false);
      }
      return;
    }
  
    if (h->in_write_dns) {
      return;
    }
    h->in_write_dns = true;
    bool over_tcp   = (dns_conn_mode == DNS_CONN_MODE::TCP_ONLY) || ((dns_conn_mode == DNS_CONN_MODE::TCP_RETRY) && tcp_retry);
    // Debug("dns", "in_flight: %d, dns_max_dns_in_flight: %d", h->in_flight, dns_max_dns_in_flight);
    if (h->in_flight < dns_max_dns_in_flight) {
      DNSEntry *e = h->entries.head;
      while (e) {
        DNSEntry *n = (DNSEntry *)e->link.next;
        if (!e->written_flag) {
          if (dns_ns_rr) {
            int ns_start = h->name_server;
            do {
              h->name_server = (h->name_server + 1) % max_nscount;
            } while (h->ns_down[h->name_server] && h->name_server != ns_start);
          }
          if (h->ns_down[h->name_server] || !write_dns_event(h, e, over_tcp)) {
            break;
          }
        }
        if (h->in_flight >= dns_max_dns_in_flight) {
          break;
        }
        e = n;
      }
    }
    h->in_write_dns = false;
  }
  
  uint16_t
  DNSHandler::get_query_id()
  {
    uint16_t q1, q2;
    q2 = q1 = (uint16_t)(generator.random() & 0xFFFF);
    if (query_id_in_use(q2)) {
      uint16_t i = q2 >> 6;
      while (qid_in_flight[i] == UINT64_MAX) {
        if (++i == sizeof(qid_in_flight) / sizeof(uint64_t)) {
          i = 0;
        }
        if (i == q1 >> 6) {
          Error("[iocore_dns] get_query_id: Exhausted all DNS query ids");
          return q1;
        }
      }
      i <<= 6;
      q2 &= 0x3F;
      while (query_id_in_use(i + q2)) {
        ++q2;
        q2 &= 0x3F;
        if (q2 == (q1 & 0x3F)) {
          Error("[iocore_dns] get_query_id: Exhausted all DNS query ids");
          return q1;
        }
      }
      q2 += i;
    }
  
    set_query_id_in_use(q2);
    return q2;
  }
  
  /**
    Construct and Write the request for a single entry (using send(3N)).
  
    @return true = keep going, false = give up for now.
  
  */
  static bool
  write_dns_event(DNSHandler *h, DNSEntry *e, bool over_tcp)
  {
    ProxyMutex *mutex = h->mutex.get();
    unsigned char buffer[MAX_DNS_PACKET_LEN];
    int offset     = over_tcp ? tcp_data_length_offset : 0;
    HEADER *header = (HEADER *)(buffer + offset);
    int r          = 0;
  
    if ((r = _ink_res_mkquery(h->m_res, e->qname, e->qtype, buffer, over_tcp)) <= 0) {
      Debug("dns", "cannot build query: %s", e->qname);
      dns_result(h, e, nullptr, false);
      return true;
    }
  
    uint16_t i = h->get_query_id();
    header->id = htons(i);
    if (e->id[dns_retries - e->retries] >= 0) {
      // clear previous id in case named was switched or domain was expanded
      h->release_query_id(e->id[dns_retries - e->retries]);
    }
    e->id[dns_retries - e->retries] = i;
    int con_fd                      = over_tcp ? h->tcpcon[h->name_server].fd : h->udpcon[h->name_server].fd;
    Debug("dns", "send query (qtype=%d) for %s to fd %d", e->qtype, e->qname, con_fd);
  
    int s = socketManager.send(con_fd, buffer, r, 0);
    if (s != r) {
      Debug("dns", "send() failed: qname = %s, %d != %d, nameserver= %d", e->qname, s, r, h->name_server);
      // changed if condition from 'r < 0' to 's < 0' - 8/2001 pas
      if (s < 0) {
        if (dns_ns_rr) {
          h->rr_failure(h->name_server);
        } else {
          h->failover();
        }
      }
      return false;
    }
  
    e->written_flag      = true;
    e->which_ns          = h->name_server;
    e->once_written_flag = true;
    ++h->in_flight;
    DNS_INCREMENT_DYN_STAT(dns_in_flight_stat);
  
    e->send_time = Thread::get_hrtime();
  
    if (e->timeout) {
      e->timeout->cancel();
    }
  
    if (h->txn_lookup_timeout) {
      e->timeout = h->mutex->thread_holding->schedule_in(e, HRTIME_MSECONDS(h->txn_lookup_timeout)); // this is in msec
    } else {
      e->timeout = h->mutex->thread_holding->schedule_in(e, HRTIME_SECONDS(dns_timeout));
    }
  
    Debug("dns", "sent qname = %s, id = %u, nameserver = %d", e->qname, e->id[dns_retries - e->retries], h->name_server);
    h->sent_one();
    return true;
  }
  
  int
  DNSEntry::delayEvent(int event, Event *e)
  {
    (void)event;
    if (dnsProcessor.handler) {
      SET_HANDLER((DNSEntryHandler)&DNSEntry::mainEvent);
      return handleEvent(EVENT_IMMEDIATE, e);
    }
    e->schedule_in(DNS_DELAY_PERIOD);
    return EVENT_CONT;
  }
  
  /** Handle timeout events. */
  int
  DNSEntry::mainEvent(int event, Event *e)
  {
    switch (event) {
    default:
      ink_assert(!"bad case");
      return EVENT_DONE;
    case EVENT_IMMEDIATE: {
      if (!dnsH) {
        dnsH = dnsProcessor.handler;
      }
      if (!dnsH) {
        Debug("dns", "handler not found, retrying...");
        SET_HANDLER((DNSEntryHandler)&DNSEntry::delayEvent);
        return handleEvent(event, e);
      }
  
      // trailing '.' indicates no domain expansion
      if (dns_search && ('.' != qname[orig_qname_len - 1])) {
        domains = dnsH->m_res->dnsrch;
        // start domain expansion straight away
        // if lookup name has no '.'
        if (domains && !strnchr(qname, '.', MAXDNAME)) {
          qname[orig_qname_len] = '.';
          qname_len = orig_qname_len + 1 + ink_strlcpy(qname + orig_qname_len + 1, *domains, MAXDNAME - (orig_qname_len + 1));
          ++domains;
        }
      } else {
        domains = nullptr;
      }
      Debug("dns", "enqueing query %s", qname);
      DNSEntry *dup = get_entry(dnsH, qname, qtype);
      if (dup) {
        Debug("dns", "collapsing NS request");
        dup->dups.enqueue(this);
      } else {
        Debug("dns", "adding first to collapsing queue");
        dnsH->entries.enqueue(this);
        write_dns(dnsH);
      }
      return EVENT_DONE;
    }
    case EVENT_INTERVAL:
      Debug("dns", "timeout for query %s", qname);
      if (dnsH->txn_lookup_timeout) {
        timeout = nullptr;
        dns_result(dnsH, this, result_ent.get(), false); // do not retry -- we are over TXN timeout on DNS alone!
        return EVENT_DONE;
      }
      if (written_flag) {
        Debug("dns", "marking %s as not-written", qname);
        written_flag = false;
        --(dnsH->in_flight);
        DNS_DECREMENT_DYN_STAT(dns_in_flight_stat);
      }
      timeout = nullptr;
      dns_result(dnsH, this, result_ent.get(), true);
      return EVENT_DONE;
    }
  }
  
  Action *
  DNSProcessor::getby(const char *x, int len, int type, Continuation *cont, Options const &opt)
  {
    Debug("dns", "received query %s type = %d, timeout = %d", x, type, opt.timeout);
    if (type == T_SRV) {
      Debug("dns_srv", "DNSProcessor::getby attempting an SRV lookup for %s, timeout = %d", x, opt.timeout);
    }
    DNSEntry *e = dnsEntryAllocator.alloc();
    e->retries  = dns_retries;
    e->init(x, len, type, cont, opt);
    MUTEX_TRY_LOCK(lock, e->mutex, this_ethread());
    if (!lock.is_locked()) {
      thread->schedule_imm(e);
    } else {
      e->handleEvent(EVENT_IMMEDIATE, nullptr);
    }
    return &e->action;
  }
  
  /**
    We have a result for an entry, return it to the user or retry if it
    is a retry-able and we have retries left.
  */
  static void
  dns_result(DNSHandler *h, DNSEntry *e, HostEnt *ent, bool retry, bool tcp_retry)
  {
    ProxyMutex *mutex = h->mutex.get();
    bool cancelled    = (e->action.cancelled ? true : false);
    retry             = retry || tcp_retry;
  
    if ((!ent || !ent->good) && !cancelled) {
      // try to retry operation
      if (retry && e->retries) {
        Debug("dns", "doing retry for %s", e->qname);
  
        DNS_INCREMENT_DYN_STAT(dns_retries_stat);
  
        --(e->retries);
        write_dns(h, tcp_retry);
        return;
      } else if (e->domains && *e->domains) {
        do {
          Debug("dns", "domain extending, last tried '%s', original '%.*s'", e->qname, e->orig_qname_len, e->qname);
  
          // Make sure the next try fits
          if (e->orig_qname_len + strlen(*e->domains) + 2 > MAXDNAME) {
            Debug("dns", "domain too large %.*s + %s", e->orig_qname_len, e->qname, *e->domains);
          } else {
            e->qname[e->orig_qname_len] = '.';
            e->qname_len =
              e->orig_qname_len + 1 + ink_strlcpy(e->qname + e->orig_qname_len + 1, *e->domains, MAXDNAME - (e->orig_qname_len + 1));
            ++(e->domains);
            e->retries = dns_retries;
            Debug("dns", "new name = %s retries = %d", e->qname, e->retries);
            write_dns(h, tcp_retry);
  
            return;
          }
  
          // Try another one
          ++(e->domains);
        } while (*e->domains);
      } else {
        e->qname[e->qname_len] = 0;
        if (!strchr(e->qname, '.') && !e->last) {
          e->last = true;
          write_dns(h, tcp_retry);
          return;
        }
      }
      if (retry) {
        DNS_INCREMENT_DYN_STAT(dns_max_retries_exceeded_stat);
      }
    }
    if (ent == BAD_DNS_RESULT) {
      ent = nullptr;
    }
    if (!cancelled) {
      if (!ent || !ent->good) {
        DNS_SUM_DYN_STAT(dns_fail_time_stat, Thread::get_hrtime() - e->submit_time);
      } else {
        DNS_SUM_DYN_STAT(dns_success_time_stat, Thread::get_hrtime() - e->submit_time);
      }
    }
    h->entries.remove(e);
  
    if (is_debug_tag_set("dns")) {
      if (is_addr_query(e->qtype)) {
        ip_text_buffer buff;
        const char *ptr    = "<none>";
        const char *result = "FAIL";
        if (ent && ent->good) {
          result = "SUCCESS";
          ptr    = inet_ntop(e->qtype == T_AAAA ? AF_INET6 : AF_INET, ent->ent.h_addr_list[0], buff, sizeof(buff));
        }
        Debug("dns", "%s result for %s = %s retry %d", result, e->qname, ptr, retry);
      } else {
        if (ent && ent->good) {
          Debug("dns", "SUCCESS result for %s = %s af=%d retry %d", e->qname, ent->ent.h_name, ent->ent.h_addrtype, retry);
        } else {
          Debug("dns", "FAIL result for %s = <not found> retry %d", e->qname, retry);
        }
      }
    }
  
    if (ent && ent->good) {
      DNS_INCREMENT_DYN_STAT(dns_lookup_success_stat);
    } else {
      DNS_INCREMENT_DYN_STAT(dns_lookup_fail_stat);
    }
  
    DNSEntry *dup = nullptr;
    while ((dup = e->dups.dequeue())) {
      if (dup->post(h, ent)) {
        e->dups.enqueue(dup);
        goto Lretry;
      }
    }
  
    if (e->timeout) {
      e->timeout->cancel(e);
      e->timeout = nullptr;
    }
    e->result_ent = ent;
  
    if (h->mutex->thread_holding == e->submit_thread) {
      MUTEX_TRY_LOCK(lock, e->action.mutex, h->mutex->thread_holding);
      if (!lock.is_locked()) {
        Debug("dns", "failed lock for result %s", e->qname);
        goto Lretry;
      }
      for (int i : e->id) {
        if (i < 0) {
          break;
        }
        h->release_query_id(i);
      }
      e->postEvent(0, nullptr);
    } else {
      for (int i : e->id) {
        if (i < 0) {
          break;
        }
        h->release_query_id(i);
      }
      e->mutex = e->action.mutex;
      SET_CONTINUATION_HANDLER(e, &DNSEntry::postEvent);
      e->submit_thread->schedule_imm_signal(e);
    }
    return;
  Lretry:
    e->result_ent = ent;
    e->retries    = 0;
    if (e->timeout) {
      e->timeout->cancel();
    }
    e->timeout = h->mutex->thread_holding->schedule_in(e, MUTEX_RETRY_DELAY);
  }
  
  int
  DNSEntry::post(DNSHandler *h, HostEnt *ent)
  {
    if (timeout) {
      timeout->cancel(this);
      timeout = nullptr;
    }
    result_ent = ent;
    if (h->mutex->thread_holding == submit_thread) {
      MUTEX_TRY_LOCK(lock, action.mutex, h->mutex->thread_holding);
      if (!lock.is_locked()) {
        Debug("dns", "failed lock for result %s", qname);
        return 1;
      }
      postEvent(0, nullptr);
    } else {
      mutex = action.mutex;
      SET_HANDLER(&DNSEntry::postEvent);
      submit_thread->schedule_imm_signal(this);
    }
    return 0;
  }
  
  int
  DNSEntry::postEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
  {
    if (!action.cancelled) {
      Debug("dns", "called back continuation for %s", qname);
      action.continuation->handleEvent(DNS_EVENT_LOOKUP, result_ent.get());
    }
    result_ent   = nullptr;
    action.mutex = nullptr;
    mutex        = nullptr;
    dnsEntryAllocator.free(this);
    return EVENT_DONE;
  }
  
  /** Decode the reply from "named". */
  static bool
  dns_process(DNSHandler *handler, HostEnt *buf, int len)
  {
    ProxyMutex *mutex = handler->mutex.get();
    HEADER *h         = (HEADER *)(buf->buf);
    DNSEntry *e       = get_dns(handler, (uint16_t)ntohs(h->id));
    bool retry        = false;
    bool tcp_retry    = false;
    bool server_ok    = true;
    uint32_t temp_ttl = 0;
  
    //
    // Do we have an entry for this id?
    //
    if (!e || !e->written_flag) {
      Debug("dns", "unknown DNS id = %u", (uint16_t)ntohs(h->id));
      return false; // cannot count this as a success
    }
    //
    // It is no longer in flight
    //
    e->written_flag = false;
    --(handler->in_flight);
    DNS_DECREMENT_DYN_STAT(dns_in_flight_stat);
  
    DNS_SUM_DYN_STAT(dns_response_time_stat, Thread::get_hrtime() - e->send_time);
  
    // retrying over TCP when truncated is set
    if (dns_conn_mode == DNS_CONN_MODE::TCP_RETRY && h->tc == 1) {
      Debug("dns", "Retrying DNS query over TCP for [%s]", e->qname);
      tcp_retry = true;
      goto Lerror;
    }
  
    if (h->rcode != NOERROR || !h->ancount) {
      Debug("dns", "received rcode = %d", h->rcode);
      switch (h->rcode) {
      default:
        Warning("Unknown DNS error %d for [%s]", h->rcode, e->qname);
        retry     = true;
        server_ok = false; // could be server problems
        goto Lerror;
      case SERVFAIL: // recoverable error
        retry = true;
        break;
      case FORMERR: // unrecoverable errors
      case REFUSED:
      case NOTIMP:
        Debug("dns", "DNS error %d for [%s]", h->rcode, e->qname);
        server_ok = false; // could be server problems
        goto Lerror;
      case NOERROR:
      case NXDOMAIN:
      case 6:  // YXDOMAIN
      case 7:  // YXRRSET
      case 8:  // NOTAUTH
      case 9:  // NOTAUTH
      case 10: // NOTZONE
        Debug("dns", "DNS error %d for [%s]", h->rcode, e->qname);
        goto Lerror;
      }
    } else {
      //
      // Initialize local data
      //
      //    struct in_addr host_addr;            unused
      u_char tbuf[MAXDNAME + 1];
      buf->ent.h_name = nullptr;
  
      int ancount       = ntohs(h->ancount);
      unsigned char *bp = buf->hostbuf;
      int buflen        = sizeof(buf->hostbuf);
      u_char *cp        = ((u_char *)h) + HFIXEDSZ;
      u_char *eom       = (u_char *)h + len;
      int n;
      ink_assert(buf->srv_hosts.hosts.size() == 0 && buf->srv_hosts.srv_hosts_length == 0);
      buf->srv_hosts.hosts.clear();
      buf->srv_hosts.srv_hosts_length = 0;
      int rname_len                   = -1;
  
      Debug("dns", "Got %d DNS records for [%s]", ancount, e->qname);
      //
      // Expand name
      //
      if ((n = ink_dn_expand((u_char *)h, eom, cp, bp, buflen)) < 0) {
        goto Lerror;
      }
  
      // Should we validate the query name?
      if (dns_validate_qname) {
        int qlen = e->qname_len;
        int rlen = strlen((char *)bp);
  
        rname_len = rlen; // Save for later use
        if ((qlen > 0) && ('.' == e->qname[qlen - 1])) {
          --qlen;
        }
        if ((rlen > 0) && ('.' == bp[rlen - 1])) {
          --rlen;
        }
        // TODO: At some point, we might want to care about the case here, and use an algorithm
        // to randomly pick upper case characters in the query, and validate the response with
        // case sensitivity.
        if ((qlen != rlen) || (strncasecmp(e->qname, (const char *)bp, qlen) != 0)) {
          // Bad mojo, forged?
          Warning("received DNS response with query name of '%s', but response query name is '%s'", e->qname, bp);
          goto Lerror;
        } else {
          Debug("dns", "query name validated properly for %s", e->qname);
        }
      }
  
      cp += n + QFIXEDSZ;
      if (is_addr_query(e->qtype)) {
        if (-1 == rname_len) {
          n = strlen((char *)bp) + 1;
        } else {
          n = rname_len + 1;
        }
        buf->ent.h_name = (char *)bp;
        bp += n;
        buflen -= n;
      }
      //
      // Configure HostEnt data structure
      //
      u_char **ap          = buf->host_aliases;
      buf->ent.h_aliases   = (char **)buf->host_aliases;
      u_char **hap         = (u_char **)buf->h_addr_ptrs;
      *hap                 = nullptr;
      buf->ent.h_addr_list = (char **)buf->h_addr_ptrs;
  
      //
      // INKqa10938: For customer (i.e. USPS) with closed environment, need to
      // build up try_server_names[] with names already successfully resolved.
      // try_server_names[] gets filled up with every success dns response.
      // Once it's full, a new entry get inputted into try_server_names round-
      // robin style every 50 success dns response.
  
      // TODO: Why do we do strlen(e->qname) ? That should be available in
      // e->qname_len, no ?
      if (local_num_entries >= DEFAULT_NUM_TRY_SERVER) {
        if ((attempt_num_entries % 50) == 0) {
          try_servers = (try_servers + 1) % countof(try_server_names);
          ink_strlcpy(try_server_names[try_servers], e->qname, MAXDNAME);
          memset(&try_server_names[try_servers][strlen(e->qname)], 0, 1);
          attempt_num_entries = 0;
        }
        ++attempt_num_entries;
      } else {
        // fill up try_server_names for try_primary_named
        try_servers = local_num_entries++;
        ink_strlcpy(try_server_names[try_servers], e->qname, MAXDNAME);
        memset(&try_server_names[try_servers][strlen(e->qname)], 0, 1);
      }
  
      /* added for SRV support [ebalsa]
         this skips the query section (qdcount)
       */
      unsigned char *here = (unsigned char *)buf->buf + HFIXEDSZ;
      if (e->qtype == T_SRV) {
        for (int ctr = ntohs(h->qdcount); ctr > 0; ctr--) {
          int strlen = dn_skipname(here, eom);
          here += strlen + QFIXEDSZ;
        }
      }
      //
      // Decode each answer
      //
      int answer = false, error = false;
  
      while (ancount-- > 0 && cp < eom && !error) {
        n = ink_dn_expand((u_char *)h, eom, cp, bp, buflen);
        if (n < 0) {
          ++error;
          break;
        }
        cp += n;
        short int type;
        NS_GET16(type, cp);
        cp += NS_INT16SZ;       // NS_GET16(cls, cp);
        NS_GET32(temp_ttl, cp); // NOTE: this is not a "long" but 32-bits (from nameser_compat.h)
        if ((temp_ttl < buf->ttl) || (buf->ttl == 0)) {
          buf->ttl = temp_ttl;
        }
        NS_GET16(n, cp);
  
        //
        // Decode cname
        //
        if ((is_addr_query(e->qtype) || e->qtype == T_SRV) && (type == T_CNAME || type == T_DNAME)) {
          if (ap >= &buf->host_aliases[DNS_MAX_ALIASES - 1]) {
            continue;
          }
          n = ink_dn_expand((u_char *)h, eom, cp, tbuf, sizeof(tbuf));
          if (n < 0) {
            ++error;
            break;
          }
          cp += n;
          *ap++ = (unsigned char *)bp;
          n     = strlen((char *)bp) + 1;
          bp += n;
          buflen -= n;
          n = strlen((char *)tbuf) + 1;
          if (n > buflen) {
            ++error;
            break;
          }
          ink_strlcpy((char *)bp, (char *)tbuf, buflen);
          bp += n;
          buflen -= n;
          if (is_debug_tag_set("dns")) {
            switch (type) {
            case T_CNAME:
              Debug("dns", "received cname = %s", tbuf);
              break;
            case T_DNAME:
              Debug("dns", "received dname = %s", tbuf);
              break;
            }
          }
          continue;
        }
        if (e->qtype != type) {
          ++error;
          break;
        }
        //
        // Decode names
        //
        if (type == T_PTR) {
          n = ink_dn_expand((u_char *)h, eom, cp, bp, buflen);
          if (n < 0) {
            ++error;
            break;
          }
          cp += n;
          if (!answer) {
            buf->ent.h_name = (char *)bp;
            Debug("dns", "received PTR name = %s", bp);
            n = strlen((char *)bp) + 1;
            bp += n;
            buflen -= n;
          } else if (ap < &buf->host_aliases[DNS_MAX_ALIASES - 1]) {
            *ap++ = bp;
            Debug("dns", "received PTR alias = %s", bp);
            n = strlen((char *)bp) + 1;
            bp += n;
            buflen -= n;
          }
        } else if (type == T_SRV) {
          if (buf->srv_hosts.hosts.size() >= hostdb_round_robin_max_count) {
            break;
          }
          cp         = here; /* hack */
          int strlen = dn_skipname(cp, eom);
          cp += strlen;
          const unsigned char *srv_off = cp;
          cp += SRV_FIXEDSZ;
          cp += dn_skipname(cp, eom);
          here = cp; /* hack */
  
          SRV srv;
  
          // expand the name
          n = ink_dn_expand((u_char *)h, eom, srv_off + SRV_SERVER, (u_char *)srv.host, MAXDNAME);
          if (n < 0) {
            ++error;
            break;
          }
          Debug("dns_srv", "Discovered SRV record [from NS lookup] with cost:%d weight:%d port:%d with host:%s",
                ink_get16(srv_off + SRV_COST), ink_get16(srv_off + SRV_WEIGHT), ink_get16(srv_off + SRV_PORT), srv.host);
  
          srv.port     = ink_get16(srv_off + SRV_PORT);
          srv.priority = ink_get16(srv_off + SRV_COST);
          srv.weight   = ink_get16(srv_off + SRV_WEIGHT);
          srv.host_len = ::strlen(srv.host) + 1;
          srv.key      = makeHostHash(srv.host);
  
          if (srv.host[0] != '\0') {
            buf->srv_hosts.srv_hosts_length += srv.host_len;
          } else {
            continue;
          }
          buf->srv_hosts.hosts.push_back(srv);
        } else if (is_addr_query(type)) {
          if (answer) {
            if (n != buf->ent.h_length) {
              cp += n;
              continue;
            }
          } else {
            int nn;
            buf->ent.h_length   = n;
            buf->ent.h_addrtype = T_A == type ? AF_INET : AF_INET6;
            buf->ent.h_name     = (char *)bp;
            nn                  = strlen((char *)bp) + 1;
            Debug("dns", "received %s name = %s", QtypeName(type), bp);
            bp += nn;
            buflen -= nn;
          }
          // attempt to use the original buffer (if it is word aligned)
          if (!(((uintptr_t)cp) % sizeof(unsigned int))) {
            *hap++ = cp;
            cp += n;
          } else {
            ip_text_buffer ip_string;
            bp = (unsigned char *)align_pointer_forward(bp, sizeof(int));
            if (bp + n >= buf->hostbuf + DNS_HOSTBUF_SIZE) {
              ++error;
              break;
            }
            memcpy((*hap++ = bp), cp, n);
            Debug("dns", "received %s = %s", QtypeName(type),
                  inet_ntop(T_AAAA == type ? AF_INET6 : AF_INET, bp, ip_string, sizeof(ip_string)));
            bp += n;
            cp += n;
          }
        } else {
          goto Lerror;
        }
        ++answer;
      }
      if (answer) {
        *ap  = nullptr;
        *hap = nullptr;
        //
        // If the named didn't send us the name, insert the one
        // the user gave us...
        //
        if (!buf->ent.h_name) {
          Debug("dns", "inserting name = %s", e->qname);
          ink_strlcpy((char *)bp, e->qname, sizeof(buf->hostbuf) - (bp - buf->hostbuf));
          buf->ent.h_name = (char *)bp;
        }
        Debug("dns", "Returning %d DNS records for [%s]", answer, e->qname);
        dns_result(handler, e, buf, retry);
        return server_ok;
      }
    }
  Lerror:;
    DNS_INCREMENT_DYN_STAT(dns_lookup_fail_stat);
    buf->good = false;
    dns_result(handler, e, buf, retry, tcp_retry);
    return server_ok;
  }
  
  RecRawStatBlock *dns_rsb;
  
  void
  ink_dns_init(ModuleVersion v)
  {
    static int init_called = 0;
  
    Debug("dns", "ink_dns_init: called with init_called = %d", init_called);
  
    ink_release_assert(!checkModuleVersion(v, HOSTDB_MODULE_VERSION));
    if (init_called) {
      return;
    }
  
    init_called = 1;
    // do one time stuff
    // create a stat block for HostDBStats
    dns_rsb = RecAllocateRawStatBlock((int)DNS_Stat_Count);
  
    //
    // Register statistics callbacks
    //
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.total_dns_lookups", RECD_INT, RECP_PERSISTENT,
                       (int)dns_total_lookups_stat, RecRawStatSyncSum);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.lookup_avg_time", RECD_INT, RECP_PERSISTENT,
                       (int)dns_response_time_stat, RecRawStatSyncHrTimeAvg);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.success_avg_time", RECD_INT, RECP_NON_PERSISTENT,
                       (int)dns_success_time_stat, RecRawStatSyncHrTimeAvg);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.lookup_successes", RECD_INT, RECP_PERSISTENT,
                       (int)dns_lookup_success_stat, RecRawStatSyncSum);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.fail_avg_time", RECD_INT, RECP_PERSISTENT, (int)dns_fail_time_stat,
                       RecRawStatSyncHrTimeAvg);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.lookup_failures", RECD_INT, RECP_PERSISTENT,
                       (int)dns_lookup_fail_stat, RecRawStatSyncSum);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.retries", RECD_INT, RECP_PERSISTENT, (int)dns_retries_stat,
                       RecRawStatSyncSum);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.max_retries_exceeded", RECD_INT, RECP_PERSISTENT,
                       (int)dns_max_retries_exceeded_stat, RecRawStatSyncSum);
  
    RecRegisterRawStat(dns_rsb, RECT_PROCESS, "proxy.process.dns.in_flight", RECD_INT, RECP_NON_PERSISTENT, (int)dns_in_flight_stat,
                       RecRawStatSyncSum);
  }
  
  #ifdef TS_HAS_TESTS
  struct DNSRegressionContinuation;
  using DNSRegContHandler = int (DNSRegressionContinuation::*)(int, void *);
  
  struct DNSRegressionContinuation : public Continuation {
    int hosts;
    const char **hostnames;
    int type;
    int *status;
    int found;
    int tofind;
    int i;
    RegressionTest *test;
  
    int
    mainEvent(int event, HostEnt *he)
    {
      (void)event;
      if (event == DNS_EVENT_LOOKUP) {
        if (he) {
          struct in_addr in;
          ++found;
          in.s_addr = *(unsigned int *)he->ent.h_addr_list[0];
          rprintf(test, "host %s [%s] = %s\n", hostnames[i - 1], he->ent.h_name, inet_ntoa(in));
        } else {
          rprintf(test, "host %s not found\n", hostnames[i - 1]);
        }
      }
      if (i < hosts) {
        dnsProcessor.gethostbyname(this, hostnames[i], DNSProcessor::Options().setHostResStyle(HOST_RES_IPV4_ONLY));
        ++i;
        return EVENT_CONT;
      } else {
        if (found == tofind) {
          *status = REGRESSION_TEST_PASSED;
        } else {
          *status = REGRESSION_TEST_FAILED;
        }
        return EVENT_DONE;
      }
    }
  
    DNSRegressionContinuation(int ahosts, int atofind, const char **ahostnames, RegressionTest *t, int atype, int *astatus)
      : Continuation(new_ProxyMutex()),
        hosts(ahosts),
        hostnames(ahostnames),
        type(atype),
        status(astatus),
        found(0),
        tofind(atofind),
        i(0),
        test(t)
    {
      SET_HANDLER((DNSRegContHandler)&DNSRegressionContinuation::mainEvent);
    }
  };
  
  static const char *dns_test_hosts[] = {"www.apple.com", "www.ibm.com", "www.microsoft.com", "www.coke.com"};
  
  REGRESSION_TEST(DNS)(RegressionTest *t, int atype, int *pstatus)
  {
    eventProcessor.schedule_in(new DNSRegressionContinuation(4, 4, dns_test_hosts, t, atype, pstatus), HRTIME_SECONDS(1));
  }
  
  #endif
  