DNS TCP and UDP parser and DNS response logger

pull/412/merge
Victor Julien 12 years ago
parent 4521de2dfd
commit 8e01cba85d

@ -17,6 +17,9 @@ app-layer.c app-layer.h \
app-layer-dcerpc.c app-layer-dcerpc.h \
app-layer-dcerpc-udp.c app-layer-dcerpc-udp.h \
app-layer-detect-proto.c app-layer-detect-proto.h \
app-layer-dns-common.c app-layer-dns-common.h \
app-layer-dns-tcp.c app-layer-dns-tcp.h \
app-layer-dns-udp.c app-layer-dns-udp.h \
app-layer-ftp.c app-layer-ftp.h \
app-layer-htp-body.c app-layer-htp-body.h \
app-layer-htp.c app-layer-htp.h \
@ -193,6 +196,7 @@ flow-var.c flow-var.h \
host.c host.h \
host-queue.c host-queue.h \
host-timeout.c host-timeout.h \
log-dnslog.c log-dnslog.h \
log-droplog.c log-droplog.h \
log-file.c log-file.h \
log-filestore.c log-filestore.h \

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

@ -52,6 +52,8 @@
#include "app-layer-ssl.h"
#include "app-layer-ssh.h"
#include "app-layer-smtp.h"
#include "app-layer-dns-udp.h"
#include "app-layer-dns-tcp.h"
#include "util-spm.h"
@ -1256,6 +1258,8 @@ void RegisterAppLayerParsers(void)
RegisterFTPParsers();
RegisterSSHParsers();
RegisterSMTPParsers();
RegisterDNSUDPParsers();
RegisterDNSTCPParsers();
/** IMAP */
//AlpProtoAdd(&alp_proto_ctx, IPPROTO_TCP, ALPROTO_IMAP, "|2A 20|OK|20|", 5, 0, STREAM_TOCLIENT);

@ -49,6 +49,9 @@ const char *TmModuleAlprotoToString(int proto)
CASE_CODE (ALPROTO_DCERPC);
CASE_CODE (ALPROTO_DCERPC_UDP);
CASE_CODE (ALPROTO_DNS_UDP);
CASE_CODE (ALPROTO_DNS_TCP);
default:
return "ALPROTO_UNDEFINED";
}

@ -39,6 +39,8 @@ enum {
ALPROTO_DCERPC,
ALPROTO_DCERPC_UDP,
ALPROTO_IRC,
ALPROTO_DNS_UDP,
ALPROTO_DNS_TCP,
/* used by the probing parser when alproto detection fails
* permanently for that particular stream */
ALPROTO_FAILED,

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

@ -103,6 +103,7 @@
#include "log-droplog.h"
#include "log-httplog.h"
#include "log-dnslog.h"
#include "log-tlslog.h"
#include "log-pcap.h"
#include "log-file.h"
@ -1593,6 +1594,13 @@ int main(int argc, char **argv)
/* file log */
TmModuleLogFileLogRegister();
TmModuleLogFilestoreRegister();
/* dns log */
TmModuleLogDnsLogRegister();
/* cuda */
#ifdef __SC_CUDA_SUPPORT__
TmModuleCudaMpmB2gRegister();
TmModuleCudaPacketBatcherRegister();
#endif
TmModuleDebugList();
AppLayerHtpNeedFileInspection();

@ -98,6 +98,9 @@ int TmModuleGetIDForTM(TmModule *tm)
for (i = 0; i < TMM_SIZE; i++) {
t = &tmm_modules[i];
if (t->name == NULL)
continue;
if (strcmp(t->name, tm->name) == 0)
return i;
}
@ -242,6 +245,7 @@ const char * TmModuleTmmIdToString(TmmId id)
CASE_CODE (TMM_ALERTSYSLOG4);
CASE_CODE (TMM_ALERTSYSLOG6);
CASE_CODE (TMM_RESPONDREJECT);
CASE_CODE (TMM_LOGDNSLOG);
CASE_CODE (TMM_LOGHTTPLOG);
CASE_CODE (TMM_LOGHTTPLOG4);
CASE_CODE (TMM_LOGHTTPLOG6);

@ -52,6 +52,7 @@ typedef enum {
TMM_ALERTSYSLOG4,
TMM_ALERTSYSLOG6,
TMM_RESPONDREJECT,
TMM_LOGDNSLOG,
TMM_LOGHTTPLOG,
TMM_LOGHTTPLOG4,
TMM_LOGHTTPLOG6,

@ -272,6 +272,7 @@ const char * SCErrorToString(SCError err)
CASE_CODE (SC_ERR_MAGIC_OPEN);
CASE_CODE (SC_ERR_MAGIC_LOAD);
CASE_CODE (SC_ERR_CUDA_BUFFER_ERROR);
CASE_CODE (SC_ERR_DNS_LOG_GENERIC);
}
return "UNKNOWN_ERROR";

@ -261,6 +261,7 @@ typedef enum {
SC_ERR_LIVE_RULE_SWAP,
SC_WARN_UNCOMMON,
SC_ERR_CUDA_BUFFER_ERROR,
SC_ERR_DNS_LOG_GENERIC,
} SCError;
const char *SCErrorToString(SCError);

@ -104,6 +104,13 @@ outputs:
#extended: yes # Log extended information like fingerprint
certs-log-dir: certs # directory to store the certificates files
# a line based log of DNS requests and/or replies (no alerts)
- dns-log:
enabled: yes
filename: dns.log
append: yes
#filetype: regular # 'regular', 'unix_stream' or 'unix_dgram'
# a line based log to used with pcap file study.
# this module is dedicated to offline pcap parsing (empty output
# if used with another kind of input). It can interoperate with

Loading…
Cancel
Save