mirror of https://github.com/OISF/suricata
DNS TCP and UDP parser and DNS response logger
parent
4521de2dfd
commit
8e01cba85d
@ -0,0 +1,641 @@
|
||||
/* Copyright (C) 2013 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 "app-layer-dns-common.h"
|
||||
#ifdef DEBUG
|
||||
#include "util-print.h"
|
||||
#endif
|
||||
|
||||
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, },
|
||||
|
||||
{ NULL, -1 },
|
||||
};
|
||||
|
||||
/** \brief register event map */
|
||||
void DNSAppLayerDecoderEventsRegister(int alproto) {
|
||||
AppLayerDecoderEventsModuleRegister(alproto, dns_decoder_event_table);
|
||||
}
|
||||
|
||||
void *DNSStateAlloc(void) {
|
||||
void *s = SCMalloc(sizeof(DNSState));
|
||||
if (unlikely(s == NULL))
|
||||
return NULL;
|
||||
|
||||
memset(s, 0, sizeof(DNSState));
|
||||
|
||||
DNSState *dns_state = (DNSState *)s;
|
||||
|
||||
TAILQ_INIT(&dns_state->tx_list);
|
||||
return s;
|
||||
}
|
||||
|
||||
void DNSStateFree(void *s) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (dns_state->buffer != NULL)
|
||||
SCFree(dns_state->buffer);
|
||||
|
||||
SCFree(s);
|
||||
s = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void *DNSGetTx(void *alstate, uint64_t tx_id) {
|
||||
/* todo */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t DNSGetTxCnt(void *alstate) {
|
||||
DNSState *dns_state = (DNSState *)alstate;
|
||||
return (uint64_t)dns_state->transaction_cnt;
|
||||
}
|
||||
|
||||
int DNSGetAlstateProgress(void *tx, uint8_t direction) {
|
||||
DNSTransaction *dns_tx = (DNSTransaction *)tx;
|
||||
return dns_tx->replied;
|
||||
}
|
||||
|
||||
/* value for tx->replied value */
|
||||
int DNSGetAlstateProgressCompletionStatus(uint8_t direction) {
|
||||
return (direction == 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
/** \internal
|
||||
* \brief Allocate a DNS TX
|
||||
* \retval tx or NULL */
|
||||
DNSTransaction *DNSTransactionAlloc(const uint16_t tx_id) {
|
||||
DNSTransaction *tx = SCMalloc(sizeof(DNSTransaction));
|
||||
if (tx == NULL)
|
||||
return NULL;
|
||||
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 */
|
||||
void DNSTransactionFree(DNSTransaction *tx) {
|
||||
DNSQueryEntry *q = NULL;
|
||||
while ((q = TAILQ_FIRST(&tx->query_list))) {
|
||||
TAILQ_REMOVE(&tx->query_list, q, next);
|
||||
SCFree(q);
|
||||
}
|
||||
|
||||
DNSAnswerEntry *a = NULL;
|
||||
while ((a = TAILQ_FIRST(&tx->answer_list))) {
|
||||
TAILQ_REMOVE(&tx->answer_list, a, next);
|
||||
SCFree(a);
|
||||
}
|
||||
while ((a = TAILQ_FIRST(&tx->authority_list))) {
|
||||
TAILQ_REMOVE(&tx->authority_list, a, next);
|
||||
SCFree(a);
|
||||
}
|
||||
SCFree(tx);
|
||||
}
|
||||
|
||||
/** \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;
|
||||
}
|
||||
|
||||
/** \brief Validation checks for DNS request header
|
||||
*
|
||||
* Will set decoder events if anomalies are found.
|
||||
*
|
||||
* \retval 0 ok
|
||||
* \retval -1 error
|
||||
*/
|
||||
int DNSValidateRequestHeader(Flow *f, const DNSHeader *dns_header) {
|
||||
uint16_t flags = ntohs(dns_header->flags);
|
||||
|
||||
if ((flags & 0x8000) != 0) {
|
||||
SCLogDebug("not a request 0x%04x", flags);
|
||||
if (f != NULL)
|
||||
AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_NOT_A_REQUEST);
|
||||
goto bad_data;
|
||||
}
|
||||
|
||||
if ((flags & 0x0040) != 0) {
|
||||
SCLogDebug("Z flag not 0, 0x%04x", flags);
|
||||
if (f != NULL)
|
||||
AppLayerDecoderEventsSetEvent(f, 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(Flow *f, const DNSHeader *dns_header) {
|
||||
uint16_t flags = ntohs(dns_header->flags);
|
||||
|
||||
if ((flags & 0x8000) == 0) {
|
||||
SCLogDebug("not a response 0x%04x", flags);
|
||||
AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_NOT_A_RESPONSE);
|
||||
goto bad_data;
|
||||
}
|
||||
|
||||
if ((flags & 0x0040) != 0) {
|
||||
SCLogDebug("Z flag not 0, 0x%04x", flags);
|
||||
AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_Z_FLAG_SET);
|
||||
goto bad_data;
|
||||
}
|
||||
|
||||
return 0;
|
||||
bad_data:
|
||||
return -1;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
|
||||
if (tx == NULL) {
|
||||
tx = DNSTransactionAlloc(tx_id);
|
||||
if (tx == NULL)
|
||||
return;
|
||||
TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
|
||||
dns_state->curr = tx;
|
||||
}
|
||||
|
||||
DNSQueryEntry *q = SCMalloc(sizeof(DNSQueryEntry) + fqdn_len);
|
||||
if (q == NULL)
|
||||
return;
|
||||
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(tx_id);
|
||||
if (tx == NULL)
|
||||
return;
|
||||
TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
|
||||
dns_state->curr = tx;
|
||||
}
|
||||
|
||||
dns_state->transaction_cnt++;
|
||||
SCLogDebug("dns_state->transaction_cnt %u", dns_state->transaction_cnt);
|
||||
|
||||
DNSAnswerEntry *q = SCMalloc(sizeof(DNSAnswerEntry) + fqdn_len + data_len);
|
||||
if (q == NULL)
|
||||
return;
|
||||
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);
|
||||
memcpy(ptr, fqdn, fqdn_len);
|
||||
ptr += fqdn_len;
|
||||
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;
|
||||
}
|
||||
|
||||
/** \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) {
|
||||
SCLogInfo("input buffer too small for domain of len %u", offset);
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
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>", fqdn_size);
|
||||
SCReturnUInt(6U);
|
||||
}
|
||||
|
||||
while (length != 0) {
|
||||
if (length & 0xc0) {
|
||||
uint16_t offset = ((length & 0x3f) << 8) + *(qdata+1);
|
||||
qdata = (const uint8_t *)input + offset;
|
||||
|
||||
if (input + input_len < qdata + 1) {
|
||||
SCLogInfo("input buffer too small");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
length = *qdata;
|
||||
SCLogDebug("qry length %u", length);
|
||||
}
|
||||
qdata++;
|
||||
|
||||
if (length > 0) {
|
||||
if (input + input_len < qdata + length) {
|
||||
SCLogInfo("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) {
|
||||
SCLogInfo("input buffer too small for len field");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
length = *qdata;
|
||||
SCLogDebug("qry length %u", length);
|
||||
}
|
||||
if (fqdn_offset) {
|
||||
fqdn_offset--;
|
||||
}
|
||||
//PrintRawDataFp(stdout, fqdn, fqdn_offset);
|
||||
SCReturnUInt(fqdn_offset);
|
||||
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) {
|
||||
SCLogInfo("input buffer too small for data of len");
|
||||
goto insufficient_data;
|
||||
}
|
||||
}
|
||||
sdata++;
|
||||
if (input + input_len < sdata) {
|
||||
SCLogInfo("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) {
|
||||
SCLogInfo("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)) {
|
||||
SCLogInfo("input buffer too small for DNSAnswerHeader");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (input + input_len < data + ntohs(head->len)) {
|
||||
SCLogInfo("input buffer too small for data of len %u", ntohs(head->len));
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("TTL %u", ntohl(head->ttl));
|
||||
|
||||
if (ntohs(head->type) == DNS_RECORD_TYPE_A && 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 if (ntohs(head->type) == DNS_RECORD_TYPE_AAAA && 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 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));
|
||||
}
|
||||
|
||||
data += ntohs(head->len);
|
||||
break;
|
||||
}
|
||||
case DNS_RECORD_TYPE_MX:
|
||||
{
|
||||
data += sizeof(DNSAnswerHeader);
|
||||
|
||||
SCLogDebug("head->len %u", ntohs(head->len));
|
||||
|
||||
if (input + input_len < data + ntohs(head->len)) {
|
||||
SCLogInfo("input buffer too small for data of len %u", ntohs(head->len));
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
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 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),
|
||||
mxname, mxname_len, ntohs(dns_header->tx_id));
|
||||
|
||||
data += ntohs(head->len);
|
||||
break;
|
||||
}
|
||||
case DNS_RECORD_TYPE_NS:
|
||||
case DNS_RECORD_TYPE_SOA:
|
||||
{
|
||||
data += sizeof(DNSAnswerHeader);
|
||||
|
||||
if (input + input_len < data + ntohs(head->len)) {
|
||||
SCLogInfo("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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
SCLogInfo("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;
|
||||
}
|
||||
default: /* unsupported record */
|
||||
{
|
||||
data += sizeof(DNSAnswerHeader);
|
||||
|
||||
if (input + input_len < data + ntohs(head->len)) {
|
||||
SCLogInfo("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));
|
||||
|
||||
//PrintRawDataFp(stdout, data, ntohs(head->len));
|
||||
data += ntohs(head->len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
insufficient_data:
|
||||
return NULL;
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/* Copyright (C) 2013 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>
|
||||
*/
|
||||
|
||||
#ifndef __APP_LAYER_DNS_COMMON_H__
|
||||
#define __APP_LAYER_DNS_COMMON_H__
|
||||
|
||||
#include "app-layer-protos.h"
|
||||
#include "app-layer-parser.h"
|
||||
#include "flow.h"
|
||||
#include "queue.h"
|
||||
#include "util-byte.h"
|
||||
|
||||
#define DNS_MAX_SIZE 256
|
||||
|
||||
#define DNS_RECORD_TYPE_A 0x0001
|
||||
#define DNS_RECORD_TYPE_NS 0x0002
|
||||
|
||||
#define DNS_RECORD_TYPE_CNAME 0x0005
|
||||
#define DNS_RECORD_TYPE_SOA 0x0006
|
||||
|
||||
#define DNS_RECORD_TYPE_TXT 0x0010
|
||||
|
||||
#define DNS_RECORD_TYPE_PTR 0x000c
|
||||
#define DNS_RECORD_TYPE_MX 0x000f
|
||||
|
||||
#define DNS_RECORD_TYPE_AAAA 0x001c
|
||||
|
||||
#define DNS_RECORD_TYPE_ANY 0x00ff
|
||||
|
||||
#define DNS_RECORD_TYPE_TKEY 0x00f9
|
||||
#define DNS_RECORD_TYPE_TSIG 0x00fa /**< XXX correct? */
|
||||
|
||||
enum {
|
||||
DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE,
|
||||
DNS_DECODER_EVENT_MALFORMED_DATA,
|
||||
DNS_DECODER_EVENT_NOT_A_REQUEST,
|
||||
DNS_DECODER_EVENT_NOT_A_RESPONSE,
|
||||
DNS_DECODER_EVENT_Z_FLAG_SET,
|
||||
};
|
||||
|
||||
/** \brief DNS packet header */
|
||||
typedef struct DNSHeader_ {
|
||||
uint16_t tx_id;
|
||||
uint16_t flags;
|
||||
uint16_t questions;
|
||||
uint16_t answer_rr;
|
||||
uint16_t authority_rr;
|
||||
uint16_t additional_rr;
|
||||
} DNSHeader;
|
||||
|
||||
typedef struct DNSQueryTrailer_ {
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
} DNSQueryTrailer;
|
||||
|
||||
/** \brief DNS answer header
|
||||
* packed as we don't want alignment to mess up sizeof() */
|
||||
struct DNSAnswerHeader_ {
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
uint32_t ttl;
|
||||
uint16_t len;
|
||||
} __attribute__((__packed__));
|
||||
typedef struct DNSAnswerHeader_ DNSAnswerHeader;
|
||||
|
||||
/** \brief List types in the TX.
|
||||
* Used when storing answers from "Answer" or "Authority" */
|
||||
typedef enum {
|
||||
DNS_LIST_ANSWER = 0,
|
||||
DNS_LIST_AUTHORITY,
|
||||
} DnsListEnum;
|
||||
|
||||
/** \brief DNS Query storage. Stored in TX list.
|
||||
*
|
||||
* Layout is:
|
||||
* [list ptr][2 byte type][2 byte class][2 byte len][...data...]
|
||||
*/
|
||||
typedef struct DNSQueryEntry_ {
|
||||
TAILQ_ENTRY(DNSQueryEntry_) next;
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
uint16_t len;
|
||||
} DNSQueryEntry;
|
||||
|
||||
/** \brief DNS Answer storage. Stored in TX list.
|
||||
*
|
||||
* Layout is:
|
||||
* [list ptr][2 byte type][2 byte class][2 byte ttl] \
|
||||
* [2 byte fqdn len][2 byte data len][...fqdn...][...data...]
|
||||
*/
|
||||
typedef struct DNSAnswerEntry_ {
|
||||
TAILQ_ENTRY(DNSAnswerEntry_) next;
|
||||
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
|
||||
uint32_t ttl;
|
||||
|
||||
uint16_t fqdn_len;
|
||||
uint16_t data_len;
|
||||
} DNSAnswerEntry;
|
||||
|
||||
/** \brief DNS Transaction, request/reply with same TX id. */
|
||||
typedef struct DNSTransaction_ {
|
||||
uint16_t tx_id; /**< transaction id */
|
||||
uint16_t replied; /**< bool indicating request is
|
||||
replied to. */
|
||||
uint16_t no_such_name; /**< server said "no such name" */
|
||||
|
||||
TAILQ_HEAD(, DNSQueryEntry_) query_list; /**< list for query/queries */
|
||||
TAILQ_HEAD(, DNSAnswerEntry_) answer_list; /**< list for answers */
|
||||
TAILQ_HEAD(, DNSAnswerEntry_) authority_list; /**< list for authority records */
|
||||
|
||||
TAILQ_ENTRY(DNSTransaction_) next;
|
||||
} DNSTransaction;
|
||||
|
||||
/** \brief Per flow DNS state container */
|
||||
typedef struct DNSState_ {
|
||||
TAILQ_HEAD(, DNSTransaction_) tx_list; /**< transaction list */
|
||||
DNSTransaction *curr; /**< ptr to current tx */
|
||||
uint16_t transaction_cnt;
|
||||
uint16_t transaction_done;
|
||||
|
||||
/* used by TCP only */
|
||||
uint16_t offset;
|
||||
uint16_t record_len;
|
||||
uint8_t *buffer;
|
||||
} DNSState;
|
||||
|
||||
void RegisterDNSParsers(void);
|
||||
void DNSParserTests(void);
|
||||
void DNSParserRegisterTests(void);
|
||||
void DNSAppLayerDecoderEventsRegister(int alproto);
|
||||
|
||||
void *DNSGetTx(void *alstate, uint64_t tx_id);
|
||||
uint64_t DNSGetTxCnt(void *alstate);
|
||||
int DNSGetAlstateProgress(void *tx, uint8_t direction);
|
||||
int DNSGetAlstateProgressCompletionStatus(uint8_t direction);
|
||||
|
||||
DNSTransaction *DNSTransactionAlloc(const uint16_t tx_id);
|
||||
void DNSTransactionFree(DNSTransaction *tx);
|
||||
DNSTransaction *DNSTransactionFindByTxId(const DNSState *dns_state, const uint16_t tx_id);
|
||||
|
||||
void *DNSStateAlloc(void);
|
||||
void DNSStateFree(void *s);
|
||||
|
||||
int DNSValidateRequestHeader(Flow *f, const DNSHeader *dns_header);
|
||||
int DNSValidateResponseHeader(Flow *f, const DNSHeader *dns_header);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
uint16_t DNSUdpResponseGetNameByOffset(const uint8_t * const input, const uint32_t input_len,
|
||||
const uint16_t offset, uint8_t *fqdn, const size_t fqdn_size);
|
||||
|
||||
#endif /* __APP_LAYER_DNS_COMMON_H__ */
|
@ -0,0 +1,652 @@
|
||||
/* Copyright (C) 2013 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 "suricata.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "decode.h"
|
||||
|
||||
#include "flow-util.h"
|
||||
|
||||
#include "threads.h"
|
||||
|
||||
#include "util-print.h"
|
||||
#include "util-pool.h"
|
||||
#include "util-debug.h"
|
||||
|
||||
#include "stream-tcp-private.h"
|
||||
#include "stream-tcp-reassemble.h"
|
||||
#include "stream-tcp.h"
|
||||
#include "stream.h"
|
||||
|
||||
#include "app-layer-protos.h"
|
||||
#include "app-layer-parser.h"
|
||||
|
||||
#include "util-spm.h"
|
||||
#include "util-unittest.h"
|
||||
|
||||
#include "app-layer-dns-tcp.h"
|
||||
|
||||
struct DNSTcpHeader_ {
|
||||
uint16_t len;
|
||||
uint16_t tx_id;
|
||||
uint16_t flags;
|
||||
uint16_t questions;
|
||||
uint16_t answer_rr;
|
||||
uint16_t authority_rr;
|
||||
uint16_t additional_rr;
|
||||
} __attribute__((__packed__));
|
||||
typedef struct DNSTcpHeader_ DNSTcpHeader;
|
||||
|
||||
/** \internal
|
||||
* \param input_len at least enough for the DNSTcpHeader
|
||||
*/
|
||||
static int DNSTCPRequestParseProbe(uint8_t *input, uint32_t input_len)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
BUG_ON(input_len < sizeof(DNSTcpHeader));
|
||||
#endif
|
||||
SCLogDebug("starting %u", input_len);
|
||||
|
||||
DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input;
|
||||
if (ntohs(dns_tcp_header->len) < sizeof(DNSHeader)) {
|
||||
goto bad_data;
|
||||
}
|
||||
if (ntohs(dns_tcp_header->len) >= input_len) {
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
input += 2;
|
||||
input_len -= 2;
|
||||
DNSHeader *dns_header = (DNSHeader *)input;
|
||||
|
||||
uint16_t q;
|
||||
const uint8_t *data = input + sizeof(DNSHeader);
|
||||
|
||||
for (q = 0; q < ntohs(dns_header->questions); q++) {
|
||||
uint16_t fqdn_offset = 0;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for len field");
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("query length %u", *data);
|
||||
|
||||
while (*data != 0) {
|
||||
if (*data > 63) {
|
||||
/** \todo set event?*/
|
||||
goto bad_data;
|
||||
}
|
||||
uint8_t length = *data;
|
||||
|
||||
data++;
|
||||
|
||||
if (length > 0) {
|
||||
if (input + input_len < data + length) {
|
||||
SCLogDebug("input buffer too small for domain of len %u", length);
|
||||
goto insufficient_data;
|
||||
}
|
||||
//PrintRawDataFp(stdout, data, qry->length);
|
||||
|
||||
if ((fqdn_offset + length + 1) < DNS_MAX_SIZE) {
|
||||
fqdn_offset += length;
|
||||
} else {
|
||||
/** \todo set event? */
|
||||
goto bad_data;
|
||||
}
|
||||
}
|
||||
|
||||
data += length;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for new len");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
SCLogDebug("qry length %u", *data);
|
||||
}
|
||||
if (fqdn_offset) {
|
||||
fqdn_offset--;
|
||||
}
|
||||
|
||||
data++;
|
||||
if (input + input_len < data + sizeof(DNSQueryTrailer)) {
|
||||
SCLogDebug("input buffer too small for DNSQueryTrailer");
|
||||
goto insufficient_data;
|
||||
}
|
||||
DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
|
||||
SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
|
||||
data += sizeof(DNSQueryTrailer);
|
||||
}
|
||||
|
||||
SCReturnInt(1);
|
||||
insufficient_data:
|
||||
SCReturnInt(0);
|
||||
bad_data:
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
|
||||
static int BufferData(DNSState *dns_state, uint8_t *data, uint16_t len) {
|
||||
if (dns_state->buffer == NULL) {
|
||||
/** \todo be smarter about this, like use a pool or several pools for
|
||||
* chunks of various sizes */
|
||||
dns_state->buffer = SCMalloc(0xffff);
|
||||
if (dns_state->buffer == NULL) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((uint32_t)len + (uint32_t)dns_state->offset > (uint32_t)dns_state->record_len) {
|
||||
SCLogInfo("oh my, we have more data than the max record size. What do we do. WHAT DO WE DOOOOO!");
|
||||
BUG_ON(1);
|
||||
len = dns_state->record_len - dns_state->offset;
|
||||
}
|
||||
|
||||
memcpy(dns_state->buffer + dns_state->offset, data, len);
|
||||
dns_state->offset += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void BufferReset(DNSState *dns_state) {
|
||||
dns_state->record_len = 0;
|
||||
dns_state->offset = 0;
|
||||
}
|
||||
|
||||
static int DNSRequestParseData(Flow *f, DNSState *dns_state, const uint8_t *input, const uint32_t input_len) {
|
||||
DNSHeader *dns_header = (DNSHeader *)input;
|
||||
|
||||
if (DNSValidateRequestHeader(f, dns_header) < 0)
|
||||
goto bad_data;
|
||||
|
||||
//SCLogInfo("ID %04x", ntohs(dns_header->tx_id));
|
||||
|
||||
uint16_t q;
|
||||
const uint8_t *data = input + sizeof(DNSHeader);
|
||||
|
||||
//PrintRawDataFp(stdout, (uint8_t*)data, input_len - (data - input));
|
||||
|
||||
for (q = 0; q < ntohs(dns_header->questions); q++) {
|
||||
uint8_t fqdn[DNS_MAX_SIZE];
|
||||
uint16_t fqdn_offset = 0;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for DNSTcpQuery");
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("query length %u", *data);
|
||||
|
||||
while (*data != 0) {
|
||||
if (*data > 63) {
|
||||
/** \todo set event?*/
|
||||
goto insufficient_data;
|
||||
}
|
||||
uint8_t length = *data;
|
||||
|
||||
data++;
|
||||
|
||||
if (length > 0) {
|
||||
if (input + input_len < data + length) {
|
||||
SCLogDebug("input buffer too small for domain of len %u", length);
|
||||
goto insufficient_data;
|
||||
}
|
||||
//PrintRawDataFp(stdout, data, qry->length);
|
||||
|
||||
if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
|
||||
memcpy(fqdn + fqdn_offset, data, length);
|
||||
fqdn_offset += length;
|
||||
fqdn[fqdn_offset++] = '.';
|
||||
} else {
|
||||
/** \todo set event? */
|
||||
goto insufficient_data;
|
||||
}
|
||||
}
|
||||
|
||||
data += length;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for DNSTcpQuery(2)");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
SCLogDebug("qry length %u", *data);
|
||||
}
|
||||
if (fqdn_offset) {
|
||||
fqdn_offset--;
|
||||
}
|
||||
|
||||
data++;
|
||||
if (input + input_len < data + sizeof(DNSQueryTrailer)) {
|
||||
SCLogDebug("input buffer too small for DNSQueryTrailer");
|
||||
goto insufficient_data;
|
||||
}
|
||||
DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
|
||||
SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
|
||||
data += sizeof(DNSQueryTrailer);
|
||||
|
||||
/* store our data */
|
||||
if (dns_state != NULL) {
|
||||
DNSStoreQueryInState(dns_state, fqdn, fqdn_offset,
|
||||
ntohs(trailer->type), ntohs(trailer->class),
|
||||
ntohs(dns_header->tx_id));
|
||||
}
|
||||
}
|
||||
|
||||
SCReturnInt(1);
|
||||
bad_data:
|
||||
insufficient_data:
|
||||
SCReturnInt(-1);
|
||||
|
||||
}
|
||||
|
||||
/** \internal
|
||||
* \brief Parse DNS request packet
|
||||
*/
|
||||
static int DNSTCPRequestParse(Flow *f, void *dstate,
|
||||
AppLayerParserState *pstate,
|
||||
uint8_t *input, uint32_t input_len,
|
||||
void *local_data, AppLayerParserResult *output)
|
||||
{
|
||||
DNSState *dns_state = (DNSState *)dstate;
|
||||
SCLogDebug("starting %u", input_len);
|
||||
|
||||
/** \todo remove this when PP is fixed to enforce ipproto */
|
||||
if (f != NULL && f->proto != IPPROTO_TCP)
|
||||
SCReturnInt(-1);
|
||||
|
||||
/* probably a rst/fin sending an eof */
|
||||
if (input_len == 0) {
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
next_record:
|
||||
/* if this is the beginning of a record, we need at least the header */
|
||||
if (dns_state->offset == 0 && input_len < sizeof(DNSTcpHeader)) {
|
||||
SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader));
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("input_len %u offset %u record %u",
|
||||
input_len, dns_state->offset, dns_state->record_len);
|
||||
|
||||
/* this is the first data of this record */
|
||||
if (dns_state->offset == 0) {
|
||||
DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input;
|
||||
SCLogDebug("DNS %p", dns_tcp_header);
|
||||
|
||||
if (ntohs(dns_tcp_header->len) < sizeof(DNSHeader)) {
|
||||
/* bogus len, doesn't fit even basic dns header */
|
||||
goto bad_data;
|
||||
} else if (ntohs(dns_tcp_header->len) == (input_len-2)) {
|
||||
/* we have all data, so process w/o buffering */
|
||||
if (DNSRequestParseData(f, dns_state, input+2, input_len-2) < 0)
|
||||
goto bad_data;
|
||||
|
||||
} else if ((input_len-2) > ntohs(dns_tcp_header->len)) {
|
||||
/* we have all data, so process w/o buffering */
|
||||
if (DNSRequestParseData(f, dns_state, input+2, ntohs(dns_tcp_header->len)) < 0)
|
||||
goto bad_data;
|
||||
|
||||
/* treat the rest of the data as a (potential) new record */
|
||||
input += ntohs(dns_tcp_header->len);
|
||||
input_len -= ntohs(dns_tcp_header->len);
|
||||
goto next_record;
|
||||
} else {
|
||||
/* not enough data, store record length and buffer */
|
||||
dns_state->record_len = ntohs(dns_tcp_header->len);
|
||||
BufferData(dns_state, input+2, input_len-2);
|
||||
}
|
||||
} else if (input_len + dns_state->offset < dns_state->record_len) {
|
||||
/* we don't have the full record yet, buffer */
|
||||
BufferData(dns_state, input, input_len);
|
||||
} else if (input_len > (uint32_t)(dns_state->record_len - dns_state->offset)) {
|
||||
/* more data than expected, we may have another record coming up */
|
||||
uint16_t need = (dns_state->record_len - dns_state->offset);
|
||||
BufferData(dns_state, input, need);
|
||||
int r = DNSRequestParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
|
||||
BufferReset(dns_state);
|
||||
if (r < 0)
|
||||
goto bad_data;
|
||||
|
||||
/* treat the rest of the data as a (potential) new record */
|
||||
input += need;
|
||||
input_len -= need;
|
||||
goto next_record;
|
||||
} else {
|
||||
/* implied exactly the amount of data we want
|
||||
* add current to buffer, then inspect buffer */
|
||||
BufferData(dns_state, input, input_len);
|
||||
int r = DNSRequestParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
|
||||
BufferReset(dns_state);
|
||||
if (r < 0)
|
||||
goto bad_data;
|
||||
}
|
||||
|
||||
SCReturnInt(1);
|
||||
insufficient_data:
|
||||
SCReturnInt(-1);
|
||||
bad_data:
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
|
||||
static int DNSReponseParseData(Flow *f, DNSState *dns_state, const uint8_t *input, const uint32_t input_len) {
|
||||
DNSHeader *dns_header = (DNSHeader *)input;
|
||||
|
||||
if (DNSValidateResponseHeader(f, dns_header) < 0)
|
||||
goto bad_data;
|
||||
|
||||
DNSTransaction *tx = NULL;
|
||||
int found = 0;
|
||||
TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
|
||||
if (tx->tx_id == ntohs(dns_header->tx_id)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
|
||||
AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
|
||||
}
|
||||
|
||||
uint16_t q;
|
||||
const uint8_t *data = input + sizeof(DNSHeader);
|
||||
for (q = 0; q < ntohs(dns_header->questions); q++) {
|
||||
uint8_t fqdn[DNS_MAX_SIZE];
|
||||
uint16_t fqdn_offset = 0;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for len field");
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("qry length %u", *data);
|
||||
|
||||
while (*data != 0) {
|
||||
uint8_t length = *data;
|
||||
data++;
|
||||
|
||||
if (length > 0) {
|
||||
if (input + input_len < data + length) {
|
||||
SCLogDebug("input buffer too small for domain of len %u", length);
|
||||
goto insufficient_data;
|
||||
}
|
||||
//PrintRawDataFp(stdout, data, length);
|
||||
|
||||
if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
|
||||
memcpy(fqdn + fqdn_offset, data, length);
|
||||
fqdn_offset += length;
|
||||
fqdn[fqdn_offset++] = '.';
|
||||
}
|
||||
}
|
||||
|
||||
data += length;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for len field");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
length = *data;
|
||||
SCLogDebug("length %u", length);
|
||||
}
|
||||
if (fqdn_offset) {
|
||||
fqdn_offset--;
|
||||
}
|
||||
|
||||
data++;
|
||||
if (input + input_len < data + sizeof(DNSQueryTrailer)) {
|
||||
SCLogDebug("input buffer too small for DNSQueryTrailer");
|
||||
goto insufficient_data;
|
||||
}
|
||||
#if DEBUG
|
||||
DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
|
||||
SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
|
||||
#endif
|
||||
data += sizeof(DNSQueryTrailer);
|
||||
}
|
||||
|
||||
for (q = 0; q < ntohs(dns_header->answer_rr); q++) {
|
||||
data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_ANSWER,
|
||||
input, input_len, data);
|
||||
if (data == NULL) {
|
||||
goto insufficient_data;
|
||||
}
|
||||
}
|
||||
|
||||
//PrintRawDataFp(stdout, (uint8_t *)data, input_len - (data - input));
|
||||
for (q = 0; q < ntohs(dns_header->authority_rr); q++) {
|
||||
data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_AUTHORITY,
|
||||
input, input_len, data);
|
||||
if (data == NULL) {
|
||||
goto insufficient_data;
|
||||
}
|
||||
}
|
||||
|
||||
SCReturnInt(1);
|
||||
bad_data:
|
||||
insufficient_data:
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
|
||||
/** \internal
|
||||
* \brief DNS TCP record parser, entry function
|
||||
*
|
||||
* Parses a DNS TCP record and fills the DNS state
|
||||
*
|
||||
* As TCP records can be 64k we'll have to buffer the data. Streaming parsing
|
||||
* would have been _very_ tricky due to the way names are compressed in DNS
|
||||
*
|
||||
*/
|
||||
static int DNSTCPResponseParse(Flow *f, void *dstate,
|
||||
AppLayerParserState *pstate,
|
||||
uint8_t *input, uint32_t input_len,
|
||||
void *local_data, AppLayerParserResult *output)
|
||||
{
|
||||
DNSState *dns_state = (DNSState *)dstate;
|
||||
|
||||
/** \todo remove this when PP is fixed to enforce ipproto */
|
||||
if (f != NULL && f->proto != IPPROTO_TCP)
|
||||
SCReturnInt(-1);
|
||||
|
||||
/* probably a rst/fin sending an eof */
|
||||
if (input_len == 0) {
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
next_record:
|
||||
/* if this is the beginning of a record, we need at least the header */
|
||||
if (dns_state->offset == 0 && input_len < sizeof(DNSTcpHeader)) {
|
||||
SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader));
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("input_len %u offset %u record %u",
|
||||
input_len, dns_state->offset, dns_state->record_len);
|
||||
|
||||
/* this is the first data of this record */
|
||||
if (dns_state->offset == 0) {
|
||||
DNSTcpHeader *dns_tcp_header = (DNSTcpHeader *)input;
|
||||
SCLogDebug("DNS %p", dns_tcp_header);
|
||||
|
||||
if (ntohs(dns_tcp_header->len) == (input_len-2)) {
|
||||
/* we have all data, so process w/o buffering */
|
||||
if (DNSReponseParseData(f, dns_state, input+2, input_len-2) < 0)
|
||||
goto bad_data;
|
||||
|
||||
} else if ((input_len-2) > ntohs(dns_tcp_header->len)) {
|
||||
/* we have all data, so process w/o buffering */
|
||||
if (DNSReponseParseData(f, dns_state, input+2, ntohs(dns_tcp_header->len)) < 0)
|
||||
goto bad_data;
|
||||
|
||||
/* treat the rest of the data as a (potential) new record */
|
||||
input += ntohs(dns_tcp_header->len);
|
||||
input_len -= ntohs(dns_tcp_header->len);
|
||||
goto next_record;
|
||||
} else {
|
||||
/* not enough data, store record length and buffer */
|
||||
dns_state->record_len = ntohs(dns_tcp_header->len);
|
||||
BufferData(dns_state, input+2, input_len-2);
|
||||
}
|
||||
} else if (input_len + dns_state->offset < dns_state->record_len) {
|
||||
/* we don't have the full record yet, buffer */
|
||||
BufferData(dns_state, input, input_len);
|
||||
} else if (input_len > (uint32_t)(dns_state->record_len - dns_state->offset)) {
|
||||
/* more data than expected, we may have another record coming up */
|
||||
uint16_t need = (dns_state->record_len - dns_state->offset);
|
||||
BufferData(dns_state, input, need);
|
||||
int r = DNSReponseParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
|
||||
BufferReset(dns_state);
|
||||
if (r < 0)
|
||||
goto bad_data;
|
||||
|
||||
/* treat the rest of the data as a (potential) new record */
|
||||
input += need;
|
||||
input_len -= need;
|
||||
goto next_record;
|
||||
} else {
|
||||
/* implied exactly the amount of data we want
|
||||
* add current to buffer, then inspect buffer */
|
||||
BufferData(dns_state, input, input_len);
|
||||
int r = DNSReponseParseData(f, dns_state, dns_state->buffer, dns_state->record_len);
|
||||
BufferReset(dns_state);
|
||||
if (r < 0)
|
||||
goto bad_data;
|
||||
}
|
||||
SCReturnInt(1);
|
||||
insufficient_data:
|
||||
SCReturnInt(-1);
|
||||
bad_data:
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
|
||||
static uint16_t DNSTcpProbingParser(uint8_t *input, uint32_t ilen)
|
||||
{
|
||||
if (ilen == 0 || ilen < sizeof(DNSTcpHeader)) {
|
||||
SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSTcpHeader));
|
||||
return ALPROTO_UNKNOWN;
|
||||
}
|
||||
|
||||
DNSTcpHeader *dns_header = (DNSTcpHeader *)input;
|
||||
if (ntohs(dns_header->len) < sizeof(DNSHeader)) {
|
||||
/* length field bogus, won't even fit a minimal DNS header. */
|
||||
return ALPROTO_FAILED;
|
||||
} else if (ntohs(dns_header->len) > ilen) {
|
||||
int r = DNSTCPRequestParseProbe(input, ilen);
|
||||
if (r == -1) {
|
||||
/* probing parser told us "bad data", so it's not
|
||||
* DNS */
|
||||
return ALPROTO_FAILED;
|
||||
} else if (ilen > 512) {
|
||||
SCLogDebug("all the parser told us was not enough data, which is expected. Lets assume it's DNS");
|
||||
return ALPROTO_DNS_TCP;
|
||||
}
|
||||
|
||||
SCLogDebug("not yet enough info %u > %u", ntohs(dns_header->len), ilen);
|
||||
return ALPROTO_UNKNOWN;
|
||||
}
|
||||
|
||||
int r = DNSTCPRequestParseProbe(input, ilen);
|
||||
if (r != 1)
|
||||
return ALPROTO_FAILED;
|
||||
|
||||
SCLogDebug("ALPROTO_DNS_TCP");
|
||||
return ALPROTO_DNS_TCP;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Update the transaction id based on the dns state
|
||||
*/
|
||||
void DNSStateUpdateTransactionId(void *state, uint16_t *id) {
|
||||
SCEnter();
|
||||
|
||||
DNSState *s = state;
|
||||
|
||||
SCLogDebug("original id %"PRIu16", s->transaction_cnt %"PRIu16,
|
||||
*id, (s->transaction_cnt));
|
||||
|
||||
if ((s->transaction_cnt) > (*id)) {
|
||||
SCLogDebug("original id %"PRIu16", updating with s->transaction_cnt %"PRIu16,
|
||||
*id, (s->transaction_cnt));
|
||||
|
||||
(*id) = (s->transaction_cnt);
|
||||
|
||||
SCLogDebug("updated id %"PRIu16, *id);
|
||||
}
|
||||
|
||||
SCReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief dns transaction cleanup callback
|
||||
*/
|
||||
void DNSStateTransactionFree(void *state, uint16_t id) {
|
||||
SCEnter();
|
||||
|
||||
DNSState *s = state;
|
||||
|
||||
s->transaction_done = id;
|
||||
SCLogDebug("state %p, id %"PRIu16, s, id);
|
||||
|
||||
/* we can't remove the actual transactions here */
|
||||
|
||||
SCReturn;
|
||||
}
|
||||
|
||||
|
||||
void RegisterDNSTCPParsers(void) {
|
||||
char *proto_name = "dnstcp";
|
||||
|
||||
/** DNS */
|
||||
AppLayerRegisterProto(proto_name, ALPROTO_DNS_TCP, STREAM_TOSERVER,
|
||||
DNSTCPRequestParse);
|
||||
AppLayerRegisterProto(proto_name, ALPROTO_DNS_TCP, STREAM_TOCLIENT,
|
||||
DNSTCPResponseParse);
|
||||
AppLayerRegisterStateFuncs(ALPROTO_DNS_TCP, DNSStateAlloc,
|
||||
DNSStateFree);
|
||||
AppLayerRegisterTransactionIdFuncs(ALPROTO_DNS_TCP,
|
||||
DNSStateUpdateTransactionId, DNSStateTransactionFree);
|
||||
|
||||
AppLayerRegisterGetTx(ALPROTO_DNS_TCP,
|
||||
DNSGetTx);
|
||||
AppLayerRegisterGetTxCnt(ALPROTO_DNS_TCP,
|
||||
DNSGetTxCnt);
|
||||
AppLayerRegisterGetAlstateProgressFunc(ALPROTO_DNS_TCP,
|
||||
DNSGetAlstateProgress);
|
||||
AppLayerRegisterGetAlstateProgressCompletionStatus(ALPROTO_DNS_TCP,
|
||||
DNSGetAlstateProgressCompletionStatus);
|
||||
|
||||
AppLayerRegisterProbingParser(&alp_proto_ctx,
|
||||
53,
|
||||
IPPROTO_TCP,
|
||||
proto_name,
|
||||
ALPROTO_DNS_TCP,
|
||||
0, sizeof(DNSTcpHeader),
|
||||
STREAM_TOSERVER,
|
||||
APP_LAYER_PROBING_PARSER_PRIORITY_HIGH, 1,
|
||||
DNSTcpProbingParser);
|
||||
|
||||
DNSAppLayerDecoderEventsRegister(ALPROTO_DNS_TCP);
|
||||
}
|
||||
|
||||
/* UNITTESTS */
|
||||
#ifdef UNITTESTS
|
||||
void DNSTCPParserRegisterTests(void) {
|
||||
// UtRegisterTest("DNSTCPParserTest01", DNSTCPParserTest01, 1);
|
||||
}
|
||||
#endif
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2013 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __APP_LAYER_DNS_TCP_H__
|
||||
#define __APP_LAYER_DNS_TCP_H__
|
||||
|
||||
#include "app-layer-protos.h"
|
||||
#include "app-layer-parser.h"
|
||||
#include "app-layer-dns-common.h"
|
||||
#include "flow.h"
|
||||
#include "queue.h"
|
||||
#include "util-byte.h"
|
||||
|
||||
void RegisterDNSTCPParsers(void);
|
||||
void DNSTCPParserTests(void);
|
||||
void DNSTCPParserRegisterTests(void);
|
||||
|
||||
#endif /* __APP_LAYER_DNS_TCP_H__ */
|
@ -0,0 +1,376 @@
|
||||
/* Copyright (C) 2013 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 "suricata.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "decode.h"
|
||||
|
||||
#include "flow-util.h"
|
||||
|
||||
#include "threads.h"
|
||||
|
||||
#include "util-print.h"
|
||||
#include "util-pool.h"
|
||||
#include "util-debug.h"
|
||||
|
||||
#include "stream-tcp-private.h"
|
||||
#include "stream-tcp-reassemble.h"
|
||||
#include "stream-tcp.h"
|
||||
#include "stream.h"
|
||||
|
||||
#include "app-layer-protos.h"
|
||||
#include "app-layer-parser.h"
|
||||
|
||||
#include "util-spm.h"
|
||||
#include "util-unittest.h"
|
||||
|
||||
#include "app-layer-dns-udp.h"
|
||||
|
||||
/** \internal
|
||||
* \brief Parse DNS request packet
|
||||
*/
|
||||
static int DNSUDPRequestParse(Flow *f, void *dstate,
|
||||
AppLayerParserState *pstate,
|
||||
uint8_t *input, uint32_t input_len,
|
||||
void *local_data, AppLayerParserResult *output)
|
||||
{
|
||||
DNSState *dns_state = (DNSState *)dstate;
|
||||
|
||||
SCLogDebug("starting %u", input_len);
|
||||
|
||||
/** \todo remove this when PP is fixed to enforce ipproto */
|
||||
if (f != NULL && f->proto != IPPROTO_UDP)
|
||||
SCReturnInt(-1);
|
||||
|
||||
if (input_len == 0 || input_len < sizeof(DNSHeader)) {
|
||||
SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSHeader));
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
DNSHeader *dns_header = (DNSHeader *)input;
|
||||
SCLogDebug("DNS %p", dns_header);
|
||||
|
||||
if (DNSValidateRequestHeader(f, dns_header) < 0)
|
||||
goto bad_data;
|
||||
|
||||
uint16_t q;
|
||||
const uint8_t *data = input + sizeof(DNSHeader);
|
||||
for (q = 0; q < ntohs(dns_header->questions); q++) {
|
||||
uint8_t fqdn[DNS_MAX_SIZE];
|
||||
uint16_t fqdn_offset = 0;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for len");
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("query length %u", *data);
|
||||
|
||||
while (*data != 0) {
|
||||
if (*data > 63) {
|
||||
/** \todo set event?*/
|
||||
goto insufficient_data;
|
||||
}
|
||||
uint8_t length = *data;
|
||||
|
||||
data++;
|
||||
|
||||
if (length > 0) {
|
||||
if (input + input_len < data + length) {
|
||||
SCLogDebug("input buffer too small for domain of len %u", length);
|
||||
goto insufficient_data;
|
||||
}
|
||||
//PrintRawDataFp(stdout, data, qry->length);
|
||||
|
||||
if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
|
||||
memcpy(fqdn + fqdn_offset, data, length);
|
||||
fqdn_offset += length;
|
||||
fqdn[fqdn_offset++] = '.';
|
||||
} else {
|
||||
/** \todo set event? */
|
||||
goto insufficient_data;
|
||||
}
|
||||
}
|
||||
|
||||
data += length;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for len(2)");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
SCLogDebug("qry length %u", *data);
|
||||
}
|
||||
if (fqdn_offset) {
|
||||
fqdn_offset--;
|
||||
}
|
||||
|
||||
data++;
|
||||
if (input + input_len < data + sizeof(DNSQueryTrailer)) {
|
||||
SCLogDebug("input buffer too small for DNSQueryTrailer");
|
||||
goto insufficient_data;
|
||||
}
|
||||
DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
|
||||
SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
|
||||
data += sizeof(DNSQueryTrailer);
|
||||
|
||||
/* store our data */
|
||||
if (dns_state != NULL) {
|
||||
DNSStoreQueryInState(dns_state, fqdn, fqdn_offset,
|
||||
ntohs(trailer->type), ntohs(trailer->class),
|
||||
ntohs(dns_header->tx_id));
|
||||
}
|
||||
}
|
||||
|
||||
SCReturnInt(1);
|
||||
bad_data:
|
||||
insufficient_data:
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
|
||||
/** \internal
|
||||
* \brief DNS UDP record parser, entry function
|
||||
*
|
||||
* Parses a DNS UDP record and fills the DNS state
|
||||
*
|
||||
*/
|
||||
static int DNSUDPResponseParse(Flow *f, void *dstate,
|
||||
AppLayerParserState *pstate,
|
||||
uint8_t *input, uint32_t input_len,
|
||||
void *local_data, AppLayerParserResult *output)
|
||||
{
|
||||
DNSState *dns_state = (DNSState *)dstate;
|
||||
|
||||
SCLogDebug("starting %u", input_len);
|
||||
|
||||
/** \todo remove this when PP is fixed to enforce ipproto */
|
||||
if (f != NULL && f->proto != IPPROTO_UDP)
|
||||
SCReturnInt(-1);
|
||||
|
||||
if (input_len == 0 || input_len < sizeof(DNSHeader)) {
|
||||
SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSHeader));
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
DNSHeader *dns_header = (DNSHeader *)input;
|
||||
SCLogDebug("DNS %p", dns_header);
|
||||
|
||||
DNSTransaction *tx = NULL;
|
||||
int found = 0;
|
||||
TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
|
||||
if (tx->tx_id == ntohs(dns_header->tx_id)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
|
||||
AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
|
||||
}
|
||||
|
||||
if (DNSValidateResponseHeader(f, dns_header) < 0)
|
||||
goto bad_data;
|
||||
|
||||
uint16_t q;
|
||||
const uint8_t *data = input + sizeof(DNSHeader);
|
||||
for (q = 0; q < ntohs(dns_header->questions); q++) {
|
||||
uint8_t fqdn[DNS_MAX_SIZE];
|
||||
uint16_t fqdn_offset = 0;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for len");
|
||||
goto insufficient_data;
|
||||
}
|
||||
SCLogDebug("qry length %u", *data);
|
||||
|
||||
while (*data != 0) {
|
||||
uint8_t length = *data;
|
||||
data++;
|
||||
|
||||
if (length > 0) {
|
||||
if (input + input_len < data + length) {
|
||||
SCLogDebug("input buffer too small for domain of len %u", length);
|
||||
goto insufficient_data;
|
||||
}
|
||||
//PrintRawDataFp(stdout, data, length);
|
||||
|
||||
if ((size_t)(fqdn_offset + length + 1) < sizeof(fqdn)) {
|
||||
memcpy(fqdn + fqdn_offset, data, length);
|
||||
fqdn_offset += length;
|
||||
fqdn[fqdn_offset++] = '.';
|
||||
}
|
||||
}
|
||||
|
||||
data += length;
|
||||
|
||||
if (input + input_len < data + 1) {
|
||||
SCLogDebug("input buffer too small for len");
|
||||
goto insufficient_data;
|
||||
}
|
||||
|
||||
length = *data;
|
||||
SCLogDebug("length %u", length);
|
||||
}
|
||||
if (fqdn_offset) {
|
||||
fqdn_offset--;
|
||||
}
|
||||
|
||||
data++;
|
||||
if (input + input_len < data + sizeof(DNSQueryTrailer)) {
|
||||
SCLogDebug("input buffer too small for DNSQueryTrailer");
|
||||
goto insufficient_data;
|
||||
}
|
||||
#if DEBUG
|
||||
DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
|
||||
SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
|
||||
#endif
|
||||
data += sizeof(DNSQueryTrailer);
|
||||
}
|
||||
|
||||
for (q = 0; q < ntohs(dns_header->answer_rr); q++) {
|
||||
data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_ANSWER,
|
||||
input, input_len, data);
|
||||
if (data == NULL) {
|
||||
goto insufficient_data;
|
||||
}
|
||||
}
|
||||
|
||||
for (q = 0; q < ntohs(dns_header->authority_rr); q++) {
|
||||
data = DNSReponseParse(dns_state, dns_header, q, DNS_LIST_AUTHORITY,
|
||||
input, input_len, data);
|
||||
if (data == NULL) {
|
||||
goto insufficient_data;
|
||||
}
|
||||
}
|
||||
|
||||
/* see if this is a "no such name" error */
|
||||
if (ntohs(dns_header->flags) & 0x0003) {
|
||||
SCLogDebug("no such name");
|
||||
|
||||
if (dns_state->curr != NULL) {
|
||||
dns_state->curr->no_such_name = 1;
|
||||
}
|
||||
}
|
||||
|
||||
SCReturnInt(1);
|
||||
|
||||
bad_data:
|
||||
insufficient_data:
|
||||
AppLayerDecoderEventsSetEvent(f, DNS_DECODER_EVENT_MALFORMED_DATA);
|
||||
SCReturnInt(-1);
|
||||
}
|
||||
|
||||
static uint16_t DNSUdpProbingParser(uint8_t *input, uint32_t ilen)
|
||||
{
|
||||
if (ilen == 0 || ilen < sizeof(DNSHeader)) {
|
||||
SCLogDebug("ilen too small, hoped for at least %"PRIuMAX, (uintmax_t)sizeof(DNSHeader));
|
||||
return ALPROTO_UNKNOWN;
|
||||
}
|
||||
|
||||
if (DNSUDPRequestParse(NULL, NULL, NULL, input, ilen, NULL, NULL) == -1)
|
||||
return ALPROTO_FAILED;
|
||||
|
||||
return ALPROTO_DNS_UDP;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Update the transaction id based on the dns state
|
||||
*/
|
||||
static void DNSStateUpdateTransactionId(void *state, uint16_t *id) {
|
||||
SCEnter();
|
||||
|
||||
DNSState *s = state;
|
||||
|
||||
SCLogDebug("original id %"PRIu16", s->transaction_cnt %"PRIu16,
|
||||
*id, (s->transaction_cnt));
|
||||
|
||||
if ((s->transaction_cnt) > (*id)) {
|
||||
SCLogDebug("original id %"PRIu16", updating with s->transaction_cnt %"PRIu16,
|
||||
*id, (s->transaction_cnt));
|
||||
|
||||
(*id) = (s->transaction_cnt);
|
||||
|
||||
SCLogDebug("updated id %"PRIu16, *id);
|
||||
}
|
||||
|
||||
SCReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief dns transaction cleanup callback
|
||||
*/
|
||||
static void DNSStateTransactionFree(void *state, uint16_t id) {
|
||||
SCEnter();
|
||||
|
||||
DNSState *s = state;
|
||||
|
||||
s->transaction_done = id;
|
||||
SCLogDebug("state %p, id %"PRIu16, s, id);
|
||||
|
||||
/* we can't remove the actual transactions here */
|
||||
|
||||
SCReturn;
|
||||
}
|
||||
|
||||
|
||||
void RegisterDNSUDPParsers(void) {
|
||||
char *proto_name = "dnsudp";
|
||||
|
||||
/** DNS */
|
||||
AppLayerRegisterProto(proto_name, ALPROTO_DNS_UDP, STREAM_TOSERVER,
|
||||
DNSUDPRequestParse);
|
||||
AppLayerRegisterProto(proto_name, ALPROTO_DNS_UDP, STREAM_TOCLIENT,
|
||||
DNSUDPResponseParse);
|
||||
AppLayerRegisterStateFuncs(ALPROTO_DNS_UDP, DNSStateAlloc,
|
||||
DNSStateFree);
|
||||
AppLayerRegisterTransactionIdFuncs(ALPROTO_DNS_UDP,
|
||||
DNSStateUpdateTransactionId, DNSStateTransactionFree);
|
||||
|
||||
AppLayerRegisterGetTx(ALPROTO_DNS_UDP,
|
||||
DNSGetTx);
|
||||
AppLayerRegisterGetTxCnt(ALPROTO_DNS_UDP,
|
||||
DNSGetTxCnt);
|
||||
AppLayerRegisterGetAlstateProgressFunc(ALPROTO_DNS_UDP,
|
||||
DNSGetAlstateProgress);
|
||||
AppLayerRegisterGetAlstateProgressCompletionStatus(ALPROTO_DNS_UDP,
|
||||
DNSGetAlstateProgressCompletionStatus);
|
||||
|
||||
AppLayerRegisterProbingParser(&alp_proto_ctx,
|
||||
53,
|
||||
IPPROTO_UDP,
|
||||
proto_name,
|
||||
ALPROTO_DNS_UDP,
|
||||
0, sizeof(DNSHeader),
|
||||
STREAM_TOSERVER,
|
||||
APP_LAYER_PROBING_PARSER_PRIORITY_HIGH, 1,
|
||||
DNSUdpProbingParser);
|
||||
|
||||
DNSAppLayerDecoderEventsRegister(ALPROTO_DNS_UDP);
|
||||
}
|
||||
|
||||
/* UNITTESTS */
|
||||
#ifdef UNITTESTS
|
||||
void DNSUDPParserRegisterTests(void) {
|
||||
// UtRegisterTest("DNSUDPParserTest01", DNSUDPParserTest01, 1);
|
||||
}
|
||||
#endif
|
@ -0,0 +1,37 @@
|
||||
/* Copyright (C) 2013 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>
|
||||
*/
|
||||
|
||||
#ifndef __APP_LAYER_DNS_UDP_H__
|
||||
#define __APP_LAYER_DNS_UDP_H__
|
||||
|
||||
#include "app-layer-protos.h"
|
||||
#include "app-layer-parser.h"
|
||||
#include "app-layer-dns-common.h"
|
||||
#include "flow.h"
|
||||
#include "queue.h"
|
||||
#include "util-byte.h"
|
||||
|
||||
void RegisterDNSUDPParsers(void);
|
||||
void DNSUDPParserTests(void);
|
||||
void DNSUDPParserRegisterTests(void);
|
||||
|
||||
#endif /* __APP_LAYER_DNS_UDP_H__ */
|
@ -0,0 +1,479 @@
|
||||
/* Copyright (C) 2007-2013 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>
|
||||
*
|
||||
* Implements dns logging portion of the engine.
|
||||
*/
|
||||
|
||||
#include "suricata-common.h"
|
||||
#include "debug.h"
|
||||
#include "detect.h"
|
||||
#include "pkt-var.h"
|
||||
#include "conf.h"
|
||||
|
||||
#include "threads.h"
|
||||
#include "threadvars.h"
|
||||
#include "tm-threads.h"
|
||||
|
||||
#include "util-print.h"
|
||||
#include "util-unittest.h"
|
||||
|
||||
#include "util-debug.h"
|
||||
|
||||
#include "output.h"
|
||||
#include "log-dnslog.h"
|
||||
#include "app-layer-dns-udp.h"
|
||||
#include "app-layer.h"
|
||||
#include "util-privs.h"
|
||||
#include "util-buffer.h"
|
||||
|
||||
#include "util-logopenfile.h"
|
||||
|
||||
#define DEFAULT_LOG_FILENAME "dns.log"
|
||||
|
||||
#define MODULE_NAME "LogDnsLog"
|
||||
|
||||
#define OUTPUT_BUFFER_SIZE 65535
|
||||
|
||||
/* we can do query logging as well, but it's disabled for now as the
|
||||
* TX id handling doesn't expect it */
|
||||
#define QUERY 0
|
||||
|
||||
TmEcode LogDnsLog (ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
|
||||
TmEcode LogDnsLogIPv4(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
|
||||
TmEcode LogDnsLogIPv6(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
|
||||
TmEcode LogDnsLogThreadInit(ThreadVars *, void *, void **);
|
||||
TmEcode LogDnsLogThreadDeinit(ThreadVars *, void *);
|
||||
void LogDnsLogExitPrintStats(ThreadVars *, void *);
|
||||
static void LogDnsLogDeInitCtx(OutputCtx *);
|
||||
|
||||
void TmModuleLogDnsLogRegister (void) {
|
||||
tmm_modules[TMM_LOGDNSLOG].name = MODULE_NAME;
|
||||
tmm_modules[TMM_LOGDNSLOG].ThreadInit = LogDnsLogThreadInit;
|
||||
tmm_modules[TMM_LOGDNSLOG].Func = LogDnsLog;
|
||||
tmm_modules[TMM_LOGDNSLOG].ThreadExitPrintStats = LogDnsLogExitPrintStats;
|
||||
tmm_modules[TMM_LOGDNSLOG].ThreadDeinit = LogDnsLogThreadDeinit;
|
||||
tmm_modules[TMM_LOGDNSLOG].RegisterTests = NULL;
|
||||
tmm_modules[TMM_LOGDNSLOG].cap_flags = 0;
|
||||
|
||||
OutputRegisterModule(MODULE_NAME, "dns-log", LogDnsLogInitCtx);
|
||||
|
||||
/* enable the logger for the app layer */
|
||||
AppLayerRegisterLogger(ALPROTO_DNS_UDP);
|
||||
AppLayerRegisterLogger(ALPROTO_DNS_TCP);
|
||||
SCLogInfo("registered %s", MODULE_NAME);
|
||||
}
|
||||
|
||||
typedef struct LogDnsFileCtx_ {
|
||||
LogFileCtx *file_ctx;
|
||||
uint32_t flags; /** Store mode */
|
||||
} LogDnsFileCtx;
|
||||
|
||||
typedef struct LogDnsLogThread_ {
|
||||
LogDnsFileCtx *dnslog_ctx;
|
||||
/** LogFileCtx has the pointer to the file and a mutex to allow multithreading */
|
||||
uint32_t dns_cnt;
|
||||
|
||||
MemBuffer *buffer;
|
||||
} LogDnsLogThread;
|
||||
|
||||
static void CreateTimeString (const struct timeval *ts, char *str, size_t size)
|
||||
{
|
||||
time_t time = ts->tv_sec;
|
||||
struct tm local_tm;
|
||||
struct tm *t = (struct tm *)SCLocalTime(time, &local_tm);
|
||||
|
||||
snprintf(str, size, "%02d/%02d/%02d-%02d:%02d:%02d.%06u",
|
||||
t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour,
|
||||
t->tm_min, t->tm_sec, (uint32_t) ts->tv_usec);
|
||||
}
|
||||
|
||||
static void CreateTypeString(uint16_t type, char *str, size_t str_size) {
|
||||
if (type == DNS_RECORD_TYPE_A) {
|
||||
snprintf(str, str_size, "A");
|
||||
} else if (type == DNS_RECORD_TYPE_NS) {
|
||||
snprintf(str, str_size, "NS");
|
||||
} else if (type == DNS_RECORD_TYPE_AAAA) {
|
||||
snprintf(str, str_size, "AAAA");
|
||||
} else if (type == DNS_RECORD_TYPE_TXT) {
|
||||
snprintf(str, str_size, "TXT");
|
||||
} else if (type == DNS_RECORD_TYPE_CNAME) {
|
||||
snprintf(str, str_size, "CNAME");
|
||||
} else if (type == DNS_RECORD_TYPE_SOA) {
|
||||
snprintf(str, str_size, "SOA");
|
||||
} else if (type == DNS_RECORD_TYPE_MX) {
|
||||
snprintf(str, str_size, "MX");
|
||||
} else if (type == DNS_RECORD_TYPE_PTR) {
|
||||
snprintf(str, str_size, "PTR");
|
||||
} else if (type == DNS_RECORD_TYPE_ANY) {
|
||||
snprintf(str, str_size, "ANY");
|
||||
} else if (type == DNS_RECORD_TYPE_TKEY) {
|
||||
snprintf(str, str_size, "TKEY");
|
||||
} else if (type == DNS_RECORD_TYPE_TSIG) {
|
||||
snprintf(str, str_size, "TSIG");
|
||||
} else {
|
||||
snprintf(str, str_size, "%04x/%u", type, type);
|
||||
}
|
||||
}
|
||||
|
||||
static void LogQuery(LogDnsLogThread *aft, char *timebuf, char *srcip, char *dstip, Port sp, Port dp, DNSTransaction *tx, DNSQueryEntry *entry) {
|
||||
LogDnsFileCtx *hlog = aft->dnslog_ctx;
|
||||
|
||||
SCLogDebug("got a DNS request and now logging !!");
|
||||
|
||||
/* reset */
|
||||
MemBufferReset(aft->buffer);
|
||||
|
||||
/* time & tx */
|
||||
MemBufferWriteString(aft->buffer,
|
||||
"%s [**] Query TX %04x [**] ", timebuf, tx->tx_id);
|
||||
|
||||
/* query */
|
||||
PrintRawUriBuf((char *)aft->buffer->buffer, &aft->buffer->offset, aft->buffer->size,
|
||||
(uint8_t *)((uint8_t *)entry + sizeof(DNSQueryEntry)),
|
||||
entry->len);
|
||||
|
||||
char record[16] = "";
|
||||
CreateTypeString(entry->type, record, sizeof(record));
|
||||
MemBufferWriteString(aft->buffer,
|
||||
" [**] %s [**] %s:%" PRIu16 " -> %s:%" PRIu16 "\n",
|
||||
record, srcip, sp, dstip, dp);
|
||||
|
||||
aft->dns_cnt++;
|
||||
|
||||
SCMutexLock(&hlog->file_ctx->fp_mutex);
|
||||
(void)MemBufferPrintToFPAsString(aft->buffer, hlog->file_ctx->fp);
|
||||
fflush(hlog->file_ctx->fp);
|
||||
SCMutexUnlock(&hlog->file_ctx->fp_mutex);
|
||||
}
|
||||
|
||||
static void LogAnswer(LogDnsLogThread *aft, char *timebuf, char *srcip, char *dstip, Port sp, Port dp, DNSTransaction *tx, DNSAnswerEntry *entry) {
|
||||
LogDnsFileCtx *hlog = aft->dnslog_ctx;
|
||||
|
||||
SCLogDebug("got a DNS response and now logging !!");
|
||||
|
||||
/* reset */
|
||||
MemBufferReset(aft->buffer);
|
||||
|
||||
/* time & tx*/
|
||||
MemBufferWriteString(aft->buffer,
|
||||
"%s [**] Response TX %04x [**] ", timebuf, tx->tx_id);
|
||||
|
||||
if (entry == NULL) {
|
||||
MemBufferWriteString(aft->buffer,
|
||||
"No Such Name");
|
||||
} else {
|
||||
/* query */
|
||||
if (entry->fqdn_len > 0) {
|
||||
PrintRawUriBuf((char *)aft->buffer->buffer, &aft->buffer->offset, aft->buffer->size,
|
||||
(uint8_t *)((uint8_t *)entry + sizeof(DNSAnswerEntry)),
|
||||
entry->fqdn_len);
|
||||
} else {
|
||||
MemBufferWriteString(aft->buffer, "<no data>");
|
||||
}
|
||||
|
||||
char record[16] = "";
|
||||
CreateTypeString(entry->type, record, sizeof(record));
|
||||
MemBufferWriteString(aft->buffer,
|
||||
" [**] %s [**] TTL %u [**] ", record, entry->ttl);
|
||||
|
||||
uint8_t *ptr = (uint8_t *)((uint8_t *)entry + sizeof(DNSAnswerEntry) + entry->fqdn_len);
|
||||
if (entry->type == DNS_RECORD_TYPE_A) {
|
||||
char a[16] = "";
|
||||
PrintInet(AF_INET, (const void *)ptr, a, sizeof(a));
|
||||
MemBufferWriteString(aft->buffer, "%s", a);
|
||||
} else if (entry->type == DNS_RECORD_TYPE_AAAA) {
|
||||
char a[46];
|
||||
PrintInet(AF_INET6, (const void *)ptr, a, sizeof(a));
|
||||
MemBufferWriteString(aft->buffer, "%s", a);
|
||||
} else if (entry->data_len == 0) {
|
||||
MemBufferWriteString(aft->buffer, "<no data>");
|
||||
} else {
|
||||
PrintRawUriBuf((char *)aft->buffer->buffer, &aft->buffer->offset,
|
||||
aft->buffer->size, ptr, entry->data_len);
|
||||
}
|
||||
}
|
||||
|
||||
/* ip/tcp header info */
|
||||
MemBufferWriteString(aft->buffer,
|
||||
" [**] %s:%" PRIu16 " -> %s:%" PRIu16 "\n",
|
||||
srcip, sp, dstip, dp);
|
||||
|
||||
aft->dns_cnt++;
|
||||
|
||||
SCMutexLock(&hlog->file_ctx->fp_mutex);
|
||||
(void)MemBufferPrintToFPAsString(aft->buffer, hlog->file_ctx->fp);
|
||||
fflush(hlog->file_ctx->fp);
|
||||
SCMutexUnlock(&hlog->file_ctx->fp_mutex);
|
||||
}
|
||||
|
||||
static TmEcode LogDnsLogIPWrapper(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq,
|
||||
PacketQueue *postpq, int ipproto)
|
||||
{
|
||||
SCEnter();
|
||||
|
||||
LogDnsLogThread *aft = (LogDnsLogThread *)data;
|
||||
char timebuf[64];
|
||||
|
||||
/* no flow, no htp state */
|
||||
if (p->flow == NULL) {
|
||||
SCLogDebug("no flow");
|
||||
SCReturnInt(TM_ECODE_OK);
|
||||
}
|
||||
|
||||
/* check if we have DNS state or not */
|
||||
FLOWLOCK_WRLOCK(p->flow); /* WRITE lock before we updated flow logged id */
|
||||
uint16_t proto = AppLayerGetProtoFromPacket(p);
|
||||
if (proto != ALPROTO_DNS_UDP && proto != ALPROTO_DNS_TCP) {
|
||||
SCLogDebug("proto not ALPROTO_DNS_UDP: %u", proto);
|
||||
goto end;
|
||||
}
|
||||
|
||||
DNSState *dns_state = (DNSState *)AppLayerGetProtoStateFromPacket(p);
|
||||
if (dns_state == NULL) {
|
||||
SCLogDebug("no dns state, so no request logging");
|
||||
goto end;
|
||||
}
|
||||
|
||||
uint64_t total_txs = AppLayerGetTxCnt(proto, dns_state);
|
||||
uint64_t tx_id = AppLayerTransactionGetLogId(p->flow);
|
||||
//int tx_progress_done_value_ts = AppLayerGetAlstateProgressCompletionStatus(proto, 0);
|
||||
//int tx_progress_done_value_tc = AppLayerGetAlstateProgressCompletionStatus(proto, 1);
|
||||
|
||||
SCLogDebug("pcap_cnt %"PRIu64, p->pcap_cnt);
|
||||
CreateTimeString(&p->ts, timebuf, sizeof(timebuf));
|
||||
|
||||
char srcip[46], dstip[46];
|
||||
Port sp, dp;
|
||||
if ((PKT_IS_TOCLIENT(p))) {
|
||||
switch (ipproto) {
|
||||
case AF_INET:
|
||||
PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), srcip, sizeof(srcip));
|
||||
PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), dstip, sizeof(dstip));
|
||||
break;
|
||||
case AF_INET6:
|
||||
PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), srcip, sizeof(srcip));
|
||||
PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), dstip, sizeof(dstip));
|
||||
break;
|
||||
default:
|
||||
goto end;
|
||||
}
|
||||
sp = p->sp;
|
||||
dp = p->dp;
|
||||
} else {
|
||||
switch (ipproto) {
|
||||
case AF_INET:
|
||||
PrintInet(AF_INET, (const void *)GET_IPV4_DST_ADDR_PTR(p), srcip, sizeof(srcip));
|
||||
PrintInet(AF_INET, (const void *)GET_IPV4_SRC_ADDR_PTR(p), dstip, sizeof(dstip));
|
||||
break;
|
||||
case AF_INET6:
|
||||
PrintInet(AF_INET6, (const void *)GET_IPV6_DST_ADDR(p), srcip, sizeof(srcip));
|
||||
PrintInet(AF_INET6, (const void *)GET_IPV6_SRC_ADDR(p), dstip, sizeof(dstip));
|
||||
break;
|
||||
default:
|
||||
goto end;
|
||||
}
|
||||
sp = p->dp;
|
||||
dp = p->sp;
|
||||
}
|
||||
#if QUERY
|
||||
if (PKT_IS_TOSERVER(p)) {
|
||||
DNSTransaction *tx = NULL;
|
||||
TAILQ_FOREACH(tx, &dns_state->tx_list, next) {
|
||||
DNSQueryEntry *entry = NULL;
|
||||
TAILQ_FOREACH(entry, &tx->query_list, next) {
|
||||
LogQuery(aft, timebuf, srcip, dstip, sp, dp, tx, entry);
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if ((PKT_IS_TOCLIENT(p))) {
|
||||
DNSTransaction *tx = NULL;
|
||||
for (; tx_id < total_txs; tx_id++)
|
||||
{
|
||||
tx = AppLayerGetTx(proto, dns_state, tx_id);
|
||||
if (tx == NULL)
|
||||
continue;
|
||||
|
||||
DNSQueryEntry *query = NULL;
|
||||
TAILQ_FOREACH(query, &tx->query_list, next) {
|
||||
LogQuery(aft, timebuf, dstip, srcip, dp, sp, tx, query);
|
||||
}
|
||||
|
||||
if (tx->no_such_name) {
|
||||
LogAnswer(aft, timebuf, srcip, dstip, sp, dp, tx, NULL);
|
||||
}
|
||||
|
||||
DNSAnswerEntry *entry = NULL;
|
||||
TAILQ_FOREACH(entry, &tx->answer_list, next) {
|
||||
LogAnswer(aft, timebuf, srcip, dstip, sp, dp, tx, entry);
|
||||
}
|
||||
|
||||
entry = NULL;
|
||||
TAILQ_FOREACH(entry, &tx->authority_list, next) {
|
||||
LogAnswer(aft, timebuf, srcip, dstip, sp, dp, tx, entry);
|
||||
}
|
||||
|
||||
SCLogDebug("calling AppLayerTransactionUpdateLoggedId");
|
||||
AppLayerTransactionUpdateLogId(p->flow);
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
FLOWLOCK_UNLOCK(p->flow);
|
||||
SCReturnInt(TM_ECODE_OK);
|
||||
}
|
||||
|
||||
TmEcode LogDnsLogIPv4(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
|
||||
{
|
||||
return LogDnsLogIPWrapper(tv, p, data, pq, postpq, AF_INET);
|
||||
}
|
||||
|
||||
TmEcode LogDnsLogIPv6(ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
|
||||
{
|
||||
return LogDnsLogIPWrapper(tv, p, data, pq, postpq, AF_INET6);
|
||||
}
|
||||
|
||||
TmEcode LogDnsLog (ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
|
||||
{
|
||||
SCEnter();
|
||||
|
||||
SCLogDebug("pcap_cnt %"PRIu64, p->pcap_cnt);
|
||||
/* no flow, no htp state */
|
||||
if (p->flow == NULL) {
|
||||
SCReturnInt(TM_ECODE_OK);
|
||||
}
|
||||
|
||||
if (!(PKT_IS_UDP(p)) && !(PKT_IS_TCP(p))) {
|
||||
SCReturnInt(TM_ECODE_OK);
|
||||
}
|
||||
|
||||
if (PKT_IS_IPV4(p)) {
|
||||
int r = LogDnsLogIPv4(tv, p, data, pq, postpq);
|
||||
SCReturnInt(r);
|
||||
} else if (PKT_IS_IPV6(p)) {
|
||||
int r = LogDnsLogIPv6(tv, p, data, pq, postpq);
|
||||
SCReturnInt(r);
|
||||
}
|
||||
|
||||
SCReturnInt(TM_ECODE_OK);
|
||||
}
|
||||
|
||||
TmEcode LogDnsLogThreadInit(ThreadVars *t, void *initdata, void **data)
|
||||
{
|
||||
LogDnsLogThread *aft = SCMalloc(sizeof(LogDnsLogThread));
|
||||
if (unlikely(aft == NULL))
|
||||
return TM_ECODE_FAILED;
|
||||
memset(aft, 0, sizeof(LogDnsLogThread));
|
||||
|
||||
if(initdata == NULL)
|
||||
{
|
||||
SCLogDebug("Error getting context for DNSLog. \"initdata\" argument NULL");
|
||||
SCFree(aft);
|
||||
return TM_ECODE_FAILED;
|
||||
}
|
||||
|
||||
aft->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
|
||||
if (aft->buffer == NULL) {
|
||||
SCFree(aft);
|
||||
return TM_ECODE_FAILED;
|
||||
}
|
||||
|
||||
/* Use the Ouptut Context (file pointer and mutex) */
|
||||
aft->dnslog_ctx= ((OutputCtx *)initdata)->data;
|
||||
|
||||
*data = (void *)aft;
|
||||
return TM_ECODE_OK;
|
||||
}
|
||||
|
||||
TmEcode LogDnsLogThreadDeinit(ThreadVars *t, void *data)
|
||||
{
|
||||
LogDnsLogThread *aft = (LogDnsLogThread *)data;
|
||||
if (aft == NULL) {
|
||||
return TM_ECODE_OK;
|
||||
}
|
||||
|
||||
MemBufferFree(aft->buffer);
|
||||
/* clear memory */
|
||||
memset(aft, 0, sizeof(LogDnsLogThread));
|
||||
|
||||
SCFree(aft);
|
||||
return TM_ECODE_OK;
|
||||
}
|
||||
|
||||
void LogDnsLogExitPrintStats(ThreadVars *tv, void *data) {
|
||||
LogDnsLogThread *aft = (LogDnsLogThread *)data;
|
||||
if (aft == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
SCLogInfo("DNS logger logged %" PRIu32 " requests", aft->dns_cnt);
|
||||
}
|
||||
|
||||
/** \brief Create a new dns log LogFileCtx.
|
||||
* \param conf Pointer to ConfNode containing this loggers configuration.
|
||||
* \return NULL if failure, LogFileCtx* to the file_ctx if succesful
|
||||
* */
|
||||
OutputCtx *LogDnsLogInitCtx(ConfNode *conf)
|
||||
{
|
||||
LogFileCtx* file_ctx = LogFileNewCtx();
|
||||
|
||||
if(file_ctx == NULL) {
|
||||
SCLogError(SC_ERR_DNS_LOG_GENERIC, "couldn't create new file_ctx");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME) < 0) {
|
||||
LogFileFreeCtx(file_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LogDnsFileCtx *dnslog_ctx = SCMalloc(sizeof(LogDnsFileCtx));
|
||||
if (unlikely(dnslog_ctx == NULL)) {
|
||||
LogFileFreeCtx(file_ctx);
|
||||
return NULL;
|
||||
}
|
||||
memset(dnslog_ctx, 0x00, sizeof(LogDnsFileCtx));
|
||||
|
||||
dnslog_ctx->file_ctx = file_ctx;
|
||||
|
||||
OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
|
||||
if (unlikely(output_ctx == NULL)) {
|
||||
LogFileFreeCtx(file_ctx);
|
||||
SCFree(dnslog_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
output_ctx->data = dnslog_ctx;
|
||||
output_ctx->DeInit = LogDnsLogDeInitCtx;
|
||||
|
||||
SCLogDebug("DNS log output initialized");
|
||||
|
||||
return output_ctx;
|
||||
}
|
||||
|
||||
static void LogDnsLogDeInitCtx(OutputCtx *output_ctx)
|
||||
{
|
||||
LogDnsFileCtx *dnslog_ctx = (LogDnsFileCtx *)output_ctx->data;
|
||||
LogFileFreeCtx(dnslog_ctx->file_ctx);
|
||||
SCFree(dnslog_ctx);
|
||||
SCFree(output_ctx);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/* Copyright (C) 2007-2013 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>
|
||||
*/
|
||||
|
||||
#ifndef __LOG_DNSLOG_H__
|
||||
#define __LOG_DNSLOG_H__
|
||||
|
||||
void TmModuleLogDnsLogRegister (void);
|
||||
void TmModuleLogDnsLogIPv4Register (void);
|
||||
void TmModuleLogDnsLogIPv6Register (void);
|
||||
OutputCtx *LogDnsLogInitCtx(ConfNode *);
|
||||
|
||||
#endif /* __LOG_DNSLOG_H__ */
|
Loading…
Reference in New Issue