Skip to content
Snippets Groups Projects
Commit f39ce29a authored by Marek Vavrusa's avatar Marek Vavrusa
Browse files

Implemented RRL classification and using name for hashing.

Basic classes (evaluated in following order):
* NORMAL - positive answer
* ERROR - rcode is not NXDOMAIN nor NOERROR
* NXDOMAIN - rcode is NXDOMAIN
* EMPTY - response doesn't contain any answers
* LARGE - packet size exceeded threshold (currently 1k)
* WILDCARD - answering from a wildcard

Reason behind not selectively classifying popular types like
DNSKEY, RRSIG or ANY is that any type could be exploited,
depending on the contents of the zone.

refs #2136
parent 47525a56
No related branches found
No related tags found
No related merge requests found
......@@ -16,9 +16,11 @@
#include <time.h>
#include <sys/socket.h>
#include <assert.h>
#include "knot/server/rrl.h"
#include "knot/common.h"
#include "libknot/consts.h"
#include "common/hattrie/murmurhash3.h"
/* Limits */
......@@ -30,6 +32,7 @@
#define RRL_DEFAULT_RATE 100
#define RRL_CAPACITY 8 /* N seconds. */
#define RRL_SSTART 4 /* 1/Nth of the rate for slow start */
#define RRL_PSIZE_LARGE 1024
/* Classification */
enum {
......@@ -38,28 +41,86 @@ enum {
CLS_ERROR = 1 << 1, /* Error response. */
CLS_NXDOMAIN = 1 << 2, /* NXDOMAIN (special case of error). */
CLS_EMPTY = 1 << 3, /* Empty response. */
CLS_SSTART = 1 << 4 /* Bucket in slow-start after collision. */
CLS_LARGE = 1 << 4, /* Response size over threshold (1024k). */
CLS_WILDCARD = 1 << 5, /* Wildcard query. */
CLS_SSTART = 1 << 6 /* Bucket in slow-start after collision. */
};
static uint8_t rrl_clsid(knot_packet_t *p) {
/*! \todo */
return CLS_NORMAL;
/* Check error code */
int ret = CLS_NULL;
switch (knot_packet_rcode(p)) {
case KNOT_RCODE_NOERROR: ret = CLS_NORMAL; break;
case KNOT_RCODE_NXDOMAIN: return CLS_NXDOMAIN; break;
default: return CLS_ERROR; break;
}
/* Check if answered from a qname */
if (ret == CLS_NORMAL && p->flags & KNOT_PF_WILDCARD) {
return CLS_WILDCARD;
}
/* Check packet size */
if (knot_packet_size(p) >= RRL_PSIZE_LARGE) {
return CLS_LARGE;
}
/* Check ancount */
if (knot_packet_ancount(p) == 0) {
return CLS_EMPTY;
}
return ret;
}
static int rrl_clsname(char *dst, uint8_t cls, knot_packet_t *p)
static int rrl_clsname(char *dst, size_t maxlen, uint8_t cls,
knot_packet_t *p, const knot_zone_t *z)
{
/*! \todo */
return 0;
const knot_dname_t *dn = NULL;
const uint8_t *n = (const uint8_t*)"\x00"; /* Fallback zone (for errors etc.) */
int nb = 1;
if (z) { /* Found associated zone. */
dn = knot_zone_name(z);
}
switch (cls) {
case CLS_ERROR: /* Could be a non-existent zone or garbage. */
case CLS_NXDOMAIN: /* Queries to non-existent names in zone. */
case CLS_WILDCARD: /* Queries to names covered by a wildcard. */
dbg_rrl_verb("%s: using zone/fallback name\n", __func__);
break;
default:
dn = knot_packet_qname(p);
break;
}
if (dn) { /* Check used dname. */
assert(dn); /* Should be always set. */
n = knot_dname_name(dn);
nb = (int)knot_dname_size(dn);
}
/* Write to wire */
if (nb > maxlen) return KNOT_ESPACE;
if (memcpy(dst, n, nb) == NULL) {
dbg_rrl("%s: failed to serialize name=%p len=%u\n",
__func__, n, nb);
return KNOT_ERROR;
}
return nb;
}
static int rrl_classify(char *dst, size_t maxlen,
sockaddr_t *a, knot_packet_t *p, uint32_t seed)
static int rrl_classify(char *dst, size_t maxlen, sockaddr_t *a,
knot_packet_t *p, const knot_zone_t *z, uint32_t seed)
{
if (!dst || !a || !p || maxlen == 0) {
return KNOT_EINVAL;
}
/* Class */
int blklen = 0;
uint8_t cls = rrl_clsid(p);
*dst = cls;
blklen += sizeof(cls);
int blklen = sizeof(cls);
/* Address (in network byteorder, adjust masks). */
uint64_t nb = 0;
......@@ -68,30 +129,29 @@ static int rrl_classify(char *dst, size_t maxlen,
} else { /* Take the /24 prefix */
nb = (uint32_t)a->addr4.sin_addr.s_addr & RRL_V4_PREFIX;
}
if (blklen + sizeof(nb) > maxlen) return KNOT_ESPACE;
memcpy(dst + blklen, (void*)&nb, sizeof(nb));
blklen += sizeof(nb);
/* Name */
int len = rrl_clsname(dst + blklen, cls, p);
if (len < 0) {
return KNOT_ERROR;
} else {
blklen += len;
}
int len = rrl_clsname(dst + blklen, maxlen - blklen, cls, p, z);
if (len < 0) return len;
blklen += len;
/* Seed. */
len = sizeof(seed);
if (memcpy(dst + blklen, (void*)&seed, len) == 0) {
blklen += len;
if (blklen + sizeof(seed) > maxlen) return KNOT_ESPACE;
if (memcpy(dst + blklen, (void*)&seed, sizeof(seed)) == 0) {
blklen += sizeof(seed);
}
return blklen;
}
static rrl_item_t* rrl_hash(rrl_table_t *t, sockaddr_t *a, knot_packet_t *p, uint32_t stamp)
static rrl_item_t* rrl_hash(rrl_table_t *t, sockaddr_t *a, knot_packet_t *p,
const knot_zone_t *zone, uint32_t stamp)
{
char buf[RRL_CLSBLK_MAXLEN];
int len = rrl_classify(buf, sizeof(buf), a, p, t->seed);
int len = rrl_classify(buf, sizeof(buf), a, p, zone, t->seed);
if (len < 0) {
return NULL;
}
......@@ -146,14 +206,14 @@ uint32_t rrl_rate(rrl_table_t *rrl)
return rrl->rate;
}
int rrl_query(rrl_table_t *rrl, sockaddr_t* src, knot_packet_t *resp)
int rrl_query(rrl_table_t *rrl, sockaddr_t* src, knot_packet_t *resp, const knot_zone_t *zone)
{
if (!rrl || !src || !resp) return KNOT_EINVAL;
/* Calculate hash and fetch */
int ret = KNOT_EOK;
uint32_t now = time(NULL);
rrl_item_t *b = rrl_hash(rrl, src, resp, now);
rrl_item_t *b = rrl_hash(rrl, src, resp, zone, now);
if (!b) {
dbg_rrl("%s: failed to compute bucket from packet\n", __func__);
return KNOT_ERROR;
......
......@@ -30,6 +30,7 @@
#include <stdint.h>
#include "common/sockaddr.h"
#include "libknot/packet/packet.h"
#include "libknot/zone/zone.h"
typedef struct rrl_item {
uint64_t pref; /* Prefix associated. */
......@@ -39,7 +40,7 @@ typedef struct rrl_item {
} rrl_item_t;
typedef struct rrl_table {
uint32_t rate; /* Configured RRL limit */
uint32_t rate; /* Configured RRL limit */
uint32_t seed; /* Pseudorandom seed for hashing. */
size_t size; /* Number of buckets */
rrl_item_t arr[]; /* Buckets */
......@@ -48,7 +49,7 @@ typedef struct rrl_table {
rrl_table_t *rrl_create(size_t size);
uint32_t rrl_setrate(rrl_table_t *rrl, uint32_t rate);
uint32_t rrl_rate(rrl_table_t *rrl);
int rrl_query(rrl_table_t *rrl, sockaddr_t* src, knot_packet_t *resp);
int rrl_query(rrl_table_t *rrl, sockaddr_t* src, knot_packet_t *resp, const knot_zone_t *zone);
int rrl_destroy(rrl_table_t *rrl);
......
......@@ -53,6 +53,8 @@ static int rrl_tests_run(int argc, char *argv[])
ok(rate == rrl_rate(rrl), "rrl: setrate");
/* 3. N unlimited requests. */
knot_dname_t *apex = knot_dname_new_from_str("rrl.", 4, NULL);
knot_zone_t *zone = knot_zone_new(knot_node_new(apex, NULL, 0), 0, 0);
sockaddr_t addr;
sockaddr_t addr6;
sockaddr_set(&addr, AF_INET, "1.2.3.4", 0);
......@@ -61,8 +63,8 @@ static int rrl_tests_run(int argc, char *argv[])
knot_response_init(pkt);
int ret = 0;
for (unsigned i = 0; i < rate; ++i) {
if (rrl_query(rrl, &addr, pkt) != KNOT_EOK ||
rrl_query(rrl, &addr6, pkt) != KNOT_EOK) {
if (rrl_query(rrl, &addr, pkt, zone) != KNOT_EOK ||
rrl_query(rrl, &addr6, pkt, zone) != KNOT_EOK) {
ret = KNOT_ELIMIT;
break;
}
......@@ -70,13 +72,14 @@ static int rrl_tests_run(int argc, char *argv[])
ok(ret == 0, "rrl: unlimited IPv4/v6 requests");
/* 4. limited request */
ret = rrl_query(rrl, &addr, pkt);
ret = rrl_query(rrl, &addr, pkt, zone);
ok(ret != 0, "rrl: throttled IPv4 request");
/* 5. limited IPv6 request */
ret = rrl_query(rrl, &addr6, pkt);
ret = rrl_query(rrl, &addr6, pkt, zone);
ok(ret != 0, "rrl: throttled IPv6 request");
knot_zone_deep_free(&zone, 0);
rrl_destroy(rrl);
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment