From 4dff353282f62806bfb2dafff6b5b3d7cb8fabbb Mon Sep 17 00:00:00 2001
From: Craig McQueen <craig.mcqueen@innerrange.com>
Date: Wed, 5 Dec 2018 14:26:38 +1100
Subject: [PATCH] DNS: Initial support for other RR lookups

Add a new function dns_getrr() to do a lookup of other RR types.

No caching of data is done.

Note, for mDNS look-ups, only the first response is processed. This is a limitation that limits its usefulness for DNS-SD queries (e.g. doing a PTR lookup of _http._tcp.local.). It should be expanded to process multiple responses, with a timeout and/or a "cancel" function as needed.

In future, it would be good to add a function specifically for doing reverse DNS look-ups. E.g. looking up the hostname for 192.168.1.2, it would do a PTR look-up for 2.1.168.192.in-addr.arpa. A flag would be needed to select between DNS or mDNS.

This change is functional, but I'm not happy with the readability and maintainability of the code. I would like to refactor it if possible, to split it into more succinct, comprehensible and maintainable functions.
---
 src/core/dns.c              | 460 +++++++++++++++++++++++++++++++-----
 src/include/lwip/dns.h      |  69 +++++-
 src/include/lwip/opt.h      |   5 +
 src/include/lwip/prot/dns.h |   1 +
 4 files changed, 475 insertions(+), 60 deletions(-)

diff --git a/src/core/dns.c b/src/core/dns.c
index 121ea12b..7a647f14 100644
--- a/src/core/dns.c
+++ b/src/core/dns.c
@@ -178,12 +178,26 @@ static u16_t dns_txid;
 #define LWIP_DNS_SET_ADDRTYPE(x, y)
 #endif /* LWIP_IPV4 && LWIP_IPV6 */
 
+#define LWIP_DNS_ADDRTYPE_MATCH_RRTYPE(reqaddrtype, rrtype) \
+            ((LWIP_DNS_ADDRTYPE_IS_IPV6(reqaddrtype) && (rrtype == PP_HTONS(DNS_RRTYPE_AAAA))) || \
+             (!LWIP_DNS_ADDRTYPE_IS_IPV6(reqaddrtype) && (rrtype == PP_HTONS(DNS_RRTYPE_A))))
+
 #if LWIP_DNS_SUPPORT_MDNS_QUERIES
 #define LWIP_DNS_ISMDNS_ARG(x) , x
 #else
 #define LWIP_DNS_ISMDNS_ARG(x)
 #endif
 
+#if LWIP_DNS_SUPPORT_OTHER_RR
+#define LWIP_DNS_RR_ARG(x) , x
+#define LWIP_DNS_RR_OR_ZERO(x) x
+#define LWIP_DNS_SET_RR(x, y) do { x = y; } while(0)
+#else
+#define LWIP_DNS_RR_ARG(x)
+#define LWIP_DNS_RR_OR_ZERO(x) 0
+#define LWIP_DNS_SET_RR(x, y)
+#endif
+
 /** DNS query message structure.
     No packing needed: only used locally on the stack. */
 struct dns_query {
@@ -216,6 +230,8 @@ typedef enum {
   DNS_STATE_DONE             = 3
 } dns_state_enum_t;
 
+typedef void (*dns_found_generic_callback)(const char *name, const void *result, void *callback_arg);
+
 /** DNS table entry */
 struct dns_table_entry {
   u32_t ttl;
@@ -236,13 +252,16 @@ struct dns_table_entry {
 #if LWIP_DNS_SUPPORT_MDNS_QUERIES
   u8_t is_mdns;
 #endif
+#if LWIP_DNS_SUPPORT_OTHER_RR
+  u8_t rr_type;
+#endif
 };
 
 /** DNS request table entry: used when dns_gehostbyname cannot answer the
  * request from the DNS table */
 struct dns_req_entry {
   /* pointer to callback on DNS query done */
-  dns_found_callback found;
+  dns_found_generic_callback found;
   /* argument passed to the callback function */
   void *arg;
 #if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
@@ -251,6 +270,9 @@ struct dns_req_entry {
 #if LWIP_IPV4 && LWIP_IPV6
   u8_t reqaddrtype;
 #endif /* LWIP_IPV4 && LWIP_IPV6 */
+#if LWIP_DNS_SUPPORT_OTHER_RR
+  u8_t rr_type;
+#endif
 };
 
 #if DNS_LOCAL_HOSTLIST
@@ -284,7 +306,10 @@ static err_t dns_lookup_local(const char *hostname, size_t hostnamelen, ip_addr_
 /* forward declarations */
 static void dns_recv(void *s, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
 static void dns_check_entries(void);
-static void dns_call_found(u8_t idx, ip_addr_t *addr);
+#if LWIP_IPV4 && LWIP_IPV6
+static void dns_update_addr(u8_t idx, const ip_addr_t *addr);
+#endif
+static void dns_call_found(u8_t idx, const void *response);
 
 /*-----------------------------------------------------------------------------
  * Globals
@@ -717,44 +742,100 @@ dns_compare_name(const char *query, struct pbuf *p, u16_t start_offset)
 }
 
 /**
- * Walk through a compact encoded DNS name and return the end of the name.
+ * Get or skip over a compact encoded DNS name
+ *
+ * Return the index of the end of the encoded name.
  *
  * @param p pbuf containing the name
  * @param query_idx start index into p pointing to encoded DNS name in the DNS server response
+ * @param dst
  * @return index to end of the name
  */
 static u16_t
-dns_skip_name(struct pbuf *p, u16_t query_idx)
+dns_get_name(struct pbuf *p, u16_t query_idx LWIP_DNS_RR_ARG(char * dst) LWIP_DNS_RR_ARG(size_t dstlen))
 {
   int n;
   u16_t offset = query_idx;
+#if LWIP_DNS_SUPPORT_OTHER_RR
+  u16_t start_idx = query_idx;
+  u16_t previous_idx = 0;
+  u16_t result = 0;
+#endif
 
-  do {
+  for (;;) {
+#if LWIP_DNS_SUPPORT_OTHER_RR
+    if (previous_idx && offset >= previous_idx) {
+      return 0xFFFF;
+    }
+#endif
     n = pbuf_try_get_at(p, offset++);
     if ((n < 0) || (offset == 0)) {
       return 0xFFFF;
     }
+    if (n == 0) {
+      break;
+    }
     /** @see RFC 1035 - 4.1.4. Message compression */
     if ((n & 0xc0) == 0xc0) {
-      /* Compressed name: since we only want to skip it (not check it), stop here */
+      /* Compressed name */
+#if LWIP_DNS_SUPPORT_OTHER_RR
+      u16_t new_idx;
+      new_idx = ((u8_t)n & 0x3f) << 8;
+      n = pbuf_try_get_at(p, offset++);
+      if ((n < 0) || (offset == 0)) {
+        return 0xFFFF;
+      }
+      new_idx |= (u8_t)n;
+      if (result == 0) {
+        result = offset;
+      }
+      // Prevent endless loops.
+      if ((previous_idx && new_idx >= previous_idx) || new_idx >= start_idx) {
+        return 0xFFFF;
+      }
+      previous_idx = offset - 2;
+      offset = new_idx;
+      start_idx = new_idx;
+#else
+      /* since we only want to skip it (not check it or get it), stop here */
+      offset++;
+      if (offset == 0) {
+          return 0xFFFF;
+      }
       break;
+#endif
     } else {
       /* Not compressed name */
       if (offset + n >= p->tot_len) {
         return 0xFFFF;
       }
+#if LWIP_DNS_SUPPORT_OTHER_RR
+      if (dst) {
+        if (dstlen < (u8_t)n + 1u) {
+          return 0xFFFF;
+        }
+        pbuf_copy_partial(p, dst, n, offset);
+        dst += n;
+        *dst++ = '.';
+        dstlen -= n + 1;
+      }
+#endif
       offset = (u16_t)(offset + n);
     }
-    n = pbuf_try_get_at(p, offset);
-    if (n < 0) {
+  }
+
+#if LWIP_DNS_SUPPORT_OTHER_RR
+  if (dst) {
+    if (dstlen == 0) {
       return 0xFFFF;
     }
-  } while (n != 0);
-
-  if (offset == 0xFFFF) {
-    return 0xFFFF;
+    *dst = '\0';
   }
-  return (u16_t)(offset + 1);
+  if (result) {
+    offset = result;
+  }
+#endif
+  return offset;
 }
 
 /**
@@ -828,6 +909,11 @@ dns_send(u8_t idx)
     query_idx++;
 
     /* fill dns query */
+#if LWIP_DNS_SUPPORT_OTHER_RR
+    if (entry->rr_type) {
+      qry.type = PP_HTONS(entry->rr_type);
+    } else
+#endif
     if (LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype)) {
       qry.type = PP_HTONS(DNS_RRTYPE_AAAA);
     } else {
@@ -949,22 +1035,17 @@ dns_alloc_pcb(void)
 }
 #endif /* ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0) */
 
+
+#if LWIP_IPV4 && LWIP_IPV6
 /**
- * dns_call_found() - call the found callback and check if there are duplicate
- * entries for the given hostname. If there are any, their found callback will
- * be called and they will be removed.
+ * dns_update_addr() - update table entry for the address found
  *
  * @param idx dns table index of the entry that is resolved or removed
  * @param addr IP address for the hostname (or NULL on error or memory shortage)
  */
 static void
-dns_call_found(u8_t idx, ip_addr_t *addr)
+dns_update_addr(u8_t idx, const ip_addr_t *addr)
 {
-#if ((LWIP_DNS_SECURE & (LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT)) != 0)
-  u8_t i;
-#endif
-
-#if LWIP_IPV4 && LWIP_IPV6
   if (addr != NULL) {
     /* check that address type matches the request and adapt the table entry */
     if (IP_IS_V6_VAL(*addr)) {
@@ -975,21 +1056,48 @@ dns_call_found(u8_t idx, ip_addr_t *addr)
       dns_table[idx].reqaddrtype = LWIP_DNS_ADDRTYPE_IPV4;
     }
   }
+}
 #endif /* LWIP_IPV4 && LWIP_IPV6 */
 
+
+/**
+ * dns_call_found() - call the found callback and check if there are duplicate
+ * entries for the given hostname. If there are any, their found callback will
+ * be called and they will be removed.
+ *
+ * @param idx dns table index of the entry that is resolved or removed
+ * @param addr IP address for the hostname (or NULL on error or memory shortage)
+ */
+static void
+dns_call_found(u8_t idx, const void *response)
+{
+#if ((LWIP_DNS_SECURE & (LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT)) != 0)
+  u8_t i;
+#endif
+
 #if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
   for (i = 0; i < DNS_MAX_REQUESTS; i++) {
     if (dns_requests[i].found && (dns_requests[i].dns_table_idx == idx)) {
-      (*dns_requests[i].found)(dns_table[idx].name, addr, dns_requests[i].arg);
-      /* flush this entry */
-      dns_requests[i].found = NULL;
+      (*dns_requests[i].found)(dns_table[idx].name, response, dns_requests[i].arg);
+#if LWIP_DNS_SUPPORT_OTHER_RR
+      if (dns_requests[i].rr_type == 0 || response == NULL)
+#endif
+      {
+        /* flush this entry */
+        dns_requests[i].found = NULL;
+      }
     }
   }
 #else
   if (dns_requests[idx].found) {
-    (*dns_requests[idx].found)(dns_table[idx].name, addr, dns_requests[idx].arg);
+    (*dns_requests[idx].found)(dns_table[idx].name, response, dns_requests[idx].arg);
+#if LWIP_DNS_SUPPORT_OTHER_RR
+    if (dns_requests[i].rr_type == 0 || response == NULL)
+#endif
+    {
+      dns_requests[idx].found = NULL;
+    }
   }
-  dns_requests[idx].found = NULL;
 #endif
 #if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
   /* close the pcb used unless other request are using it */
@@ -997,20 +1105,25 @@ dns_call_found(u8_t idx, ip_addr_t *addr)
     if (i == idx) {
       continue; /* only check other requests */
     }
-    if (dns_table[i].state == DNS_STATE_ASKING) {
-      if (dns_table[i].pcb_idx == dns_table[idx].pcb_idx) {
-        /* another request is still using the same pcb */
+#if LWIP_DNS_SUPPORT_OTHER_RR
+    if (dns_requests[i].rr_type == 0 || response == NULL)
+#endif
+    {
+      if (dns_table[i].state == DNS_STATE_ASKING) {
+        if (dns_table[i].pcb_idx == dns_table[idx].pcb_idx) {
+          /* another request is still using the same pcb */
+          dns_table[idx].pcb_idx = DNS_MAX_SOURCE_PORTS;
+          break;
+        }
+      }
+      if (dns_table[idx].pcb_idx < DNS_MAX_SOURCE_PORTS) {
+        /* if we come here, the pcb is not used any more and can be removed */
+        udp_remove(dns_pcbs[dns_table[idx].pcb_idx]);
+        dns_pcbs[dns_table[idx].pcb_idx] = NULL;
         dns_table[idx].pcb_idx = DNS_MAX_SOURCE_PORTS;
-        break;
       }
     }
   }
-  if (dns_table[idx].pcb_idx < DNS_MAX_SOURCE_PORTS) {
-    /* if we come here, the pcb is not used any more and can be removed */
-    udp_remove(dns_pcbs[dns_table[idx].pcb_idx]);
-    dns_pcbs[dns_table[idx].pcb_idx] = NULL;
-    dns_table[idx].pcb_idx = DNS_MAX_SOURCE_PORTS;
-  }
 #endif
 }
 
@@ -1168,7 +1281,15 @@ dns_correct_response(u8_t idx, u32_t ttl)
   if (entry->ttl > DNS_MAX_TTL) {
     entry->ttl = DNS_MAX_TTL;
   }
-  dns_call_found(idx, &entry->ipaddr);
+#if LWIP_IPV4 && LWIP_IPV6
+  dns_update_addr(idx, &entry->ipaddr);
+#endif
+#if LWIP_DNS_SUPPORT_OTHER_RR
+  if (entry->rr_type == 0)
+#endif
+  {
+    dns_call_found(idx, &entry->ipaddr);
+  }
 
   if (entry->ttl == 0) {
     /* RFC 883, page 29: "Zero values are
@@ -1195,6 +1316,9 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
   struct dns_answer ans;
   struct dns_query qry;
   u16_t nquestions, nanswers;
+#if LWIP_DNS_SUPPORT_OTHER_RR
+  u16_t nextrarr;
+#endif
 
   LWIP_UNUSED_ARG(arg);
   LWIP_UNUSED_ARG(pcb);
@@ -1204,22 +1328,31 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
   if (p->tot_len < (SIZEOF_DNS_HDR + SIZEOF_DNS_QUERY)) {
     LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too small\n"));
     /* free pbuf and return */
-    goto ignore_packet;
+    pbuf_free(p);
+    return;
   }
 
   /* copy dns payload inside static buffer for processing */
   if (pbuf_copy_partial(p, &hdr, SIZEOF_DNS_HDR, 0) == SIZEOF_DNS_HDR) {
     /* Match the ID in the DNS header with the name table. */
-    txid = lwip_htons(hdr.id);
+    txid = lwip_ntohs(hdr.id);
     for (i = 0; i < DNS_TABLE_SIZE; i++) {
       struct dns_table_entry *entry = &dns_table[i];
       if ((entry->state == DNS_STATE_ASKING) &&
           (entry->txid == txid)) {
 
-        /* We only care about the question(s) and the answers. The authrr
-           and the extrarr are simply discarded. */
-        nquestions = lwip_htons(hdr.numquestions);
-        nanswers   = lwip_htons(hdr.numanswers);
+        /* For IP address look-ups, we only care about the question(s) and the
+           answers. The authrr and the extrarr are simply discarded.
+           But for other RR type look-ups, we _are_ interested in extrarr. */
+        nquestions = lwip_ntohs(hdr.numquestions);
+        nanswers   = lwip_ntohs(hdr.numanswers);
+#if LWIP_DNS_SUPPORT_OTHER_RR
+        if (entry->rr_type) {
+          nextrarr = lwip_ntohs(hdr.numextrarr);
+        } else {
+          nextrarr = 0;
+        }
+#endif
 
         /* Check for correct response. */
         if ((hdr.flags1 & DNS_FLAG1_RESPONSE) == 0) {
@@ -1255,8 +1388,7 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
           goto ignore_packet; /* ignore this packet */
         }
         if ((qry.cls != PP_HTONS(DNS_RRCLASS_IN)) ||
-            (LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype) && (qry.type != PP_HTONS(DNS_RRTYPE_AAAA))) ||
-            (!LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype) && (qry.type != PP_HTONS(DNS_RRTYPE_A)))) {
+            (!LWIP_DNS_RR_OR_ZERO(entry->rr_type) && !LWIP_DNS_ADDRTYPE_MATCH_RRTYPE(entry->reqaddrtype, qry.type))) {
           LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", entry->name));
           goto ignore_packet; /* ignore this packet */
         }
@@ -1284,9 +1416,15 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
             goto ignore_packet;
           }
         } else {
-          while ((nanswers > 0) && (res_idx < p->tot_len)) {
+          while ((nanswers || LWIP_DNS_RR_OR_ZERO(nextrarr)) && (res_idx < p->tot_len)) {
+#if LWIP_DNS_SUPPORT_OTHER_RR
+            dns_rr_response_t rr_response;
+
+            res_idx = dns_get_name(p, res_idx, rr_response.name, DNS_MAX_NAME_LENGTH);
+#else /* LWIP_DNS_SUPPORT_OTHER_RR */
             /* skip answer resource record's host name */
-            res_idx = dns_skip_name(p, res_idx);
+            res_idx = dns_get_name(p, res_idx);
+#endif /* LWIP_DNS_SUPPORT_OTHER_RR */
             if (res_idx == 0xFFFF) {
               goto ignore_packet; /* ignore this packet */
             }
@@ -1301,6 +1439,97 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
             res_idx = (u16_t)(res_idx + SIZEOF_DNS_ANSWER);
 
             if (ans.cls == PP_HTONS(DNS_RRCLASS_IN)) {
+#if LWIP_DNS_SUPPORT_OTHER_RR
+              if (entry->rr_type) {
+                rr_response.rr_type = lwip_ntohs(ans.type);
+                rr_response.ttl = lwip_ntohl(ans.ttl);
+                rr_response.is_extrarr = (nanswers == 0);
+                switch (rr_response.rr_type) {
+#if LWIP_IPV4
+                  case DNS_RRTYPE_A:
+                    {
+                      ip4_addr_t ip4addr;
+                      /* read the IP address after answer resource record's header */
+                      if (pbuf_copy_partial(p, &ip4addr, sizeof(ip4_addr_t), res_idx) != sizeof(ip4_addr_t)) {
+                        goto ignore_packet; /* ignore this packet */
+                      }
+                      ip_addr_copy_from_ip4(rr_response.data.a_aaaa.addr, ip4addr);
+                      dns_call_found(i, &rr_response);
+                    }
+                    break;
+#endif /* LWIP_IPV4 */
+#if LWIP_IPV6
+                  case DNS_RRTYPE_AAAA:
+                    {
+                      ip6_addr_t ip6addr;
+                      /* read the IP address after answer resource record's header */
+                      if (pbuf_copy_partial(p, &ip6addr, sizeof(ip6_addr_t), res_idx) != sizeof(ip6_addr_t)) {
+                        goto ignore_packet; /* ignore this packet */
+                      }
+                      ip_addr_copy_from_ip6(rr_response.data.a_aaaa.addr, ip6addr);
+                      dns_call_found(i, &rr_response);
+                    }
+                    break;
+#endif /* LWIP_IPV6 */
+                  case DNS_RRTYPE_CNAME:
+                    dns_get_name(p, res_idx, rr_response.data.cname.cname, DNS_MAX_NAME_LENGTH);
+                    dns_call_found(i, &rr_response);
+                    break;
+                  case DNS_RRTYPE_MX:
+                    if (pbuf_copy_partial(p, &rr_response.data.mx, 2, res_idx) != 2) {
+                      goto ignore_packet;
+                    }
+                    rr_response.data.mx.priority    = lwip_ntohs(rr_response.data.mx.priority);
+                    dns_get_name(p, res_idx + 2, rr_response.data.mx.target, DNS_MAX_NAME_LENGTH);
+                    dns_call_found(i, &rr_response);
+                    break;
+                  case DNS_RRTYPE_PTR:
+                    dns_get_name(p, res_idx, rr_response.data.ptr.target, DNS_MAX_NAME_LENGTH);
+                    dns_call_found(i, &rr_response);
+                    break;
+                  case DNS_RRTYPE_SRV:
+                    if (pbuf_copy_partial(p, &rr_response.data.srv, 6, res_idx) != 6) {
+                      goto ignore_packet;
+                    }
+                    rr_response.data.srv.priority   = lwip_ntohs(rr_response.data.srv.priority);
+                    rr_response.data.srv.weight     = lwip_ntohs(rr_response.data.srv.weight);
+                    rr_response.data.srv.port       = lwip_ntohs(rr_response.data.srv.port);
+                    dns_get_name(p, res_idx + 6, rr_response.data.srv.target, DNS_MAX_NAME_LENGTH);
+                    dns_call_found(i, &rr_response);
+                    break;
+                  case DNS_RRTYPE_TXT:
+                    {
+                      u16_t txt_num = 0;
+                      u16_t txt_idx = res_idx;
+                      int   txt_len;
+                      u16_t txt_len_buf;
+
+                      for (;;) {
+                        if (txt_idx >= res_idx + lwip_ntohs(ans.len)) {
+                          break;
+                        }
+                        txt_len = pbuf_try_get_at(p, txt_idx);
+                        if (txt_len < 0 || txt_idx + 1u + (u8_t)txt_len > res_idx + lwip_ntohs(ans.len)) {
+                          goto ignore_packet;
+                        }
+                        txt_len_buf = LWIP_MIN((u16_t)txt_len, DNS_MAX_NAME_LENGTH - 1u);
+                        if (pbuf_copy_partial(p, &rr_response.data.txt.txt, txt_len_buf, txt_idx + 1u) != txt_len_buf) {
+                          goto ignore_packet;
+                        }
+                        rr_response.data.txt.txt[txt_len_buf] = '\0';
+                        rr_response.data.txt.txt_num = txt_num++;
+                        dns_call_found(i, &rr_response);
+                        txt_idx += 1u + (u8_t)txt_len;
+                      }
+                    }
+                    break;
+                  default:
+                    rr_response.data.generic.nothing = 0;
+                    dns_call_found(i, &rr_response);
+                    break;
+                }
+              }
+#endif /* LWIP_DNS_SUPPORT_OTHER_RR */
 #if LWIP_IPV4
               if ((ans.type == PP_HTONS(DNS_RRTYPE_A)) && (ans.len == PP_HTONS(sizeof(ip4_addr_t)))) {
 #if LWIP_IPV4 && LWIP_IPV6
@@ -1313,10 +1542,15 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
                     goto ignore_packet; /* ignore this packet */
                   }
                   ip_addr_copy_from_ip4(dns_table[i].ipaddr, ip4addr);
-                  pbuf_free(p);
                   /* handle correct response */
                   dns_correct_response(i, lwip_ntohl(ans.ttl));
-                  return;
+#if LWIP_DNS_SUPPORT_OTHER_RR
+                  if (dns_table[i].rr_type == 0)
+#endif
+                  {
+                    pbuf_free(p);
+                    return;
+                  }
                 }
               }
 #endif /* LWIP_IPV4 */
@@ -1333,20 +1567,33 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
                   }
                   /* @todo: scope ip6addr? Might be required for link-local addresses at least? */
                   ip_addr_copy_from_ip6_packed(dns_table[i].ipaddr, ip6addr);
-                  pbuf_free(p);
                   /* handle correct response */
                   dns_correct_response(i, lwip_ntohl(ans.ttl));
-                  return;
+#if LWIP_DNS_SUPPORT_OTHER_RR
+                  if (dns_table[i].rr_type == 0)
+#endif /* LWIP_DNS_SUPPORT_OTHER_RR */
+                  {
+                    pbuf_free(p);
+                    return;
+                  }
                 }
               }
 #endif /* LWIP_IPV6 */
             }
             /* skip this answer */
-            if ((int)(res_idx + lwip_htons(ans.len)) > 0xFFFF) {
+            if ((int)(res_idx + lwip_ntohs(ans.len)) > 0xFFFF) {
               goto ignore_packet; /* ignore this packet */
             }
-            res_idx = (u16_t)(res_idx + lwip_htons(ans.len));
+            res_idx = (u16_t)(res_idx + lwip_ntohs(ans.len));
+#if LWIP_DNS_SUPPORT_OTHER_RR
+            if (nanswers) {
+              --nanswers;
+            } else {
+              --nextrarr;
+            }
+#else
             --nanswers;
+#endif /* LWIP_DNS_SUPPORT_OTHER_RR */
           }
 #if LWIP_IPV4 && LWIP_IPV6
           if ((entry->reqaddrtype == LWIP_DNS_ADDRTYPE_IPV4_IPV6) ||
@@ -1366,6 +1613,7 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
 #endif /* LWIP_IPV4 && LWIP_IPV6 */
           LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in response\n", entry->name));
         }
+ignore_packet:
         /* call callback to indicate error, clean up memory and return */
         pbuf_free(p);
         dns_call_found(i, NULL);
@@ -1375,7 +1623,6 @@ dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
     }
   }
 
-ignore_packet:
   /* deallocate memory and return */
   pbuf_free(p);
   return;
@@ -1391,8 +1638,9 @@ ignore_packet:
  * @return err_t return code.
  */
 static err_t
-dns_enqueue(const char *name, size_t hostnamelen, dns_found_callback found,
-            void *callback_arg LWIP_DNS_ADDRTYPE_ARG(u8_t dns_addrtype) LWIP_DNS_ISMDNS_ARG(u8_t is_mdns))
+dns_enqueue(const char *name, size_t hostnamelen, dns_found_generic_callback found,
+            void *callback_arg LWIP_DNS_ADDRTYPE_ARG(u8_t dns_addrtype) LWIP_DNS_ISMDNS_ARG(u8_t is_mdns)
+            LWIP_DNS_RR_ARG(u8_t rr_type))
 {
   u8_t i;
   u8_t lseq, lseqi;
@@ -1408,9 +1656,25 @@ dns_enqueue(const char *name, size_t hostnamelen, dns_found_callback found,
 #if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
   /* check for duplicate entries */
   for (i = 0; i < DNS_TABLE_SIZE; i++) {
+#if LWIP_DNS_SUPPORT_OTHER_RR
+    if (rr_type) {
+      continue;
+    }
+#endif
     if ((dns_table[i].state == DNS_STATE_ASKING) &&
         (lwip_strnicmp(name, dns_table[i].name, namelen) == 0) &&
         !dns_table[i].name[namelen]) {
+#if LWIP_DNS_SUPPORT_OTHER_RR
+      if (rr_type) {
+        if (dns_table[i].rr_type != rr_type) {
+          /* requested RR types don't match */
+          continue;
+        }
+      }
+#if LWIP_IPV4 && LWIP_IPV6
+      else
+#endif
+#endif
 #if LWIP_IPV4 && LWIP_IPV6
       if (dns_table[i].reqaddrtype != dns_addrtype) {
         /* requested address types don't match
@@ -1426,6 +1690,7 @@ dns_enqueue(const char *name, size_t hostnamelen, dns_found_callback found,
           dns_requests[r].arg = callback_arg;
           dns_requests[r].dns_table_idx = i;
           LWIP_DNS_SET_ADDRTYPE(dns_requests[r].reqaddrtype, dns_addrtype);
+          LWIP_DNS_SET_RR(dns_requests[r].rr_type, rr_type);
           LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": duplicate request\n", name));
           return ERR_INPROGRESS;
         }
@@ -1495,6 +1760,8 @@ dns_enqueue(const char *name, size_t hostnamelen, dns_found_callback found,
   entry->seqno = dns_seqno;
   LWIP_DNS_SET_ADDRTYPE(entry->reqaddrtype, dns_addrtype);
   LWIP_DNS_SET_ADDRTYPE(req->reqaddrtype, dns_addrtype);
+  LWIP_DNS_SET_RR(entry->rr_type, rr_type);
+  LWIP_DNS_SET_RR(req->rr_type, rr_type);
   req->found = found;
   req->arg   = callback_arg;
   MEMCPY(entry->name, name, namelen);
@@ -1650,8 +1917,83 @@ dns_gethostbyname_addrtype(const char *hostname, ip_addr_t *addr, dns_found_call
   }
 
   /* queue query with specified callback */
-  return dns_enqueue(hostname, hostnamelen, found, callback_arg LWIP_DNS_ADDRTYPE_ARG(dns_addrtype)
-                     LWIP_DNS_ISMDNS_ARG(is_mdns));
+  return dns_enqueue(hostname, hostnamelen, (dns_found_generic_callback)found,
+                     callback_arg LWIP_DNS_ADDRTYPE_ARG(dns_addrtype)
+                     LWIP_DNS_ISMDNS_ARG(is_mdns) LWIP_DNS_RR_ARG(0));
+}
+
+#if LWIP_DNS_SUPPORT_OTHER_RR
+/**
+ * @ingroup dns
+ * Look up a DNS RR for a hostname (string).
+ * NON-BLOCKING callback version for use with raw API!!!
+ *
+ * Returns immediately with one of err_t return codes:
+ * - ERR_INPROGRESS enqueue a request to be sent to the DNS server
+ *   for resolution if no errors are present.
+ * - ERR_ARG: dns client not initialized or invalid hostname
+ * - ERR_VAL: no DNS servers set
+ *
+ * @param hostname the hostname that is to be queried
+ * @param rr_type the type of resource record to look up
+ * @param found A callback function to be called (possibly multiple times) with results.
+ *              The callback function will be called 0 to many times, each time with one result.
+ *              Finally, the callback function will be called with a NULL pointer in rr_response.
+ * @param callback_arg argument to pass to the callback function
+ * @return a err_t return code.
+ */
+err_t
+dns_getrr(const char *name, u8_t rr_type,
+          dns_rr_found_callback found, void *callback_arg)
+{
+  size_t namelen;
+#if LWIP_IPV4 && LWIP_IPV6
+  u8_t dns_addrtype = LWIP_DNS_ADDRTYPE_DEFAULT;
+#endif
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+  u8_t is_mdns;
+#endif
+  /* not initialized or no valid server yet, or invalid addr pointer
+   * or invalid hostname or invalid hostname length */
+  if ((!name) || (!name[0]) || (!rr_type)) {
+    return ERR_ARG;
+  }
+#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) == 0)
+  if (dns_pcbs[0] == NULL) {
+    return ERR_ARG;
+  }
+#endif
+  namelen = strlen(name);
+  if (name[namelen - 1] == '.') {
+      namelen--;
+  }
+  if (namelen >= DNS_MAX_NAME_LENGTH) {
+    LWIP_DEBUGF(DNS_DEBUG, ("dns_gethostbyname: name too long to resolve"));
+    return ERR_ARG;
+  }
+
+#if LWIP_DNS_SUPPORT_MDNS_QUERIES
+  if (strstr(name, ".local") == &name[namelen] - 6) {
+    is_mdns = 1;
+  } else {
+    is_mdns = 0;
+  }
+
+  if (!is_mdns)
+#endif /* LWIP_DNS_SUPPORT_MDNS_QUERIES */
+  {
+    /* prevent calling found callback if no server is set, return error instead */
+    if (ip_addr_isany_val(dns_servers[0])) {
+      return ERR_VAL;
+    }
+  }
+
+  /* queue query with specified callback */
+  return dns_enqueue(name, namelen, (dns_found_generic_callback)found,
+                     callback_arg LWIP_DNS_ADDRTYPE_ARG(dns_addrtype)
+                     LWIP_DNS_ISMDNS_ARG(is_mdns) LWIP_DNS_RR_ARG(rr_type));
 }
+#endif /* LWIP_DNS_SUPPORT_OTHER_RR */
+
 
 #endif /* LWIP_DNS */
diff --git a/src/include/lwip/dns.h b/src/include/lwip/dns.h
index 09134154..4ae105ab 100644
--- a/src/include/lwip/dns.h
+++ b/src/include/lwip/dns.h
@@ -112,7 +112,6 @@ err_t            dns_gethostbyname_addrtype(const char *hostname, ip_addr_t *add
                                    dns_found_callback found, void *callback_arg,
                                    u8_t dns_addrtype);
 
-
 #if DNS_LOCAL_HOSTLIST
 size_t         dns_local_iterate(dns_found_callback iterator_fn, void *iterator_arg);
 err_t          dns_local_lookup(const char *hostname, ip_addr_t *addr, u8_t dns_addrtype);
@@ -122,6 +121,74 @@ err_t          dns_local_addhost(const char *hostname, const ip_addr_t *addr);
 #endif /* DNS_LOCAL_HOSTLIST_IS_DYNAMIC */
 #endif /* DNS_LOCAL_HOSTLIST */
 
+#if LWIP_DNS_SUPPORT_OTHER_RR
+
+typedef struct {
+  char                  nothing;
+} dns_generic_data_t;
+
+typedef struct {
+  ip_addr_t             addr;
+} dns_a_aaaa_data_t;
+
+typedef struct {
+  char                  target[DNS_MAX_NAME_LENGTH];
+} dns_ptr_data_t;
+
+typedef struct {
+  char                  cname[DNS_MAX_NAME_LENGTH];
+} dns_cname_data_t;
+
+typedef struct {
+  u16_t                 priority;
+  char                  target[DNS_MAX_NAME_LENGTH];
+} dns_mx_data_t;
+
+typedef struct {
+  u16_t                 txt_num;
+  char                  txt[DNS_MAX_NAME_LENGTH];
+} dns_txt_data_t;
+
+typedef struct {
+  u16_t                 priority;
+  u16_t                 weight;
+  u16_t                 port;
+  char                  target[DNS_MAX_NAME_LENGTH];
+} dns_srv_data_t;
+
+typedef union {
+  dns_generic_data_t    generic;
+  dns_a_aaaa_data_t     a_aaaa;
+  dns_ptr_data_t        ptr;
+  dns_cname_data_t      cname;
+  dns_mx_data_t         mx;
+  dns_txt_data_t        txt;
+  dns_srv_data_t        srv;
+} dns_rr_type_data_t;
+
+typedef struct {
+  char                  name[DNS_MAX_NAME_LENGTH];
+  u8_t                  rr_type;
+  u8_t                  is_extrarr;
+  u32_t                 ttl;
+  dns_rr_type_data_t    data;
+} dns_rr_response_t;
+
+/** Callback which is invoked when a result is found for a RR look-up by dns_getrr().
+ * The callback can be called multiple times, once for each RR result in answer to the query.
+ * The end of the query is indicated by a NULL value for rr_response.
+ * @param name Pointer to the name that was looked up.
+ * @param rr_response Pointer to a dns_rr_response_t containing one result to the RR look-up.
+ *                    The end of the query is indicated by a NULL value for rr_response.
+ * @param callback_arg A user-specified callback argument passed to dns_gethostbyname.
+*/
+typedef void (*dns_rr_found_callback)(const char *name, const dns_rr_response_t *rr_response, void *callback_arg);
+
+err_t            dns_getrr(const char *name, u8_t rr_type,
+                                   dns_rr_found_callback found, void *callback_arg);
+
+#endif /* LWIP_DNS_SUPPORT_OTHER_RR */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/include/lwip/opt.h b/src/include/lwip/opt.h
index 15882b22..7c7a14a5 100644
--- a/src/include/lwip/opt.h
+++ b/src/include/lwip/opt.h
@@ -1198,6 +1198,11 @@
 #if !defined LWIP_DNS_SUPPORT_MDNS_QUERIES || defined __DOXYGEN__
 #define LWIP_DNS_SUPPORT_MDNS_QUERIES   0
 #endif
+
+/** Set this to 1 to enable querying other RR types */
+#if !defined LWIP_DNS_SUPPORT_OTHER_RR || defined __DOXYGEN__
+#define LWIP_DNS_SUPPORT_OTHER_RR       0
+#endif
 /**
  * @}
  */
diff --git a/src/include/lwip/prot/dns.h b/src/include/lwip/prot/dns.h
index 94782d6e..ce6c21a8 100644
--- a/src/include/lwip/prot/dns.h
+++ b/src/include/lwip/prot/dns.h
@@ -70,6 +70,7 @@ extern "C" {
 #define DNS_RRTYPE_TXT            16    /* text strings */
 #define DNS_RRTYPE_AAAA           28    /* IPv6 address */
 #define DNS_RRTYPE_SRV            33    /* service location */
+#define DNS_RRTYPE_NAPTR          35    /* naming authority pointer */
 #define DNS_RRTYPE_ANY            255   /* any type */
 
 /* DNS field CLASS used for "Resource Records" */
-- 
2.18.0.windows.1

