aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/suricata/src/app-layer-dns-common.c
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:21:41 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:21:41 -0700
commit8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch)
treec7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/app-layer-dns-common.c
parent13d05bc8458758ee39cb829098241e89616717ee (diff)
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/app-layer-dns-common.c')
-rw-r--r--framework/src/suricata/src/app-layer-dns-common.c1141
1 files changed, 1141 insertions, 0 deletions
diff --git a/framework/src/suricata/src/app-layer-dns-common.c b/framework/src/suricata/src/app-layer-dns-common.c
new file mode 100644
index 00000000..4a3f9ccd
--- /dev/null
+++ b/framework/src/suricata/src/app-layer-dns-common.c
@@ -0,0 +1,1141 @@
+/* Copyright (C) 2013-2014 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#include "suricata-common.h"
+#include "stream.h"
+#include "app-layer-parser.h"
+#include "app-layer-dns-common.h"
+#ifdef DEBUG
+#include "util-print.h"
+#endif
+#include "util-memcmp.h"
+#include "util-atomic.h"
+
+typedef struct DNSConfig_ {
+ uint32_t request_flood;
+ uint32_t state_memcap; /**< memcap in bytes per state */
+ uint64_t global_memcap; /**< memcap in bytes globally for parser */
+} DNSConfig;
+static DNSConfig dns_config;
+
+void DNSConfigInit(void)
+{
+ memset(&dns_config, 0x00, sizeof(dns_config));
+}
+
+void DNSConfigSetRequestFlood(uint32_t value)
+{
+ dns_config.request_flood = value;
+}
+
+void DNSConfigSetStateMemcap(uint32_t value)
+{
+ dns_config.state_memcap = value;
+}
+
+SC_ATOMIC_DECLARE(uint64_t, dns_memuse); /**< byte counter of current memuse */
+SC_ATOMIC_DECLARE(uint64_t, dns_memcap_state); /**< counts number of 'rejects' */
+SC_ATOMIC_DECLARE(uint64_t, dns_memcap_global); /**< counts number of 'rejects' */
+
+void DNSConfigSetGlobalMemcap(uint64_t value)
+{
+ dns_config.global_memcap = value;
+
+ SC_ATOMIC_INIT(dns_memuse);
+ SC_ATOMIC_INIT(dns_memcap_state);
+ SC_ATOMIC_INIT(dns_memcap_global);
+}
+
+void DNSIncrMemcap(uint32_t size, DNSState *state)
+{
+ if (state != NULL) {
+ state->memuse += size;
+ }
+ SC_ATOMIC_ADD(dns_memuse, size);
+}
+
+void DNSDecrMemcap(uint32_t size, DNSState *state)
+{
+ if (state != NULL) {
+ BUG_ON(size > state->memuse); /**< TODO remove later */
+ state->memuse -= size;
+ }
+
+ BUG_ON(size > SC_ATOMIC_GET(dns_memuse)); /**< TODO remove later */
+ (void)SC_ATOMIC_SUB(dns_memuse, size);
+}
+
+int DNSCheckMemcap(uint32_t want, DNSState *state)
+{
+ if (state != NULL) {
+ if (state->memuse + want > dns_config.state_memcap) {
+ SC_ATOMIC_ADD(dns_memcap_state, 1);
+ DNSSetEvent(state, DNS_DECODER_EVENT_STATE_MEMCAP_REACHED);
+ return -1;
+ }
+ }
+
+ if (SC_ATOMIC_GET(dns_memuse) + (uint64_t)want > dns_config.global_memcap) {
+ SC_ATOMIC_ADD(dns_memcap_global, 1);
+ return -2;
+ }
+
+ return 0;
+}
+
+uint64_t DNSMemcapGetMemuseCounter(void)
+{
+ uint64_t x = SC_ATOMIC_GET(dns_memuse);
+ return x;
+}
+
+uint64_t DNSMemcapGetMemcapStateCounter(void)
+{
+ uint64_t x = SC_ATOMIC_GET(dns_memcap_state);
+ return x;
+}
+
+uint64_t DNSMemcapGetMemcapGlobalCounter(void)
+{
+ uint64_t x = SC_ATOMIC_GET(dns_memcap_global);
+ return x;
+}
+
+SCEnumCharMap dns_decoder_event_table[ ] = {
+ { "UNSOLLICITED_RESPONSE", DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE, },
+ { "MALFORMED_DATA", DNS_DECODER_EVENT_MALFORMED_DATA, },
+ { "NOT_A_REQUEST", DNS_DECODER_EVENT_NOT_A_REQUEST, },
+ { "NOT_A_RESPONSE", DNS_DECODER_EVENT_NOT_A_RESPONSE, },
+ { "Z_FLAG_SET", DNS_DECODER_EVENT_Z_FLAG_SET, },
+ { "FLOODED", DNS_DECODER_EVENT_FLOODED, },
+ { "STATE_MEMCAP_REACHED", DNS_DECODER_EVENT_STATE_MEMCAP_REACHED, },
+
+ { NULL, -1 },
+};
+
+int DNSStateGetEventInfo(const char *event_name,
+ int *event_id, AppLayerEventType *event_type)
+{
+ *event_id = SCMapEnumNameToValue(event_name, dns_decoder_event_table);
+ if (*event_id == -1) {
+ SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
+ "dns's enum map table.", event_name);
+ /* this should be treated as fatal */
+ return -1;
+ }
+
+ *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
+
+ return 0;
+}
+
+void DNSAppLayerRegisterGetEventInfo(uint8_t ipproto, AppProto alproto)
+{
+ AppLayerParserRegisterGetEventInfo(ipproto, alproto, DNSStateGetEventInfo);
+
+ return;
+}
+
+AppLayerDecoderEvents *DNSGetEvents(void *state, uint64_t id)
+{
+ DNSState *dns_state = (DNSState *)state;
+ DNSTransaction *tx;
+
+ if (dns_state->curr && dns_state->curr->tx_num == (id + 1)) {
+ return dns_state->curr->decoder_events;
+ }
+
+ TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+ if (tx->tx_num == (id+1))
+ return tx->decoder_events;
+ }
+ return NULL;
+}
+
+int DNSHasEvents(void *state)
+{
+ DNSState *dns_state = (DNSState *)state;
+ return (dns_state->events > 0);
+}
+
+void *DNSGetTx(void *alstate, uint64_t tx_id)
+{
+ DNSState *dns_state = (DNSState *)alstate;
+ DNSTransaction *tx = NULL;
+
+ /* fast track: try the current tx */
+ if (dns_state->curr && dns_state->curr->tx_num == tx_id + 1)
+ return dns_state->curr;
+
+ /* fast track:
+ * if the prev tx_id is equal to the stored tx ptr, we can
+ * use this shortcut to get to the next. */
+ if (dns_state->iter) {
+ if (tx_id == dns_state->iter->tx_num) {
+ tx = TAILQ_NEXT(dns_state->iter, next);
+ if (tx && tx->tx_num == tx_id + 1) {
+ dns_state->iter = tx;
+ return tx;
+ }
+ }
+ }
+
+ /* no luck with the fast tracks, do the full list walk */
+ TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+ SCLogDebug("tx->tx_num %u, tx_id %"PRIu64, tx->tx_num, (tx_id+1));
+ if ((tx_id+1) != tx->tx_num)
+ continue;
+
+ SCLogDebug("returning tx %p", tx);
+ dns_state->iter = tx;
+ return tx;
+ }
+
+ return NULL;
+}
+
+uint64_t DNSGetTxCnt(void *alstate)
+{
+ DNSState *dns_state = (DNSState *)alstate;
+ return (uint64_t)dns_state->transaction_max;
+}
+
+int DNSGetAlstateProgress(void *tx, uint8_t direction)
+{
+ DNSTransaction *dns_tx = (DNSTransaction *)tx;
+ if (direction & STREAM_TOCLIENT) {
+ /* response side of the tx is done if we parsed a reply
+ * or if we tagged this tx as 'reply lost'. */
+ return (dns_tx->replied|dns_tx->reply_lost) ? 1 : 0;
+ }
+ else {
+ /* tx is only created if we have a complete request,
+ * or if we lost the request. Either way, if we have
+ * a tx it we consider the request complete. */
+ return 1;
+ }
+}
+
+/** \brief get value for 'complete' status in DNS
+ *
+ * For DNS we use a simple bool. 1 means done.
+ */
+int DNSGetAlstateProgressCompletionStatus(uint8_t direction)
+{
+ return 1;
+}
+
+void DNSSetEvent(DNSState *s, uint8_t e)
+{
+ if (s && s->curr) {
+ SCLogDebug("s->curr->decoder_events %p", s->curr->decoder_events);
+ AppLayerDecoderEventsSetEventRaw(&s->curr->decoder_events, e);
+ SCLogDebug("s->curr->decoder_events %p", s->curr->decoder_events);
+ s->events++;
+ } else {
+ SCLogDebug("couldn't set event %u", e);
+ }
+}
+
+/** \internal
+ * \brief Allocate a DNS TX
+ * \retval tx or NULL */
+static DNSTransaction *DNSTransactionAlloc(DNSState *state, const uint16_t tx_id)
+{
+ if (DNSCheckMemcap(sizeof(DNSTransaction), state) < 0)
+ return NULL;
+
+ DNSTransaction *tx = SCMalloc(sizeof(DNSTransaction));
+ if (unlikely(tx == NULL))
+ return NULL;
+ DNSIncrMemcap(sizeof(DNSTransaction), state);
+
+ memset(tx, 0x00, sizeof(DNSTransaction));
+
+ TAILQ_INIT(&tx->query_list);
+ TAILQ_INIT(&tx->answer_list);
+ TAILQ_INIT(&tx->authority_list);
+
+ tx->tx_id = tx_id;
+ return tx;
+}
+
+/** \internal
+ * \brief Free a DNS TX
+ * \param tx DNS TX to free */
+static void DNSTransactionFree(DNSTransaction *tx, DNSState *state)
+{
+ SCEnter();
+
+ DNSQueryEntry *q = NULL;
+ while ((q = TAILQ_FIRST(&tx->query_list))) {
+ TAILQ_REMOVE(&tx->query_list, q, next);
+ DNSDecrMemcap((sizeof(DNSQueryEntry) + q->len), state);
+ SCFree(q);
+ }
+
+ DNSAnswerEntry *a = NULL;
+ while ((a = TAILQ_FIRST(&tx->answer_list))) {
+ TAILQ_REMOVE(&tx->answer_list, a, next);
+ DNSDecrMemcap((sizeof(DNSAnswerEntry) + a->fqdn_len + a->data_len), state);
+ SCFree(a);
+ }
+ while ((a = TAILQ_FIRST(&tx->authority_list))) {
+ TAILQ_REMOVE(&tx->authority_list, a, next);
+ DNSDecrMemcap((sizeof(DNSAnswerEntry) + a->fqdn_len + a->data_len), state);
+ SCFree(a);
+ }
+
+ AppLayerDecoderEventsFreeEvents(&tx->decoder_events);
+
+ if (tx->de_state != NULL) {
+ DetectEngineStateFree(tx->de_state);
+ BUG_ON(state->tx_with_detect_state_cnt == 0);
+ state->tx_with_detect_state_cnt--;
+ }
+
+ if (state->iter == tx)
+ state->iter = NULL;
+
+ DNSDecrMemcap(sizeof(DNSTransaction), state);
+ SCFree(tx);
+ SCReturn;
+}
+
+/**
+ * \brief dns transaction cleanup callback
+ */
+void DNSStateTransactionFree(void *state, uint64_t tx_id)
+{
+ SCEnter();
+
+ DNSState *dns_state = state;
+ DNSTransaction *tx = NULL;
+
+ SCLogDebug("state %p, id %"PRIu64, dns_state, tx_id);
+
+ TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+ SCLogDebug("tx %p tx->tx_num %u, tx_id %"PRIu64, tx, tx->tx_num, (tx_id+1));
+ if ((tx_id+1) < tx->tx_num)
+ break;
+ else if ((tx_id+1) > tx->tx_num)
+ continue;
+
+ if (tx == dns_state->curr)
+ dns_state->curr = NULL;
+
+ if (tx->decoder_events != NULL) {
+ if (tx->decoder_events->cnt <= dns_state->events)
+ dns_state->events -= tx->decoder_events->cnt;
+ else
+ dns_state->events = 0;
+ }
+
+ TAILQ_REMOVE(&dns_state->tx_list, tx, next);
+ DNSTransactionFree(tx, state);
+ break;
+ }
+ SCReturn;
+}
+
+/** \internal
+ * \brief Find the DNS Tx in the state
+ * \param tx_id id of the tx
+ * \retval tx or NULL if not found */
+DNSTransaction *DNSTransactionFindByTxId(const DNSState *dns_state, const uint16_t tx_id)
+{
+ if (dns_state->curr == NULL)
+ return NULL;
+
+ /* fast path */
+ if (dns_state->curr->tx_id == tx_id) {
+ return dns_state->curr;
+
+ /* slow path, iterate list */
+ } else {
+ DNSTransaction *tx = NULL;
+ TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
+ if (tx->tx_id == tx_id) {
+ return tx;
+ }
+ }
+ }
+ /* not found */
+ return NULL;
+}
+
+int DNSStateHasTxDetectState(void *alstate)
+{
+ DNSState *state = (DNSState *)alstate;
+ return (state->tx_with_detect_state_cnt > 0);
+}
+
+DetectEngineState *DNSGetTxDetectState(void *vtx)
+{
+ DNSTransaction *tx = (DNSTransaction *)vtx;
+ return tx->de_state;
+}
+
+int DNSSetTxDetectState(void *alstate, void *vtx, DetectEngineState *s)
+{
+ DNSState *state = (DNSState *)alstate;
+ DNSTransaction *tx = (DNSTransaction *)vtx;
+ state->tx_with_detect_state_cnt++;
+ tx->de_state = s;
+ return 0;
+}
+
+void *DNSStateAlloc(void)
+{
+ void *s = SCMalloc(sizeof(DNSState));
+ if (unlikely(s == NULL))
+ return NULL;
+
+ memset(s, 0, sizeof(DNSState));
+
+ DNSState *dns_state = (DNSState *)s;
+
+ DNSIncrMemcap(sizeof(DNSState), dns_state);
+
+ TAILQ_INIT(&dns_state->tx_list);
+ return s;
+}
+
+void DNSStateFree(void *s)
+{
+ SCEnter();
+ if (s) {
+ DNSState *dns_state = (DNSState *) s;
+
+ DNSTransaction *tx = NULL;
+ while ((tx = TAILQ_FIRST(&dns_state->tx_list))) {
+ TAILQ_REMOVE(&dns_state->tx_list, tx, next);
+ DNSTransactionFree(tx, dns_state);
+ }
+
+ if (dns_state->buffer != NULL) {
+ DNSDecrMemcap(0xffff, dns_state); /** TODO update if/once we alloc
+ * in a smarter way */
+ SCFree(dns_state->buffer);
+ }
+
+ BUG_ON(dns_state->tx_with_detect_state_cnt > 0);
+
+ DNSDecrMemcap(sizeof(DNSState), dns_state);
+ BUG_ON(dns_state->memuse > 0);
+ SCFree(s);
+ }
+ SCReturn;
+}
+
+/** \brief Validation checks for DNS request header
+ *
+ * Will set decoder events if anomalies are found.
+ *
+ * \retval 0 ok
+ * \retval -1 error
+ */
+int DNSValidateRequestHeader(DNSState *dns_state, const DNSHeader *dns_header)
+{
+ uint16_t flags = ntohs(dns_header->flags);
+
+ if ((flags & 0x8000) != 0) {
+ SCLogDebug("not a request 0x%04x", flags);
+ DNSSetEvent(dns_state, DNS_DECODER_EVENT_NOT_A_REQUEST);
+ goto bad_data;
+ }
+
+ if ((flags & 0x0040) != 0) {
+ SCLogDebug("Z flag not 0, 0x%04x", flags);
+ DNSSetEvent(dns_state, DNS_DECODER_EVENT_Z_FLAG_SET);
+ goto bad_data;
+ }
+
+ return 0;
+bad_data:
+ return -1;
+}
+
+/** \brief Validation checks for DNS response header
+ *
+ * Will set decoder events if anomalies are found.
+ *
+ * \retval 0 ok
+ * \retval -1 error
+ */
+int DNSValidateResponseHeader(DNSState *dns_state, const DNSHeader *dns_header)
+{
+ uint16_t flags = ntohs(dns_header->flags);
+
+ if ((flags & 0x8000) == 0) {
+ SCLogDebug("not a response 0x%04x", flags);
+ DNSSetEvent(dns_state, DNS_DECODER_EVENT_NOT_A_RESPONSE);
+ goto bad_data;
+ }
+
+ if ((flags & 0x0040) != 0) {
+ SCLogDebug("Z flag not 0, 0x%04x", flags);
+ DNSSetEvent(dns_state, DNS_DECODER_EVENT_Z_FLAG_SET);
+ goto bad_data;
+ }
+
+ return 0;
+bad_data:
+ return -1;
+}
+
+/** \internal
+ * \brief check the query list to see if we already have this exact query
+ * \retval bool true or false
+ */
+static int QueryIsDuplicate(DNSTransaction *tx, const uint8_t *fqdn, const uint16_t fqdn_len,
+ const uint16_t type, const uint16_t class)
+{
+ DNSQueryEntry *q = NULL;
+
+ TAILQ_FOREACH(q, &tx->query_list, next) {
+ uint8_t *qfqdn = (uint8_t *)q + sizeof(DNSQueryEntry);
+
+ if (q->len == fqdn_len && q->type == type &&
+ q->class == class &&
+ SCMemcmp(qfqdn, fqdn, fqdn_len) == 0) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void DNSStoreQueryInState(DNSState *dns_state, const uint8_t *fqdn, const uint16_t fqdn_len,
+ const uint16_t type, const uint16_t class, const uint16_t tx_id)
+{
+ /* flood protection */
+ if (dns_state->givenup)
+ return;
+
+ /* find the tx and see if this is an exact duplicate */
+ DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
+ if ((tx != NULL) && (QueryIsDuplicate(tx, fqdn, fqdn_len, type, class) == TRUE)) {
+ SCLogDebug("query is duplicate");
+ return;
+ }
+
+ /* see if the last tx is unreplied */
+ if (dns_state->curr != tx && dns_state->curr != NULL &&
+ dns_state->curr->replied == 0)
+ {
+ dns_state->curr->reply_lost = 1;
+ dns_state->unreplied_cnt++;
+
+ /* check flood limit */
+ if (dns_config.request_flood != 0 &&
+ dns_state->unreplied_cnt > dns_config.request_flood) {
+ DNSSetEvent(dns_state, DNS_DECODER_EVENT_FLOODED);
+ dns_state->givenup = 1;
+ }
+ }
+
+ if (tx == NULL) {
+ tx = DNSTransactionAlloc(dns_state, tx_id);
+ if (tx == NULL)
+ return;
+ dns_state->transaction_max++;
+ SCLogDebug("dns_state->transaction_max updated to %"PRIu64, dns_state->transaction_max);
+ TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
+ dns_state->curr = tx;
+ tx->tx_num = dns_state->transaction_max;
+ SCLogDebug("new tx %u with internal id %u", tx->tx_id, tx->tx_num);
+ }
+
+ if (DNSCheckMemcap((sizeof(DNSQueryEntry) + fqdn_len), dns_state) < 0)
+ return;
+ DNSQueryEntry *q = SCMalloc(sizeof(DNSQueryEntry) + fqdn_len);
+ if (unlikely(q == NULL))
+ return;
+ DNSIncrMemcap((sizeof(DNSQueryEntry) + fqdn_len), dns_state);
+
+ q->type = type;
+ q->class = class;
+ q->len = fqdn_len;
+ memcpy((uint8_t *)q + sizeof(DNSQueryEntry), fqdn, fqdn_len);
+
+ TAILQ_INSERT_TAIL(&tx->query_list, q, next);
+
+ SCLogDebug("Query for TX %04x stored", tx_id);
+}
+
+void DNSStoreAnswerInState(DNSState *dns_state, const int rtype, const uint8_t *fqdn,
+ const uint16_t fqdn_len, const uint16_t type, const uint16_t class, const uint16_t ttl,
+ const uint8_t *data, const uint16_t data_len, const uint16_t tx_id)
+{
+ DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
+ if (tx == NULL) {
+ tx = DNSTransactionAlloc(dns_state, tx_id);
+ if (tx == NULL)
+ return;
+ TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
+ dns_state->curr = tx;
+ tx->tx_num = dns_state->transaction_max;
+ }
+
+ if (DNSCheckMemcap((sizeof(DNSAnswerEntry) + fqdn_len + data_len), dns_state) < 0)
+ return;
+ DNSAnswerEntry *q = SCMalloc(sizeof(DNSAnswerEntry) + fqdn_len + data_len);
+ if (unlikely(q == NULL))
+ return;
+ DNSIncrMemcap((sizeof(DNSAnswerEntry) + fqdn_len + data_len), dns_state);
+
+ q->type = type;
+ q->class = class;
+ q->ttl = ttl;
+ q->fqdn_len = fqdn_len;
+ q->data_len = data_len;
+
+ uint8_t *ptr = (uint8_t *)q + sizeof(DNSAnswerEntry);
+ if (fqdn != NULL && fqdn_len > 0) {
+ memcpy(ptr, fqdn, fqdn_len);
+ ptr += fqdn_len;
+ }
+ if (data != NULL && data_len > 0) {
+ memcpy(ptr, data, data_len);
+ }
+
+ if (rtype == DNS_LIST_ANSWER)
+ TAILQ_INSERT_TAIL(&tx->answer_list, q, next);
+ else if (rtype == DNS_LIST_AUTHORITY)
+ TAILQ_INSERT_TAIL(&tx->authority_list, q, next);
+ else
+ BUG_ON(1);
+
+ SCLogDebug("Answer for TX %04x stored", tx_id);
+
+ /* mark tx is as replied so we can log it */
+ tx->replied = 1;
+
+ /* reset unreplied counter */
+ dns_state->unreplied_cnt = 0;
+}
+
+/** \internal
+ * \brief get domain name from dns packet
+ *
+ * In case of compressed name storage this function follows the ptrs to
+ * create the full domain name.
+ *
+ * The length bytes are converted into dots, e.g. |03|com|00| becomes
+ * .com
+ * The trailing . is not stored.
+ *
+ * \param input input buffer (complete dns record)
+ * \param input_len lenght of input buffer
+ * \param offset offset into @input where dns name starts
+ * \param fqdn buffer to store result
+ * \param fqdn_size size of @fqdn buffer
+ * \retval 0 on error/no buffer
+ * \retval size size of fqdn
+ */
+static uint16_t DNSResponseGetNameByOffset(const uint8_t * const input, const uint32_t input_len,
+ const uint16_t offset, uint8_t *fqdn, const size_t fqdn_size)
+{
+ if (input + input_len < input + offset + 1) {
+ SCLogDebug("input buffer too small for domain of len %u", offset);
+ goto insufficient_data;
+ }
+
+ int steps = 0;
+ uint16_t fqdn_offset = 0;
+ uint8_t length = *(input + offset);
+ const uint8_t *qdata = input + offset;
+ SCLogDebug("qry length %u", length);
+
+ if (length == 0) {
+ memcpy(fqdn, "<root>", 6);
+ SCReturnUInt(6U);
+ }
+
+ while (length != 0) {
+ int cnt = 0;
+ while (length & 0xc0) {
+ uint16_t offset = ((length & 0x3f) << 8) + *(qdata+1);
+ qdata = (const uint8_t *)input + offset;
+
+ if (input + input_len < qdata + 1) {
+ SCLogDebug("input buffer too small");
+ goto insufficient_data;
+ }
+
+ length = *qdata;
+ SCLogDebug("qry length %u", length);
+
+ if (cnt++ == 100) {
+ SCLogDebug("too many pointer iterations, loop?");
+ goto bad_data;
+ }
+ }
+ qdata++;
+
+ if (length == 0) {
+ break;
+ }
+
+ if (input + input_len < qdata + length) {
+ SCLogDebug("input buffer too small for domain of len %u", length);
+ goto insufficient_data;
+ }
+ //PrintRawDataFp(stdout, qdata, length);
+
+ if ((size_t)(fqdn_offset + length + 1) < fqdn_size) {
+ memcpy(fqdn + fqdn_offset, qdata, length);
+ fqdn_offset += length;
+ fqdn[fqdn_offset++] = '.';
+ }
+ qdata += length;
+
+ if (input + input_len < qdata + 1) {
+ SCLogDebug("input buffer too small for len field");
+ goto insufficient_data;
+ }
+
+ length = *qdata;
+ SCLogDebug("qry length %u", length);
+ steps++;
+ if (steps >= 255)
+ goto bad_data;
+ }
+ if (fqdn_offset) {
+ fqdn_offset--;
+ }
+ //PrintRawDataFp(stdout, fqdn, fqdn_offset);
+ SCReturnUInt(fqdn_offset);
+bad_data:
+insufficient_data:
+ SCReturnUInt(0U);
+}
+
+/** \internal
+ * \brief skip past domain name field
+ *
+ * Skip the domain at position data. We don't care about following compressed names
+ * as we only want to know when the next part of the buffer starts
+ *
+ * \param input input buffer (complete dns record)
+ * \param input_len lenght of input buffer
+ * \param data current position
+ *
+ * \retval NULL on out of bounds data
+ * \retval sdata ptr to position in buffer past the name
+ */
+static const uint8_t *SkipDomain(const uint8_t * const input,
+ const uint32_t input_len, const uint8_t *data)
+{
+ const uint8_t *sdata = data;
+ while (*sdata != 0x00) {
+ if (*sdata & 0xc0) {
+ sdata++;
+ break;
+ } else {
+ sdata += ((*sdata) + 1);
+ }
+ if (input + input_len < sdata) {
+ SCLogDebug("input buffer too small for data of len");
+ goto insufficient_data;
+ }
+ }
+ sdata++;
+ if (input + input_len < sdata) {
+ SCLogDebug("input buffer too small for data of len");
+ goto insufficient_data;
+ }
+ return sdata;
+insufficient_data:
+ return NULL;
+}
+
+const uint8_t *DNSReponseParse(DNSState *dns_state, const DNSHeader * const dns_header,
+ const uint16_t num, const DnsListEnum list, const uint8_t * const input,
+ const uint32_t input_len, const uint8_t *data)
+{
+ if (input + input_len < data + 2) {
+ SCLogDebug("input buffer too small for record 'name' field, record %u, "
+ "total answer_rr %u", num, ntohs(dns_header->answer_rr));
+ goto insufficient_data;
+ }
+
+ uint8_t fqdn[DNS_MAX_SIZE];
+ uint16_t fqdn_len = 0;
+
+ /* see if name is compressed */
+ if (!(data[0] & 0xc0)) {
+ if ((fqdn_len = DNSResponseGetNameByOffset(input, input_len,
+ data - input, fqdn, sizeof(fqdn))) == 0)
+ {
+#if DEBUG
+ PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+ BUG_ON(1);
+#endif
+ goto insufficient_data;
+ }
+ //PrintRawDataFp(stdout, fqdn, fqdn_len);
+ const uint8_t *tdata = SkipDomain(input, input_len, data);
+ if (tdata == NULL) {
+ goto insufficient_data;
+ }
+ data = tdata;
+ } else {
+ uint16_t offset = (data[0] & 0x3f) << 8 | data[1];
+
+ if ((fqdn_len = DNSResponseGetNameByOffset(input, input_len,
+ offset, fqdn, sizeof(fqdn))) == 0)
+ {
+#if DEBUG
+ PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+ BUG_ON(1);
+#endif
+ goto insufficient_data;
+ }
+ //PrintRawDataFp(stdout, fqdn, fqdn_len);
+ data += 2;
+ }
+
+ if (input + input_len < data + sizeof(DNSAnswerHeader)) {
+ SCLogDebug("input buffer too small for DNSAnswerHeader");
+ goto insufficient_data;
+ }
+
+ const DNSAnswerHeader *head = (DNSAnswerHeader *)data;
+
+ data += sizeof(DNSAnswerHeader);
+
+ 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;
+ }
+
+ 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));
+ //SCLogInfo("A %s TTL %u", a, ntohl(head->ttl));
+
+ DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+ ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+ data, 4, ntohs(dns_header->tx_id));
+ } 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));
+
+ DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+ ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+ data, 16, 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:
+ {
+ uint8_t name[DNS_MAX_SIZE];
+ uint16_t name_len = 0;
+ uint8_t skip = 0;
+
+ if (ntohs(head->type) == DNS_RECORD_TYPE_MX) {
+ // Skip the preference header
+ skip = 2;
+ }
+
+ 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);
+#endif
+ goto insufficient_data;
+ }
+
+ DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+ ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+ name, name_len, ntohs(dns_header->tx_id));
+
+ data += ntohs(head->len);
+ break;
+ }
+ case DNS_RECORD_TYPE_NS:
+ case DNS_RECORD_TYPE_SOA:
+ {
+ uint8_t pname[DNS_MAX_SIZE];
+ uint16_t pname_len = 0;
+
+ if ((pname_len = DNSResponseGetNameByOffset(input, input_len,
+ data - input, pname, sizeof(pname))) == 0)
+ {
+#if DEBUG
+ PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+ BUG_ON(1);
+#endif
+ goto insufficient_data;
+ }
+
+ if (ntohs(head->type) == DNS_RECORD_TYPE_SOA) {
+ const uint8_t *sdata = SkipDomain(input, input_len, data);
+ if (sdata == NULL) {
+ goto insufficient_data;
+ }
+
+ uint8_t pmail[DNS_MAX_SIZE];
+ uint16_t pmail_len = 0;
+ SCLogDebug("getting pmail");
+ if ((pmail_len = DNSResponseGetNameByOffset(input, input_len,
+ sdata - input, pmail, sizeof(pmail))) == 0)
+ {
+#if DEBUG
+ PrintRawDataFp(stdout, (uint8_t *)input, input_len);
+ BUG_ON(1);
+#endif
+ goto insufficient_data;
+ }
+ SCLogDebug("pmail_len %u", pmail_len);
+ //PrintRawDataFp(stdout, (uint8_t *)pmail, pmail_len);
+
+ const uint8_t *tdata = SkipDomain(input, input_len, sdata);
+ if (tdata == NULL) {
+ goto insufficient_data;
+ }
+#if DEBUG
+ struct Trailer {
+ uint32_t serial;
+ uint32_t refresh;
+ uint32_t retry;
+ uint32_t experiation;
+ uint32_t minttl;
+ } *tail = (struct Trailer *)tdata;
+
+ if (input + input_len < tdata + sizeof(struct Trailer)) {
+ SCLogDebug("input buffer too small for data of len");
+ goto insufficient_data;
+ }
+
+ SCLogDebug("serial %u refresh %u retry %u exp %u min ttl %u",
+ ntohl(tail->serial), ntohl(tail->refresh),
+ ntohl(tail->retry), ntohl(tail->experiation),
+ ntohl(tail->minttl));
+#endif
+ }
+
+ DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+ ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+ pname, pname_len, ntohs(dns_header->tx_id));
+
+ data += ntohs(head->len);
+ break;
+ }
+ case DNS_RECORD_TYPE_TXT:
+ {
+ uint16_t datalen = ntohs(head->len);
+ uint8_t txtlen = *data;
+ const uint8_t *tdata = data + 1;
+
+ do {
+ //PrintRawDataFp(stdout, (uint8_t*)tdata, txtlen);
+
+ if (txtlen > datalen)
+ goto bad_data;
+
+ DNSStoreAnswerInState(dns_state, list, fqdn, fqdn_len,
+ ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+ (uint8_t*)tdata, (uint16_t)txtlen, ntohs(dns_header->tx_id));
+
+ datalen -= txtlen;
+ tdata += txtlen;
+ txtlen = *tdata;
+
+ tdata++;
+ datalen--;
+
+ SCLogDebug("datalen %u, txtlen %u", datalen, txtlen);
+ } while (datalen > 1);
+
+ data += ntohs(head->len);
+ break;
+ }
+ default: /* unsupported record */
+ {
+ DNSStoreAnswerInState(dns_state, list, NULL, 0,
+ ntohs(head->type), ntohs(head->class), ntohl(head->ttl),
+ NULL, 0, ntohs(dns_header->tx_id));
+
+ //PrintRawDataFp(stdout, data, ntohs(head->len));
+ data += ntohs(head->len);
+ break;
+ }
+ }
+ return data;
+bad_data:
+insufficient_data:
+ return NULL;
+}
+
+void DNSCreateTypeString(uint16_t type, char *str, size_t str_size)
+{
+ switch (type) {
+ case DNS_RECORD_TYPE_A:
+ snprintf(str, str_size, "A");
+ break;
+ case DNS_RECORD_TYPE_NS:
+ snprintf(str, str_size, "NS");
+ break;
+ case DNS_RECORD_TYPE_AAAA:
+ snprintf(str, str_size, "AAAA");
+ break;
+ case DNS_RECORD_TYPE_TXT:
+ snprintf(str, str_size, "TXT");
+ break;
+ case DNS_RECORD_TYPE_CNAME:
+ snprintf(str, str_size, "CNAME");
+ break;
+ case DNS_RECORD_TYPE_SOA:
+ snprintf(str, str_size, "SOA");
+ break;
+ case DNS_RECORD_TYPE_MX:
+ snprintf(str, str_size, "MX");
+ break;
+ case DNS_RECORD_TYPE_PTR:
+ snprintf(str, str_size, "PTR");
+ break;
+ case DNS_RECORD_TYPE_ANY:
+ snprintf(str, str_size, "ANY");
+ break;
+ case DNS_RECORD_TYPE_TKEY:
+ snprintf(str, str_size, "TKEY");
+ break;
+ case DNS_RECORD_TYPE_TSIG:
+ snprintf(str, str_size, "TSIG");
+ break;
+ case DNS_RECORD_TYPE_SRV:
+ snprintf(str, str_size, "SRV");
+ break;
+ case DNS_RECORD_TYPE_NAPTR:
+ snprintf(str, str_size, "NAPTR");
+ break;
+ case DNS_RECORD_TYPE_DS:
+ snprintf(str, str_size, "DS");
+ break;
+ case DNS_RECORD_TYPE_RRSIG:
+ snprintf(str, str_size, "RRSIG");
+ break;
+ case DNS_RECORD_TYPE_NSEC:
+ snprintf(str, str_size, "NSEC");
+ break;
+ case DNS_RECORD_TYPE_NSEC3:
+ snprintf(str, str_size, "NSEC3");
+ break;
+ default:
+ 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);
+ }
+}