From: David Cannings Date: Thu, 16 Apr 2015 18:40:46 +0000 (+0100) Subject: Added support for full parsing of the rcode header in DNS answer X-Git-Tag: suricata-2.1beta4~23 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2918a75da1f3f74f1c57e5848e4e7a860854b945;p=thirdparty%2Fsuricata.git Added support for full parsing of the rcode header in DNS answer 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. --- diff --git a/src/app-layer-dns-common.c b/src/app-layer-dns-common.c index 4d724dfaa4..852afa7c85 100644 --- a/src/app-layer-dns-common.c +++ b/src/app-layer-dns-common.c @@ -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); + } +} diff --git a/src/app-layer-dns-common.h b/src/app-layer-dns-common.h index d795f0cba6..1564fec986 100644 --- a/src/app-layer-dns-common.h +++ b/src/app-layer-dns-common.h @@ -60,6 +60,26 @@ #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__ */ diff --git a/src/app-layer-dns-tcp.c b/src/app-layer-dns-tcp.c index 8ed1a85dcb..36b8848aeb 100644 --- a/src/app-layer-dns-tcp.c +++ b/src/app-layer-dns-tcp.c @@ -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 diff --git a/src/app-layer-dns-udp.c b/src/app-layer-dns-udp.c index 86350d0ce3..1917914b1b 100644 --- a/src/app-layer-dns-udp.c +++ b/src/app-layer-dns-udp.c @@ -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 diff --git a/src/log-dnslog.c b/src/log-dnslog.c index 7d226e8b8a..f0f381b521 100644 --- a/src/log-dnslog.c +++ b/src/log-dnslog.c @@ -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); diff --git a/src/output-json-dns.c b/src/output-json-dns.c index 44494dbc09..d0be0e6aa8 100644 --- a/src/output-json-dns.c +++ b/src/output-json-dns.c @@ -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;