In the case where DNS requests are sent over the same flow w/o a
reply being received, we now set an event in the flow and refuse
to add more transactions to the state. This protects the DNS
handling from getting overloaded slowing down everything.
A new option to configure this behaviour was added:
app-layer:
protocols:
dnsudp:
enabled: yes
detection-ports:
udp:
toserver: 53
request-flood: 750
The request-flood parameter can be 0 (disabling this feature) or a
positive integer. It defaults to 500.
This means that if 500 unreplied requests are seen in a row an event
is set. Rule
2240007 was added to dns-events.rules to match on this.
alert dns any any -> any any (msg:"SURICATA DNS Not a response"; flow:to_client; app-layer-event:dns.not_a_response; sid:2240005; rev:1;)
# Z flag (reserved) not 0
alert dns any any -> any any (msg:"SURICATA DNS Z flag set"; app-layer-event:dns.z_flag_set; sid:2240006; rev:1;)
+# Request Flood Detected
+alert dns any any -> any any (msg:"SURICATA DNS request flood detected"; flow:to_server; app-layer-event:dns.flooded; sid:2240007; rev:1;)
#include "util-print.h"
#endif
+typedef struct DNSConfig_ {
+ uint32_t request_flood;
+} DNSConfig;
+static DNSConfig dns_config;
+
+void DNSConfigInit(void) {
+ memset(&dns_config, 0x00, sizeof(dns_config));
+}
+
+void DNSConfigSetRequestFlood(uint32_t value) {
+ dns_config.request_flood = value;
+}
+
SCEnumCharMap dns_decoder_event_table[ ] = {
{ "UNSOLLICITED_RESPONSE", DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE, },
{ "MALFORMED_DATA", DNS_DECODER_EVENT_MALFORMED_DATA, },
{ "NOT_A_REQUEST", DNS_DECODER_EVENT_NOT_A_REQUEST, },
{ "NOT_A_RESPONSE", DNS_DECODER_EVENT_NOT_A_RESPONSE, },
{ "Z_FLAG_SET", DNS_DECODER_EVENT_Z_FLAG_SET, },
+ { "FLOODED", DNS_DECODER_EVENT_FLOODED, },
{ NULL, -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)
{
- if (dns_state->curr != NULL && dns_state->curr->replied == 0)
+ /* flood protection */
+ if (dns_state->givenup)
+ return;
+
+ if (dns_state->curr != NULL && dns_state->curr->replied == 0) {
dns_state->curr->reply_lost = 1;
+ dns_state->unreplied_cnt++;
+
+ /* check flood limit */
+ if (dns_config.request_flood != 0 &&
+ dns_state->unreplied_cnt > dns_config.request_flood) {
+ DNSSetEvent(dns_state, DNS_DECODER_EVENT_FLOODED);
+ dns_state->givenup = 1;
+ }
+ }
DNSTransaction *tx = DNSTransactionFindByTxId(dns_state, tx_id);
if (tx == NULL) {
TAILQ_INSERT_TAIL(&dns_state->tx_list, tx, next);
dns_state->curr = tx;
tx->tx_num = dns_state->transaction_max;
-
}
DNSAnswerEntry *q = SCMalloc(sizeof(DNSAnswerEntry) + fqdn_len + data_len);
/* mark tx is as replied so we can log it */
tx->replied = 1;
+
+ /* reset unreplied counter */
+ dns_state->unreplied_cnt = 0;
}
/** \internal
DNS_DECODER_EVENT_NOT_A_REQUEST,
DNS_DECODER_EVENT_NOT_A_RESPONSE,
DNS_DECODER_EVENT_Z_FLAG_SET,
+ DNS_DECODER_EVENT_FLOODED,
};
/** \brief DNS packet header */
TAILQ_HEAD(, DNSTransaction_) tx_list; /**< transaction list */
DNSTransaction *curr; /**< ptr to current tx */
uint64_t transaction_max;
+ uint32_t unreplied_cnt; /**< number of unreplied requests in a row */
uint16_t events;
+ uint16_t givenup;
/* used by TCP only */
uint16_t offset;
uint8_t *buffer;
} DNSState;
+#define DNS_CONFIG_DEFAULT_REQUEST_FLOOD 500
+
+void DNSConfigInit(void);
+void DNSConfigSetRequestFlood(uint32_t value);
+
void RegisterDNSParsers(void);
void DNSParserTests(void);
void DNSParserRegisterTests(void);
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 ((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);
}
uint16_t q;
}
}
- if (!found) {
- SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
- DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
- }
-
SCReturnInt(1);
bad_data:
insufficient_data:
#include "suricata-common.h"
#include "suricata.h"
+#include "conf.h"
+#include "util-misc.h"
+
#include "debug.h"
#include "decode.h"
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 ((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);
}
+
if (DNSValidateResponseHeader(dns_state, dns_header) < 0)
goto bad_data;
if (ntohs(dns_header->flags) & 0x0003) {
SCLogDebug("no such name");
- if (dns_state->curr != NULL) {
- dns_state->curr->no_such_name = 1;
+ if (tx != NULL) {
+ tx->no_such_name = 1;
}
}
- if (!found) {
- SCLogDebug("DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE");
- DNSSetEvent(dns_state, DNS_DECODER_EVENT_UNSOLLICITED_RESPONSE);
- }
-
- SCReturnInt(1);
+ SCReturnInt(1);
bad_data:
insufficient_data:
return ALPROTO_DNS_UDP;
}
+static void DNSUDPConfigure(void) {
+ uint32_t request_flood = DNS_CONFIG_DEFAULT_REQUEST_FLOOD;
+
+ ConfNode *p = ConfGetNode("app-layer.protocols.dnsudp.request-flood");
+ if (p != NULL) {
+ uint32_t value;
+ if (ParseSizeStringU32(p->val, &value) < 0) {
+ SCLogError(SC_ERR_DNS_CONFIG, "invalid value for request-flood %s", p->val);
+ } else {
+ request_flood = value;
+ }
+ }
+ SCLogInfo("DNS request flood protection level: %u", request_flood);
+ DNSConfigSetRequestFlood(request_flood);
+}
void RegisterDNSUDPParsers(void) {
char *proto_name = "dnsudp";
DNSGetAlstateProgressCompletionStatus);
DNSAppLayerRegisterGetEventInfo(ALPROTO_DNS_UDP);
+
+ DNSUDPConfigure();
} else {
SCLogInfo("Parsed disabled for %s protocol. Protocol detection"
"still on.", proto_name);
SC_WARN_XFF_INVALID_MODE,
SC_WARN_XFF_INVALID_HEADER,
SC_ERR_THRESHOLD_SETUP,
+ SC_ERR_DNS_CONFIG,
} SCError;
const char *SCErrorToString(SCError);