]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
Added support for full parsing of the rcode header in DNS answer
authorDavid Cannings <david.cannings@nccgroup.com>
Thu, 16 Apr 2015 18:40:46 +0000 (19:40 +0100)
committerVictor Julien <victor@inliniac.net>
Thu, 7 May 2015 09:45:33 +0000 (11:45 +0200)
packets. Where rcode isn't "no error" this is displayed in both DNS and
JSON logs.

Note that this changes the current "No such domain" to "NXDOMAIN" in DNS
logs. This could be fixed if desired to maintain compatibility with
anybody crazy enough to parse the DNS log.

When the rcode is not "no error" (for example NXDOMAIN or SERVFAIL) it
is unlikely that there will be answer RRs. Therefore the rname from the
query is used.

Because the rcode applies to a whole answer packet (not individual
queries) it is impossible to determine which query RR caused the error.
Because of this most DNS servers currently reject multiple queries per
packet. Therefore each query RR is output instead with the relevant
error code, likely to be FORMERR if queries > 1.

src/app-layer-dns-common.c
src/app-layer-dns-common.h
src/app-layer-dns-tcp.c
src/app-layer-dns-udp.c
src/log-dnslog.c
src/output-json-dns.c

index 4d724dfaa4d117a1f20ea2186bb3166017ed802b..852afa7c85dd6cae1a7be5c6c0928e78f0c534a4 100644 (file)
@@ -794,22 +794,22 @@ const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_
     }
 
     const DNSAnswerHeader *head = (DNSAnswerHeader *)data;
-    switch (ntohs(head->type)) {
-        case DNS_RECORD_TYPE_A:
-        case DNS_RECORD_TYPE_AAAA:
-        case DNS_RECORD_TYPE_CNAME:
-        {
-            data += sizeof(DNSAnswerHeader);
 
-            SCLogDebug("head->len %u", ntohs(head->len));
+    data += sizeof(DNSAnswerHeader);
 
-            if (input + input_len < data + ntohs(head->len)) {
-                SCLogDebug("input buffer too small for data of len %u", ntohs(head->len));
-                goto insufficient_data;
-            }
-            SCLogDebug("TTL %u", ntohl(head->ttl));
+    SCLogDebug("head->len %u", ntohs(head->len));
+
+    if (input + input_len < data + ntohs(head->len)) {
+        SCLogDebug("input buffer too small for data of len %u", ntohs(head->len));
+        goto insufficient_data;
+    }
 
-            if (ntohs(head->type) == DNS_RECORD_TYPE_A && ntohs(head->len) == 4) {
+    SCLogDebug("TTL %u", ntohl(head->ttl));
+
+    switch (ntohs(head->type)) {
+        case DNS_RECORD_TYPE_A:
+        {
+            if (ntohs(head->len) == 4) {
                 //PrintRawDataFp(stdout, data, ntohs(head->len));
                 //char a[16];
                 //PrintInet(AF_INET, (const void *)data, a, sizeof(a));
@@ -818,7 +818,17 @@ const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_
                 DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
                         ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
                         data, 4, ntohs(dns_header->tx_id));
-            } else if (ntohs(head->type) == DNS_RECORD_TYPE_AAAA && ntohs(head->len) == 16) {
+            } else {
+                SCLogDebug("invalid length for A response data: %u", ntohs(head->len));
+                goto bad_data;
+            }
+
+            data += ntohs(head->len);
+            break;
+        }
+        case DNS_RECORD_TYPE_AAAA:
+        {
+            if (ntohs(head->len) == 16) {
                 //char a[46];
                 //PrintInet(AF_INET6, (const void *)data, a, sizeof(a));
                 //SCLogInfo("AAAA %s TTL %u", a, ntohl(head->ttl));
@@ -826,46 +836,29 @@ const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_
                 DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
                         ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
                         data, 16, ntohs(dns_header->tx_id));
-            } else if (ntohs(head->type) == DNS_RECORD_TYPE_CNAME) {
-                uint8_t cname[DNS_MAX_SIZE];
-                uint16_t cname_len = 0;
-
-                if ((cname_len = DNSResponseGetNameByOffset(input, input_len,
-                                data - input, cname, sizeof(cname))) == 0)
-                {
-#if DEBUG
-                    PrintRawDataFp(stdout, (uint8_t *)input, input_len);
-                    BUG_ON(1);
-#endif
-                    goto insufficient_data;
-                }
-
-                DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
-                        ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
-                        cname, cname_len, ntohs(dns_header->tx_id));
+            } else {
+                SCLogDebug("invalid length for AAAA response data: %u", ntohs(head->len));
+                goto bad_data;
             }
 
             data += ntohs(head->len);
             break;
         }
         case DNS_RECORD_TYPE_MX:
+        case DNS_RECORD_TYPE_CNAME:
+        case DNS_RECORD_TYPE_PTR:
         {
-            data += sizeof(DNSAnswerHeader);
+            uint8_t name[DNS_MAX_SIZE];
+            uint16_t name_len = 0;
+            uint8_t skip = 0;
 
-            SCLogDebug("head->len %u", ntohs(head->len));
-
-            if (input + input_len < data + ntohs(head->len)) {
-                SCLogDebug("input buffer too small for data of len %u", ntohs(head->len));
-                goto insufficient_data;
+            if (ntohs(head->type) == DNS_RECORD_TYPE_MX) {
+                // Skip the preference header
+                skip = 2;
             }
 
-            SCLogDebug("TTL %u", ntohl(head->ttl));
-
-            uint8_t mxname[DNS_MAX_SIZE];
-            uint16_t mxname_len = 0;
-
-            if ((mxname_len = DNSResponseGetNameByOffset(input, input_len,
-                            data - input + 2, mxname, sizeof(mxname))) == 0) {
+            if ((name_len = DNSResponseGetNameByOffset(input, input_len,
+                            data - input + skip, name, sizeof(name))) == 0) {
 #if DEBUG
                 PrintRawDataFp(stdout, (uint8_t *)input, input_len);
                 BUG_ON(1);
@@ -875,7 +868,7 @@ const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_
 
             DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
                     ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
-                    mxname, mxname_len, ntohs(dns_header->tx_id));
+                    name, name_len, ntohs(dns_header->tx_id));
 
             data += ntohs(head->len);
             break;
@@ -883,15 +876,6 @@ const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_
         case DNS_RECORD_TYPE_NS:
         case DNS_RECORD_TYPE_SOA:
         {
-            data += sizeof(DNSAnswerHeader);
-
-            if (input + input_len < data + ntohs(head->len)) {
-                SCLogDebug("input buffer too small for data of len %u", ntohs(head->len));
-                goto insufficient_data;
-            }
-
-            SCLogDebug("TTL %u", ntohl(head->ttl));
-
             uint8_t pname[DNS_MAX_SIZE];
             uint16_t pname_len = 0;
 
@@ -960,13 +944,6 @@ const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_
         }
         case DNS_RECORD_TYPE_TXT:
         {
-            data += sizeof(DNSAnswerHeader);
-
-            if (input + input_len < data + ntohs(head->len)) {
-                SCLogDebug("input buffer too small for data of len %u", ntohs(head->len));
-                goto insufficient_data;
-            }
-
             uint16_t datalen = ntohs(head->len);
             uint8_t txtlen = *data;
             const uint8_t *tdata = data + 1;
@@ -996,13 +973,6 @@ const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_
         }
         default:    /* unsupported record */
         {
-            data += sizeof(DNSAnswerHeader);
-
-            if (input + input_len < data + ntohs(head->len)) {
-                SCLogDebug("input buffer too small for data of len %u", ntohs(head->len));
-                goto insufficient_data;
-            }
-
             DNSStoreAnswerInState(dns_state, list, NULL, 0,
                     ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
                     NULL, 0, ntohs(dns_header->tx_id));
@@ -1076,3 +1046,68 @@ void DNSCreateTypeString(uint16_t type, char *str, size_t str_size)
             snprintf(str, str_size, "%04x/%u", type, type);
     }
 }
+
+void DNSCreateRcodeString(uint8_t rcode, char *str, size_t str_size)
+{
+    switch (rcode) {
+        case DNS_RCODE_NOERROR:
+            snprintf(str, str_size, "NOERROR");
+            break;
+        case DNS_RCODE_FORMERR:
+            snprintf(str, str_size, "FORMERR");
+            break;
+        case DNS_RCODE_SERVFAIL:
+            snprintf(str, str_size, "SERVFAIL");
+            break;
+        case DNS_RCODE_NXDOMAIN:
+            snprintf(str, str_size, "NXDOMAIN");
+            break;
+        case DNS_RCODE_NOTIMP:
+            snprintf(str, str_size, "NOTIMP");
+            break;
+        case DNS_RCODE_REFUSED:
+            snprintf(str, str_size, "REFUSED");
+            break;
+        case DNS_RCODE_YXDOMAIN:
+            snprintf(str, str_size, "YXDOMAIN");
+            break;
+        case DNS_RCODE_YXRRSET:
+            snprintf(str, str_size, "YXRRSET");
+            break;
+        case DNS_RCODE_NXRRSET:
+            snprintf(str, str_size, "NXRRSET");
+            break;
+        case DNS_RCODE_NOTAUTH:
+            snprintf(str, str_size, "NOTAUTH");
+            break;
+        case DNS_RCODE_NOTZONE:
+            snprintf(str, str_size, "NOTZONE");
+            break;
+        /* these are the same, need more logic */
+        case DNS_RCODE_BADVERS:
+        //case DNS_RCODE_BADSIG:
+            snprintf(str, str_size, "BADVERS/BADSIG");
+            break;
+        case DNS_RCODE_BADKEY:
+            snprintf(str, str_size, "BADKEY");
+            break;
+        case DNS_RCODE_BADTIME:
+            snprintf(str, str_size, "BADTIME");
+            break;
+        case DNS_RCODE_BADMODE:
+            snprintf(str, str_size, "BADMODE");
+            break;
+        case DNS_RCODE_BADNAME:
+            snprintf(str, str_size, "BADNAME");
+            break;
+        case DNS_RCODE_BADALG:
+            snprintf(str, str_size, "BADALG");
+            break;
+        case DNS_RCODE_BADTRUNC:
+            snprintf(str, str_size, "BADTRUNC");
+            break;
+        default:
+            SCLogDebug("could not map DNS rcode to name, bug!");
+            snprintf(str, str_size, "%04x/%u", rcode, rcode);
+    }
+}
index d795f0cba6578ff1cb15cf9b6b4695670720b6ab..1564fec986833d30a03bdc5fc240c1bcf82534cc 100644 (file)
 
 #define DNS_RECORD_TYPE_ANY     255
 
+#define DNS_RCODE_NOERROR       0
+#define DNS_RCODE_FORMERR       1
+#define DNS_RCODE_SERVFAIL      2
+#define DNS_RCODE_NXDOMAIN      3
+#define DNS_RCODE_NOTIMP        4
+#define DNS_RCODE_REFUSED       5
+#define DNS_RCODE_YXDOMAIN      6
+#define DNS_RCODE_YXRRSET       7
+#define DNS_RCODE_NXRRSET       8
+#define DNS_RCODE_NOTAUTH       9
+#define DNS_RCODE_NOTZONE       10
+#define DNS_RCODE_BADVERS       16
+#define DNS_RCODE_BADSIG        16
+#define DNS_RCODE_BADKEY        17
+#define DNS_RCODE_BADTIME       18
+#define DNS_RCODE_BADMODE       19
+#define DNS_RCODE_BADNAME       20
+#define DNS_RCODE_BADALG        21
+#define DNS_RCODE_BADTRUNC      22
+
 enum {
     DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE,
     DNS_DECODER_EVENT_MALFORMED_DATA,
@@ -139,7 +159,7 @@ typedef struct DNSTransaction_ {
     uint8_t replied;                                /**< bool indicating request is
                                                          replied to. */
     uint8_t reply_lost;
-    uint8_t no_such_name;                           /**< server said "no such name" */
+    uint8_t rcode;                                  /**< response code (e.g. "no error" / "no such name") */
     uint8_t recursion_desired;                      /**< server said "recursion desired" */
 
     TAILQ_HEAD(, DNSQueryEntry_) query_list;        /**< list for query/queries */
@@ -228,5 +248,6 @@ uint16_t DNSUdpResponseGetNameByOffset(const uint8_t * const input, const uint32
         const uint16_t offset, uint8_t *fqdn, const size_t fqdn_size);
 
 void DNSCreateTypeString(uint16_t type, char *str, size_t str_size);
+void DNSCreateRcodeString(uint8_t rcode, char *str, size_t str_size);
 
 #endif /* __APP_LAYER_DNS_COMMON_H__ */
index 8ed1a85dcb67c91f773bd4da4366a092c02a79b1..36b8848aeb6379b800568eeaf765258504075347 100644 (file)
@@ -141,7 +141,7 @@ static int DNSTCPRequestParseProbe(uint8_t *input, uint32_t input_len)
         data += sizeof(DNSQueryTrailer);
     }
 
-       SCReturnInt(1);
+    SCReturnInt(1);
 insufficient_data:
     SCReturnInt(0);
 bad_data:
@@ -262,7 +262,7 @@ static int DNSRequestParseData(Flow *f, DNSState *dns_state, const uint8_t *inpu
         }
     }
 
-       SCReturnInt(1);
+    SCReturnInt(1);
 bad_data:
 insufficient_data:
     SCReturnInt(-1);
@@ -277,7 +277,7 @@ static int DNSTCPRequestParse(Flow *f, void *dstate,
                               uint8_t *input, uint32_t input_len,
                               void *local_data)
 {
-       DNSState *dns_state = (DNSState *)dstate;
+    DNSState *dns_state = (DNSState *)dstate;
     SCLogDebug("starting %u", input_len);
 
     /** \todo remove this when PP is fixed to enforce ipproto */
@@ -351,7 +351,7 @@ next_record:
             goto bad_data;
     }
 
-       SCReturnInt(1);
+    SCReturnInt(1);
 insufficient_data:
     SCReturnInt(-1);
 bad_data:
@@ -447,11 +447,15 @@ static int DNSReponseParseData(Flow *f, DNSState *dns_state, const uint8_t *inpu
         }
     }
 
-    /* see if this is a "no such name" error */
-    if (ntohs(dns_header->flags) & 0x0003) {
-        SCLogDebug("no such name");
+    /* parse rcode, e.g. "noerror" or "nxdomain" */
+    uint8_t rcode = ntohs(dns_header->flags) & 0x0F;
+    if (rcode <= DNS_RCODE_NOTZONE || (rcode >= DNS_RCODE_BADSIG && rcode <= DNS_RCODE_BADTRUNC)) {
+        SCLogDebug("rcode %u", rcode);
         if (tx != NULL)
-            tx->no_such_name = 1;
+            tx->rcode = rcode;
+    } else {
+        /* this is not invalid, rcodes can be user defined */
+        SCLogDebug("unexpected DNS rcode %u", rcode);
     }
 
     if (ntohs(dns_header->flags) & 0x0080) {
@@ -464,7 +468,7 @@ static int DNSReponseParseData(Flow *f, DNSState *dns_state, const uint8_t *inpu
         tx->replied = 1;
     }
 
-       SCReturnInt(1);
+    SCReturnInt(1);
 bad_data:
 insufficient_data:
     SCReturnInt(-1);
@@ -484,7 +488,7 @@ static int DNSTCPResponseParse(Flow *f, void *dstate,
                                uint8_t *input, uint32_t input_len,
                                void *local_data)
 {
-       DNSState *dns_state = (DNSState *)dstate;
+    DNSState *dns_state = (DNSState *)dstate;
 
     /** \todo remove this when PP is fixed to enforce ipproto */
     if (f != NULL && f->proto != IPPROTO_TCP)
@@ -553,7 +557,7 @@ next_record:
         if (r < 0)
             goto bad_data;
     }
-       SCReturnInt(1);
+    SCReturnInt(1);
 insufficient_data:
     SCReturnInt(-1);
 bad_data:
@@ -664,6 +668,6 @@ void RegisterDNSTCPParsers(void)
 #ifdef UNITTESTS
 void DNSTCPParserRegisterTests(void)
 {
-//     UtRegisterTest("DNSTCPParserTest01", DNSTCPParserTest01, 1);
+//    UtRegisterTest("DNSTCPParserTest01", DNSTCPParserTest01, 1);
 }
 #endif
index 86350d0ce396530f135e2c884a181870ead9a21b..1917914b1b11a55f60f563a0963185bcb3e52ff6 100644 (file)
@@ -147,7 +147,7 @@ static int DNSUDPRequestParse(Flow *f, void *dstate,
         }
     }
 
-       SCReturnInt(1);
+    SCReturnInt(1);
 bad_data:
 insufficient_data:
     SCReturnInt(-1);
@@ -164,7 +164,7 @@ static int DNSUDPResponseParse(Flow *f, void *dstate,
                                uint8_t *input, uint32_t input_len,
                                void *local_data)
 {
-       DNSState *dns_state = (DNSState *)dstate;
+    DNSState *dns_state = (DNSState *)dstate;
 
     SCLogDebug("starting %u", input_len);
 
@@ -269,11 +269,15 @@ static int DNSUDPResponseParse(Flow *f, void *dstate,
         }
     }
 
-    /* see if this is a "no such name" error */
-    if (ntohs(dns_header->flags) & 0x0003) {
-        SCLogDebug("no such name");
+    /* parse rcode, e.g. "noerror" or "nxdomain" */
+    uint8_t rcode = ntohs(dns_header->flags) & 0x0F;
+    if (rcode <= DNS_RCODE_NOTZONE || (rcode >= DNS_RCODE_BADSIG && rcode <= DNS_RCODE_BADTRUNC)) {
+        SCLogDebug("rcode %u", rcode);
         if (tx != NULL)
-            tx->no_such_name = 1;
+            tx->rcode = rcode;
+    } else {
+        /* this is not invalid, rcodes can be user defined */
+        SCLogDebug("unexpected DNS rcode %u", rcode);
     }
 
     if (ntohs(dns_header->flags) & 0x0080) {
@@ -613,10 +617,10 @@ end:
 
 void DNSUDPParserRegisterTests(void)
 {
-       UtRegisterTest("DNSUDPParserTest01", DNSUDPParserTest01, 1);
-       UtRegisterTest("DNSUDPParserTest02", DNSUDPParserTest02, 1);
-       UtRegisterTest("DNSUDPParserTest03", DNSUDPParserTest03, 1);
-       UtRegisterTest("DNSUDPParserTest04", DNSUDPParserTest04, 1);
-       UtRegisterTest("DNSUDPParserTest05", DNSUDPParserTest05, 1);
+    UtRegisterTest("DNSUDPParserTest01", DNSUDPParserTest01, 1);
+    UtRegisterTest("DNSUDPParserTest02", DNSUDPParserTest02, 1);
+    UtRegisterTest("DNSUDPParserTest03", DNSUDPParserTest03, 1);
+    UtRegisterTest("DNSUDPParserTest04", DNSUDPParserTest04, 1);
+    UtRegisterTest("DNSUDPParserTest05", DNSUDPParserTest05, 1);
 }
 #endif
index 7d226e8b8a20878dc3a42114e57a8d13a004355e..f0f381b521aac4b77a0e9b5694586bddd69d5802 100644 (file)
@@ -40,6 +40,7 @@
 
 #include "output.h"
 #include "log-dnslog.h"
+#include "app-layer-dns-common.h"
 #include "app-layer-dns-udp.h"
 #include "app-layer.h"
 #include "util-privs.h"
@@ -109,16 +110,18 @@ static void LogAnswer(LogDnsLogThread *aft, char *timebuf, char *srcip, char *ds
 
     /* reset */
     MemBufferReset(aft->buffer);
-
     /* time & tx*/
     MemBufferWriteString(aft->buffer,
             "%s [**] Response TX %04x [**] ", timebuf, tx->tx_id);
 
     if (entry == NULL) {
-        if (tx->no_such_name)
-            MemBufferWriteString(aft->buffer, "No Such Name");
-        else if (tx->recursion_desired)
+        if (tx->rcode) {
+            char rcode[16] = "";
+            DNSCreateRcodeString(tx->rcode, rcode, sizeof(rcode));
+            MemBufferWriteString(aft->buffer, "%s", rcode);
+        } else if (tx->recursion_desired) {
             MemBufferWriteString(aft->buffer, "Recursion Desired");
+        }
     } else {
         /* query */
         if (entry->fqdn_len > 0) {
@@ -216,7 +219,7 @@ static int LogDnsLogger(ThreadVars *tv, void *data, const Packet *p, Flow *f,
         LogQuery(aft, timebuf, dstip, srcip, dp, sp, dns_tx, query);
     }
 
-    if (dns_tx->no_such_name)
+    if (dns_tx->rcode)
         LogAnswer(aft, timebuf, srcip, dstip, sp, dp, dns_tx, NULL);
     if (dns_tx->recursion_desired)
         LogAnswer(aft, timebuf, srcip, dstip, sp, dp, dns_tx, NULL);
index 44494dbc0925e036ef4f71125a8a14751b03984f..d0be0e6aa8f15b4df83443980432eaddc9a4dbe7 100644 (file)
@@ -126,6 +126,12 @@ static void OutputAnswer(LogDnsLogThread *aft, json_t *djs, DNSTransaction *tx,
     /* id */
     json_object_set_new(js, "id", json_integer(tx->tx_id));
 
+    /* rcode */
+    char rcode[16] = "";
+    DNSCreateRcodeString(tx->rcode, rcode, sizeof(rcode));
+    json_object_set_new(js, "rcode", json_string(rcode));
+
+    /* we are logging an answer RR */
     if (entry != NULL) {
         /* query */
         if (entry->fqdn_len > 0) {
@@ -157,7 +163,8 @@ static void OutputAnswer(LogDnsLogThread *aft, json_t *djs, DNSTransaction *tx,
             json_object_set_new(js, "rdata", json_string(a));
         } else if (entry->data_len == 0) {
             json_object_set_new(js, "rdata", json_string(""));
-        } else if (entry->type == DNS_RECORD_TYPE_TXT) {
+        } else if (entry->type == DNS_RECORD_TYPE_TXT || entry->type == DNS_RECORD_TYPE_CNAME ||
+                    entry->type == DNS_RECORD_TYPE_MX || entry->type == DNS_RECORD_TYPE_PTR) {
             if (entry->data_len != 0) {
                 char buffer[256] = "";
                 uint16_t copy_len = entry->data_len < (sizeof(buffer) - 1) ?
@@ -180,13 +187,55 @@ static void OutputAnswer(LogDnsLogThread *aft, json_t *djs, DNSTransaction *tx,
     return;
 }
 
+static void OutputFailure(LogDnsLogThread *aft, json_t *djs, DNSTransaction *tx, DNSQueryEntry *entry)
+{
+    MemBuffer *buffer = (MemBuffer *)aft->buffer;
+    json_t *js = json_object();
+    if (js == NULL)
+        return;
+
+    /* type */
+    json_object_set_new(js, "type", json_string("answer"));
+
+    /* id */
+    json_object_set_new(js, "id", json_integer(tx->tx_id));
+
+    /* rcode */
+    char rcode[16] = "";
+    DNSCreateRcodeString(tx->rcode, rcode, sizeof(rcode));
+    json_object_set_new(js, "rcode", json_string(rcode));
+
+    /* no answer RRs, use query for rname */
+    char *c;
+    c = BytesToString((uint8_t *)((uint8_t *)entry + sizeof(DNSQueryEntry)), entry->len);
+    if (c != NULL) {
+        json_object_set_new(js, "rrname", json_string(c));
+        SCFree(c);
+    }
+
+    /* reset */
+    MemBufferReset(buffer);
+    json_object_set_new(djs, "dns", js);
+    OutputJSONBuffer(djs, aft->dnslog_ctx->file_ctx, buffer);
+    json_object_del(djs, "dns");
+
+    return;
+}
+
 static void LogAnswers(LogDnsLogThread *aft, json_t *js, DNSTransaction *tx, uint64_t tx_id)
 {
 
     SCLogDebug("got a DNS response and now logging !!");
 
-    if (tx->no_such_name) {
-        OutputAnswer(aft, js, tx, NULL);
+    /* rcode != noerror */
+    if (tx->rcode) {
+        /* Most DNS servers do not support multiple queries because
+         * the rcode in response is not per-query.  Multiple queries
+         * are likely to lead to FORMERR, so log this. */
+        DNSQueryEntry *query = NULL;
+        TAILQ_FOREACH(query, &tx->query_list, next) {
+            OutputFailure(aft, js, tx, query);
+        }
     }
 
     DNSAnswerEntry *entry = NULL;