mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
901 lines
31 KiB
C
901 lines
31 KiB
C
/* 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"
|
|
|
|
#ifdef HAVE_RUST
|
|
#include "app-layer-dns-tcp-rust.h"
|
|
#endif
|
|
|
|
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;
|
|
|
|
static uint16_t DNSTcpProbingParser(uint8_t *input, uint32_t ilen,
|
|
uint32_t *offset);
|
|
|
|
/** \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;
|
|
}
|
|
#ifdef DEBUG
|
|
DNSQueryTrailer *trailer = (DNSQueryTrailer *)data;
|
|
SCLogDebug("trailer type %04x class %04x", ntohs(trailer->type), ntohs(trailer->class));
|
|
#endif
|
|
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) {
|
|
if (DNSCheckMemcap(0xffff, dns_state) < 0)
|
|
return -1;
|
|
|
|
/** \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;
|
|
}
|
|
DNSIncrMemcap(0xffff, dns_state);
|
|
}
|
|
|
|
if ((uint32_t)len + (uint32_t)dns_state->offset > (uint32_t)dns_state->record_len) {
|
|
SCLogDebug("oh my, we have more data than the max record size. What do we do. WHAT DO WE DOOOOO!");
|
|
#ifdef DEBUG
|
|
BUG_ON(1);
|
|
#endif
|
|
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(dns_state, 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));
|
|
|
|
if (dns_state != NULL) {
|
|
if (timercmp(&dns_state->last_req, &dns_state->last_resp, >=)) {
|
|
if (dns_state->window <= dns_state->unreplied_cnt) {
|
|
dns_state->window++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
DNSState *dns_state = (DNSState *)dstate;
|
|
SCLogDebug("starting %u", input_len);
|
|
|
|
if (input == NULL && input_len > 0) {
|
|
SCLogDebug("Input is NULL, but len is %"PRIu32": must be a gap.",
|
|
input_len);
|
|
dns_state->gap_ts = 1;
|
|
SCReturnInt(1);
|
|
}
|
|
|
|
if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
|
|
SCReturnInt(1);
|
|
}
|
|
|
|
/** \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 == NULL || input_len == 0) {
|
|
goto insufficient_data;
|
|
}
|
|
|
|
/* Clear gap state. */
|
|
if (dns_state->gap_ts) {
|
|
if (DNSTcpProbingParser(input, input_len, NULL) == ALPROTO_DNS) {
|
|
SCLogDebug("New data probed as DNS, clearing gap state.");
|
|
BufferReset(dns_state);
|
|
dns_state->gap_ts = 0;
|
|
} else {
|
|
SCLogDebug("Unable to sync DNS parser, leaving gap state.");
|
|
SCReturnInt(1);
|
|
}
|
|
}
|
|
|
|
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 += (2 + ntohs(dns_tcp_header->len));
|
|
input_len -= (2 + 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;
|
|
}
|
|
|
|
if (f != NULL) {
|
|
dns_state->last_req = f->lastts;
|
|
}
|
|
|
|
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(dns_state, dns_header) < 0)
|
|
goto bad_data;
|
|
|
|
DNSTransaction *tx = NULL;
|
|
int found = 0;
|
|
if ((tx = DNSTransactionFindByTxId(dns_state, ntohs(dns_header->tx_id))) != NULL)
|
|
found = 1;
|
|
|
|
if (!found) {
|
|
SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
|
|
DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
|
|
} else if (dns_state->unreplied_cnt > 0) {
|
|
dns_state->unreplied_cnt--;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
SCLogDebug("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;
|
|
}
|
|
#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;
|
|
}
|
|
}
|
|
|
|
/* parse rcode, e.g. "noerror" or "nxdomain" */
|
|
uint8_t rcode = ntohs(dns_header->flags) & 0x0F;
|
|
if (rcode <= DNS_RCODE_NOTZONE) {
|
|
SCLogDebug("rcode %u", rcode);
|
|
if (tx != NULL)
|
|
tx->rcode = rcode;
|
|
} else {
|
|
/* this is not invalid, rcodes can be user defined */
|
|
SCLogDebug("unexpected DNS rcode %u", rcode);
|
|
}
|
|
|
|
if (ntohs(dns_header->flags) & 0x0080) {
|
|
SCLogDebug("recursion desired");
|
|
if (tx != NULL)
|
|
tx->recursion_desired = 1;
|
|
}
|
|
|
|
if (tx != NULL) {
|
|
tx->replied = 1;
|
|
}
|
|
|
|
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)
|
|
{
|
|
DNSState *dns_state = (DNSState *)dstate;
|
|
|
|
if (input == NULL && input_len > 0) {
|
|
SCLogDebug("Input is NULL, but len is %"PRIu32": must be a gap.",
|
|
input_len);
|
|
dns_state->gap_tc = 1;
|
|
SCReturnInt(1);
|
|
}
|
|
|
|
if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) {
|
|
SCReturnInt(1);
|
|
}
|
|
|
|
/** \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 == NULL || input_len == 0) {
|
|
goto insufficient_data;
|
|
}
|
|
|
|
/* Clear gap state. */
|
|
if (dns_state->gap_tc) {
|
|
if (DNSTcpProbingParser(input, input_len, NULL) == ALPROTO_DNS) {
|
|
SCLogDebug("New data probed as DNS, clearing gap state.");
|
|
BufferReset(dns_state);
|
|
dns_state->gap_tc = 0;
|
|
} else {
|
|
SCLogDebug("Unable to sync DNS parser, leaving gap state.");
|
|
SCReturnInt(1);
|
|
}
|
|
}
|
|
|
|
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) == 0) {
|
|
goto bad_data;
|
|
} else 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 += (2 + ntohs(dns_tcp_header->len));
|
|
input_len -= (2 + 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;
|
|
}
|
|
|
|
if (f != NULL) {
|
|
dns_state->last_resp = f->lastts;
|
|
}
|
|
|
|
SCReturnInt(1);
|
|
insufficient_data:
|
|
SCReturnInt(-1);
|
|
bad_data:
|
|
SCReturnInt(-1);
|
|
}
|
|
|
|
static uint16_t DNSTcpProbingParser(uint8_t *input, uint32_t ilen, uint32_t *offset)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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");
|
|
return ALPROTO_DNS;
|
|
}
|
|
|
|
/**
|
|
* \brief Probing parser for TCP DNS responses.
|
|
*
|
|
* This is a minimal parser that just checks that the input contains enough
|
|
* data for a TCP DNS response.
|
|
*/
|
|
static uint16_t DNSTcpProbeResponse(uint8_t *input, uint32_t len,
|
|
uint32_t *offset)
|
|
{
|
|
if (len == 0 || len < sizeof(DNSTcpHeader)) {
|
|
return ALPROTO_UNKNOWN;
|
|
}
|
|
|
|
DNSTcpHeader *dns_header = (DNSTcpHeader *)input;
|
|
|
|
if (ntohs(dns_header->len) < sizeof(DNSHeader)) {
|
|
return ALPROTO_FAILED;
|
|
}
|
|
|
|
return ALPROTO_DNS;
|
|
}
|
|
|
|
void RegisterDNSTCPParsers(void)
|
|
{
|
|
const char *proto_name = "dns";
|
|
#ifdef HAVE_RUST
|
|
RegisterRustDNSTCPParsers();
|
|
return;
|
|
#endif
|
|
/** DNS */
|
|
if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
|
|
AppLayerProtoDetectRegisterProtocol(ALPROTO_DNS, proto_name);
|
|
|
|
if (RunmodeIsUnittests()) {
|
|
AppLayerProtoDetectPPRegister(IPPROTO_TCP,
|
|
"53",
|
|
ALPROTO_DNS,
|
|
0, sizeof(DNSTcpHeader),
|
|
STREAM_TOSERVER,
|
|
DNSTcpProbingParser, NULL);
|
|
} else {
|
|
int have_cfg = AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
|
|
proto_name, ALPROTO_DNS,
|
|
0, sizeof(DNSTcpHeader),
|
|
DNSTcpProbingParser,
|
|
DNSTcpProbeResponse);
|
|
/* if we have no config, we enable the default port 53 */
|
|
if (!have_cfg) {
|
|
SCLogWarning(SC_ERR_DNS_CONFIG, "no DNS TCP config found, "
|
|
"enabling DNS detection on "
|
|
"port 53.");
|
|
AppLayerProtoDetectPPRegister(IPPROTO_TCP, "53",
|
|
ALPROTO_DNS, 0, sizeof(DNSTcpHeader),
|
|
STREAM_TOSERVER, DNSTcpProbingParser,
|
|
DNSTcpProbeResponse);
|
|
}
|
|
}
|
|
} else {
|
|
SCLogInfo("Protocol detection and parser disabled for %s protocol.",
|
|
proto_name);
|
|
return;
|
|
}
|
|
|
|
if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
|
|
AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_DNS, STREAM_TOSERVER,
|
|
DNSTCPRequestParse);
|
|
AppLayerParserRegisterParser(IPPROTO_TCP , ALPROTO_DNS, STREAM_TOCLIENT,
|
|
DNSTCPResponseParse);
|
|
AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_DNS, DNSStateAlloc,
|
|
DNSStateFree);
|
|
AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_DNS,
|
|
DNSStateTransactionFree);
|
|
|
|
AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_DNS, DNSGetEvents);
|
|
AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_DNS, DNSHasEvents);
|
|
AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_DNS,
|
|
DNSStateHasTxDetectState,
|
|
DNSGetTxDetectState, DNSSetTxDetectState);
|
|
|
|
AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_DNS, DNSGetTx);
|
|
AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_DNS, DNSGetTxCnt);
|
|
AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_DNS, DNSGetTxLogged,
|
|
DNSSetTxLogged);
|
|
AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_DNS,
|
|
DNSGetAlstateProgress);
|
|
AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_DNS,
|
|
DNSGetAlstateProgressCompletionStatus);
|
|
DNSAppLayerRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_DNS);
|
|
|
|
/* This parser accepts gaps. */
|
|
AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_DNS,
|
|
APP_LAYER_PARSER_OPT_ACCEPT_GAPS);
|
|
|
|
} else {
|
|
SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
|
|
"still on.", proto_name);
|
|
}
|
|
|
|
#ifdef UNITTESTS
|
|
AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_DNS,
|
|
DNSTCPParserRegisterTests);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
/* UNITTESTS */
|
|
#ifdef UNITTESTS
|
|
|
|
#include "util-unittest-helper.h"
|
|
|
|
static int DNSTCPParserTestMultiRecord(void)
|
|
{
|
|
/* This is a buffer containing 20 DNS requests each prefixed by
|
|
* the request length for transport over TCP. It was generated with Scapy,
|
|
* where each request is:
|
|
* DNS(id=i, rd=1, qd=DNSQR(qname="%d.google.com" % i, qtype="A"))
|
|
* where i is 0 to 19.
|
|
*/
|
|
uint8_t req[] = {
|
|
0x00, 0x1e, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x30,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x31,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x02, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x32,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x03, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x04, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x34,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x05, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x35,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x06, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x36,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x07, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x37,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x08, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1e, 0x00, 0x09, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x39,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1f, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x31,
|
|
0x30, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
|
0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00,
|
|
0x01, 0x00, 0x1f, 0x00, 0x0b, 0x01, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
|
0x31, 0x31, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
|
0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01,
|
|
0x00, 0x01, 0x00, 0x1f, 0x00, 0x0c, 0x01, 0x00,
|
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x02, 0x31, 0x32, 0x06, 0x67, 0x6f, 0x6f, 0x67,
|
|
0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00,
|
|
0x01, 0x00, 0x01, 0x00, 0x1f, 0x00, 0x0d, 0x01,
|
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x02, 0x31, 0x33, 0x06, 0x67, 0x6f, 0x6f,
|
|
0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00,
|
|
0x00, 0x01, 0x00, 0x01, 0x00, 0x1f, 0x00, 0x0e,
|
|
0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x02, 0x31, 0x34, 0x06, 0x67, 0x6f,
|
|
0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d,
|
|
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x1f, 0x00,
|
|
0x0f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x02, 0x31, 0x35, 0x06, 0x67,
|
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f,
|
|
0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x1f,
|
|
0x00, 0x10, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x02, 0x31, 0x36, 0x06,
|
|
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63,
|
|
0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
|
|
0x1f, 0x00, 0x11, 0x01, 0x00, 0x00, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x31, 0x37,
|
|
0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
|
|
0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
|
|
0x00, 0x1f, 0x00, 0x12, 0x01, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x31,
|
|
0x38, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
|
0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00,
|
|
0x01, 0x00, 0x1f, 0x00, 0x13, 0x01, 0x00, 0x00,
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
|
0x31, 0x39, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
|
0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01,
|
|
0x00, 0x01
|
|
};
|
|
size_t reqlen = sizeof(req);
|
|
|
|
DNSState *state = DNSStateAlloc();
|
|
FAIL_IF_NULL(state);
|
|
Flow *f = UTHBuildFlow(AF_INET, "1.2.3.4", "1.2.3.5", 1024, 53);
|
|
FAIL_IF_NULL(f);
|
|
f->proto = IPPROTO_TCP;
|
|
f->alproto = ALPROTO_DNS;
|
|
f->alstate = state;
|
|
|
|
FAIL_IF_NOT(DNSTCPRequestParse(f, f->alstate, NULL, req, reqlen, NULL));
|
|
FAIL_IF(state->transaction_max != 20);
|
|
|
|
UTHFreeFlow(f);
|
|
PASS;
|
|
}
|
|
|
|
void DNSTCPParserRegisterTests(void)
|
|
{
|
|
UtRegisterTest("DNSTCPParserTestMultiRecord", DNSTCPParserTestMultiRecord);
|
|
}
|
|
|
|
#endif /* UNITTESTS */
|