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.
pull/1470/head
David Cannings 10 years ago committed by Victor Julien
parent cf839c931f
commit 2918a75da1

@ -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);
}
}

@ -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__ */

@ -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

@ -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

@ -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);

@ -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;

Loading…
Cancel
Save