text: #include "dns.h"
  #include "log.h"
  #include "str.h"
  #include "timer.h"
  #include "url.h"
  #include "util.h"
  
  struct dns_data {
    struct dns_data *next;
    struct mg_connection *c;
    int64_t expire;
    uint16_t txnid;
  };
  
  static struct dns_data *s_reqs;  // Active DNS requests
  
  static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
                           struct mg_dns *, bool);
  
  static void mg_dns_free(struct dns_data *d) {
    LIST_DELETE(struct dns_data, &s_reqs, d);
    free(d);
  }
  
  void mg_resolve_cancel(struct mg_connection *c) {
    struct dns_data *tmp, *d;
    for (d = s_reqs; d != NULL; d = tmp) {
      tmp = d->next;
      if (d->c == c) mg_dns_free(d);
    }
  }
  
  static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
                                        char *to, size_t tolen, int depth) {
    size_t i = 0, j = 0;
    if (tolen > 0) to[0] = '\0';
    if (depth > 5) return 0;
    while (ofs + i + 1 < len) {
      size_t n = s[ofs + i];
      if (n == 0) {
        i++;
        break;
      }
      if (n & 0xc0) {
        size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]);  // 12 is hdr len
        if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
            mg_dns_parse_name_depth(s, len, ptr, to, tolen, depth + 1) == 0)
          return 0;
        i += 2;
        break;
      }
      if (ofs + i + n + 1 >= len) return 0;
      if (j > 0) {
        if (j < tolen) to[j] = '.';
        j++;
      }
      if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
      j += n;
      i += n + 1;
      if (j < tolen) to[j] = '\0';  // Zero-terminate this chunk
    }
    if (tolen > 0) to[tolen - 1] = '\0';  // Make sure make sure it is nul-term
    return i;
  }
  
  static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
                                  char *dst, size_t dstlen) {
    return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0);
  }
  
  size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
                         bool is_question, struct mg_dns_rr *rr) {
    const uint8_t *s = buf + ofs, *e = &buf[len];
  
    memset(rr, 0, sizeof(*rr));
    if (len < sizeof(struct mg_dns_header)) return 0;  // Too small
    if (len > 512) return 0;  //  Too large, we don't expect that
    if (s >= e) return 0;     //  Overflow
  
    if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0)
      return 0;
    s += rr->nlen + 4;
    if (s > e) return 0;
    rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
    rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
    if (is_question) return (size_t) (rr->nlen + 4);
  
    s += 6;
    if (s > e) return 0;
    rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
    if (s + rr->alen > e) return 0;
    return (size_t) (rr->nlen + rr->alen + 10);
  }
  
  bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
    const struct mg_dns_header *h = (struct mg_dns_header *) buf;
    struct mg_dns_rr rr;
    size_t i, n, ofs = sizeof(*h);
    memset(dm, 0, sizeof(*dm));
  
    if (len < sizeof(*h)) return 0;                // Too small, headers dont fit
    if (mg_ntohs(h->num_questions) > 1) return 0;  // Sanity
    if (mg_ntohs(h->num_answers) > 10) return 0;   // Sanity
    dm->txnid = mg_ntohs(h->txnid);
  
    for (i = 0; i < mg_ntohs(h->num_questions); i++) {
      if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
      // MG_INFO(("Q %zu %zu", ofs, n));
      ofs += n;
    }
    for (i = 0; i < mg_ntohs(h->num_answers); i++) {
      // MG_INFO(("A -- %zu %zu %s", ofs, n, dm->name));
      if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false;
      mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
      ofs += n;
  
      if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
        dm->addr.is_ip6 = false;
        memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
        dm->resolved = true;
        break;  // Return success
      } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
        dm->addr.is_ip6 = true;
        memcpy(&dm->addr.ip6, &buf[ofs - 16], 16);
        dm->resolved = true;
        break;  // Return success
      }
    }
    return true;
  }
  
  static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
                     void *fn_data) {
    struct dns_data *d, *tmp;
    if (ev == MG_EV_POLL) {
      int64_t now = *(int64_t *) ev_data;
      for (d = s_reqs; d != NULL; d = tmp) {
        tmp = d->next;
        // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
        if (now > d->expire) mg_error(d->c, "DNS timeout");
      }
    } else if (ev == MG_EV_READ) {
      struct mg_dns_message dm;
      int resolved = 0;
      if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
        char *s = mg_hexdump(c->recv.buf, c->recv.len);
        MG_ERROR(("Unexpected DNS response:\n%s\n", s));
        free(s);
      } else {
        MG_VERBOSE(("%s %d", dm.name, dm.resolved));
        for (d = s_reqs; d != NULL; d = tmp) {
          tmp = d->next;
          // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
          if (dm.txnid != d->txnid) continue;
          if (d->c->is_resolving) {
            if (dm.resolved) {
              char buf[100];
              dm.addr.port = d->c->rem.port;  // Save port
              d->c->rem = dm.addr;            // Copy resolved address
              MG_DEBUG(("%lu %s resolved to %s", d->c->id, dm.name,
                        mg_ntoa(&d->c->rem, buf, sizeof(buf))));
              mg_connect_resolved(d->c);
  #if MG_ENABLE_IPV6
            } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0') {
              struct mg_str x = mg_str(dm.name);
              mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
  #endif
            } else {
              mg_error(d->c, "%s DNS lookup failed", dm.name);
            }
          } else {
            MG_ERROR(("%lu already resolved", d->c->id));
          }
          mg_dns_free(d);
          resolved = 1;
        }
      }
      if (!resolved) MG_ERROR(("stray DNS reply"));
      c->recv.len = 0;
    } else if (ev == MG_EV_CLOSE) {
      for (d = s_reqs; d != NULL; d = tmp) {
        tmp = d->next;
        mg_error(d->c, "DNS error");
        mg_dns_free(d);
      }
    }
    (void) fn_data;
  }
  
  static void mg_dns_send(struct mg_connection *c, const struct mg_str *name,
                          uint16_t txnid, bool ipv6) {
    struct {
      struct mg_dns_header header;
      uint8_t data[256];
    } pkt;
    size_t i, n;
    memset(&pkt, 0, sizeof(pkt));
    pkt.header.txnid = mg_htons(txnid);
    pkt.header.flags = mg_htons(0x100);
    pkt.header.num_questions = mg_htons(1);
    for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
      if (name->ptr[i] == '.' || i >= name->len) {
        pkt.data[n] = (uint8_t) (i - n);
        memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
        n = i + 1;
      }
      if (i >= name->len) break;
    }
    memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5);  // A query
    n += 5;
    if (ipv6) pkt.data[n - 3] = 0x1c;  // AAAA query
    // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6);  // AAAA query
    // n += 6;
    mg_send(c, &pkt, sizeof(pkt.header) + n);
  #if 0
    // Immediately after A query, send AAAA query. Whatever reply comes first,
    // we'll use it. Note: we cannot send two queries in a single packet.
    // https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query
    pkt.data[n - 3] = 0x1c;  // AAAA query
    mg_send(c, &pkt, sizeof(pkt.header) + n);
  #endif
  }
  
  static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
                           struct mg_dns *dnsc, bool ipv6) {
    struct dns_data *d = NULL;
    if (dnsc->url == NULL) {
      mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
    } else if (dnsc->c == NULL) {
      dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
      if (dnsc->c != NULL) {
        dnsc->c->pfn = dns_cb;
        // dnsc->c->is_hexdumping = 1;
      }
    }
    if (dnsc->c == NULL) {
      mg_error(c, "resolver");
    } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
      mg_error(c, "resolve OOM");
    } else {
      char buf[100];
      d->txnid = s_reqs ? (uint16_t) (s_reqs->txnid + 1) : 1;
      d->next = s_reqs;
      s_reqs = d;
      d->expire = mg_millis() + (int64_t) ms;
      d->c = c;
      c->is_resolving = 1;
      MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
                  name->ptr, mg_ntoa(&dnsc->c->rem, buf, sizeof(buf)), d->txnid));
      mg_dns_send(dnsc->c, name, d->txnid, ipv6);
    }
  }
  
  void mg_resolve(struct mg_connection *c, const char *url) {
    struct mg_str host = mg_url_host(url);
    c->rem.port = mg_htons(mg_url_port(url));
    if (mg_aton(host, &c->rem)) {
      // host is an IP address, do not fire name resolution
      mg_connect_resolved(c);
    } else {
      // host is not an IP, send DNS resolution request
      mg_sendnsreq(c, &host, c->mgr->dnstimeout, &c->mgr->dns4, false);
    }
  }
  