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;