diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
commit | 8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch) | |
tree | c7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/app-layer-dns-common.c | |
parent | 13d05bc8458758ee39cb829098241e89616717ee (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.c | 1141 |
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); + } +} |