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

Initial implementation of RRL.

Based on memo and implementation notes from
Vixie and Schryver.
http://ss.vix.su/~vixie/isc-tn-2012-1.txt

Basically a token bucket algorithm, no interpolation
yet. Classification of responses based on:
<address prefix, resp.class, name, seed>

address prefix = /24 for IPv4, /56 for IPv6
resp.class = based on rcode,question and ancount
name = either qname or answer
seed = secret to harden collision prediction

No SLIP yet.
parent 6a31bf80
Branches
Tags
No related merge requests found
......@@ -62,6 +62,8 @@ src/libknot/zone/zone-tree.c
src/libknot/zone/dname-table.h
src/libknot/zone/dname-table.c
src/Makefile.am
src/common/hattrie/murmurhash3.c
src/common/hattrie/murmurhash3.h
src/common/slab/slab.c
src/common/slab/slab.h
src/common/slab/alloc-common.h
......@@ -160,6 +162,8 @@ src/knot/server/journal.c
src/knot/server/journal.h
src/knot/server/notify.c
src/knot/server/notify.h
src/knot/server/rrl.c
src/knot/server/rrl.h
src/knot/ctl/process.c
src/knot/ctl/process.h
src/knot/conf/cf-lex.l
......@@ -196,6 +200,8 @@ src/tests/knot/journal_tests.c
src/tests/knot/journal_tests.h
src/tests/knot/server_tests.c
src/tests/knot/server_tests.h
src/tests/knot/rrl_tests.c
src/tests/knot/rrl_tests.h
src/tests/libknot/unittests_libknot.c
src/tests/libknot/libknot/dname_tests.c
src/tests/libknot/libknot/dname_tests.h
......
......@@ -76,6 +76,8 @@ unittests_SOURCES = \
tests/knot/journal_tests.h \
tests/knot/server_tests.c \
tests/knot/server_tests.h \
tests/knot/rrl_tests.c \
tests/knot/rrl_tests.h \
tests/unittests_main.c
unittests_libknot_realdata_SOURCES = \
......@@ -215,6 +217,8 @@ libknot_la_SOURCES = \
libknot/tsig-op.c
libknots_la_SOURCES = \
common/hattrie/murmurhash3.c \
common/hattrie/murmurhash3.h \
common/slab/slab.c \
common/slab/slab.h \
common/libtap/tap.c \
......@@ -304,6 +308,8 @@ libknotd_la_SOURCES = \
knot/server/notify.h \
knot/server/notify.c \
knot/server/zones.h \
knot/server/rrl.c \
knot/server/rrl.h \
knot/zone/zone-load.c \
knot/zone/zone-load.h \
knot/zone/semantic-check.c \
......
......@@ -70,6 +70,7 @@ const error_table_t knot_error_msgs[] = {
{KNOT_ECNAME, "CNAME loop found in zone."},
{KNOT_ENODIFF, "Cannot create zone diff."},
{KNOT_EDSDIGESTLEN, "DS digest length does not match digest type." },
{KNOT_ELIMIT, "Exceeded response rate limit." },
{KNOT_ERROR, 0}
};
......@@ -83,7 +83,8 @@ enum knot_error {
KNOT_EIXFRSPACE, /*!< IXFR reply did not fit in. */
KNOT_ECNAME, /*!< CNAME loop found in zone. */
KNOT_ENODIFF, /*!< No zone diff can be created. */
KNOT_EDSDIGESTLEN /*!< DS digest length does not match digest type. */
KNOT_EDSDIGESTLEN, /*!< DS digest length does not match digest type. */
KNOT_ELIMIT /*!< Exceeded response rate limit. */
};
/*! \brief Table linking error messages to error codes. */
......
......@@ -33,6 +33,7 @@
#define KNOTD_THREADS_DEBUG
#define KNOTD_JOURNAL_DEBUG
#define KNOTD_NET_DEBUG
#define KNOTD_RRL_DEBUG
#endif
#ifdef KNOT_ZONES_DEBUG
......@@ -179,6 +180,47 @@
/******************************************************************************/
#ifdef KNOTD_RRL_DEBUG
/* Brief messages. */
#ifdef DEBUG_ENABLE_BRIEF
#define dbg_rrl(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg)
#define dbg_rrl_hex(data, len) hex_log(LOG_SERVER, (data), (len))
#else
#define dbg_rrl(msg...)
#define dbg_rrl_hex(data, len)
#endif
/* Verbose messages. */
#ifdef DEBUG_ENABLE_VERBOSE
#define dbg_rrl_verb(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg)
#define dbg_rrl_hex_verb(data, len) hex_log(LOG_SERVER, (data), (len))
#else
#define dbg_rrl_verb(msg...)
#define dbg_rrl_hex_verb(data, len)
#endif
/* Detail messages. */
#ifdef DEBUG_ENABLE_DETAILS
#define dbg_rrl_detail(msg...) log_msg(LOG_SERVER, LOG_DEBUG, msg)
#define dbg_rrl_hex_detail(data, len) hex_log(LOG_SERVER, (data), (len))
#else
#define dbg_rrl_detail(msg...)
#define dbg_rrl_hex_detail(data, len)
#endif
/* No messages. */
#else
#define dbg_rrl(msg...)
#define dbg_rrl_hex(data, len)
#define dbg_rrl_verb(msg...)
#define dbg_rrl_hex_verb(data, len)
#define dbg_rrl_detail(msg...)
#define dbg_rrl_hex_detail(data, len)
#endif
/******************************************************************************/
#ifdef KNOTD_THREADS_DEBUG
/* Brief messages. */
......
/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <time.h>
#include <sys/socket.h>
#include "knot/server/rrl.h"
#include "knot/common.h"
#include "common/hattrie/murmurhash3.h"
/* Limits */
#define RRL_CLSBLK_MAXLEN (4 + 8 + 1 + 256)
/* CIDR block prefix lengths for v4/v6 */
#define RRL_V4_PREFIX ((uint32_t)0xffffff00) /* /24 */
#define RRL_V6_PREFIX ((uint32_t)0xffffffffffffff00) /* /56 */
/* Defaults */
#define RRL_DEFAULT_RATE 100
#define RRL_CAPACITY 8 /* N seconds. */
/* Classification */
enum {
CLS_NULL = 0 << 0, /* Empty bucket. */
CLS_NORMAL = 1 << 0, /* Normal response. */
CLS_ERROR = 1 << 1, /* Error response. */
CLS_NXDOMAIN = 1 << 2, /* NXDOMAIN (special case of error). */
CLS_EMPTY = 1 << 3 /* Empty response. */
};
static uint8_t rrl_clsid(knot_packet_t *p) {
/*! \todo */
return CLS_NORMAL;
}
static int rrl_clsname(char *dst, uint8_t cls, knot_packet_t *p)
{
/*! \todo */
return 0;
}
static int rrl_classify(char *dst, size_t maxlen,
sockaddr_t *a, knot_packet_t *p, uint32_t seed)
{
/* Address. */
/*! \todo This is wrong, as the addr is in net byteorder. */
uint64_t nb = 0;
int blklen = 0;
if (a->family == AF_INET6) { /* Take top 56 bits */
nb = *((uint64_t*)&a->addr6.sin6_addr) & RRL_V6_PREFIX;
blklen = 7 * sizeof(uint8_t);
} else {
nb = (uint32_t)a->addr4.sin_addr.s_addr & RRL_V4_PREFIX;
blklen = 3 * sizeof(uint8_t);
}
memcpy(dst, (void*)&nb, blklen);
/* Class */
uint8_t cls = rrl_clsid(p);
*(dst + blklen) = cls;
blklen += sizeof(cls);
/* Name */
int nl = rrl_clsname(dst + blklen, cls, p);
if (nl < 0) {
return KNOT_ERROR;
} else {
blklen += nl;
}
/* Seed. */
if (memcpy(dst + blklen, (void*)&seed, sizeof(seed)) == 0) {
blklen += nl;
}
return blklen;
}
static rrl_item_t* rrl_hash(rrl_table_t *t, sockaddr_t *a, knot_packet_t *p)
{
char buf[RRL_CLSBLK_MAXLEN];
int len = rrl_classify(buf, sizeof(buf), a, p, t->seed);
if (len < 0) {
return NULL;
}
uint32_t id = hash(buf, len) % t->size;
dbg_rrl("%s: classified pkt as '0x%04x'\n", __func__, id);
return t->arr + id;
}
rrl_table_t *rrl_create(size_t size)
{
const size_t tbl_len = sizeof(rrl_table_t) + size * sizeof(rrl_item_t);
rrl_table_t *t = malloc(tbl_len);
if (!t) return NULL;
memset(t, 0, tbl_len);
t->rate = RRL_DEFAULT_RATE;
t->seed = time(NULL);
t->size = size;
return t;
}
uint32_t rrl_setrate(rrl_table_t *rrl, uint32_t rate)
{
if (!rrl) return 0;
uint32_t old = rrl->rate;
rrl->rate = rate;
return old;
}
uint32_t rrl_rate(rrl_table_t *rrl)
{
if (!rrl) return 0;
return rrl->rate;
}
int rrl_query(rrl_table_t *rrl, sockaddr_t* src, knot_packet_t *resp)
{
if (!rrl || !src || !resp) return KNOT_EINVAL;
/* Calculate hash and fetch */
int ret = KNOT_EOK;
rrl_item_t *b = rrl_hash(rrl, src, resp);
if (!b) {
dbg_rrl("%s: failed to compute bucket from packet\n", __func__);
return KNOT_ERROR;
}
/* Initialize. */
uint32_t now = time(NULL);
if (b->flags == CLS_NULL) {
b->flags = rrl_clsid(resp);
b->ntok = rrl->rate;
b->time = now;
/*! \todo Reuse from rrl_hash() and also store address. */
/*! \todo Should check address for collisions. */
}
/* Calculate rate for dT */
uint32_t dt = now - b->time;
if (dt > RRL_CAPACITY) {
dt = RRL_CAPACITY;
}
dbg_rrl("%s: bucket=%p tokens=%hu flags=%x dt=%u\n",
__func__, b, b->ntok, b->flags, dt);
if (dt > 0) { /* Window moved. */
b->ntok += rrl->rate * dt; /*! \todo Interpolate. */
if (b->ntok > RRL_CAPACITY * rrl->rate) {
b->ntok = RRL_CAPACITY * rrl->rate;
}
}
/* Visit bucket. */
b->time = now;
/* Check token count */
if (b->ntok > 0) {
--b->ntok;
} else {
ret = KNOT_ELIMIT; /* No available token. */
}
return ret;
}
int rrl_destroy(rrl_table_t *rrl)
{
free(rrl);
return KNOT_EOK;
}
/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*!
* \file rrl.h
*
* \author Marek Vavrusa <marek.vavusa@nic.cz>
*
* \brief Response-rate limiting API.
*
* \addtogroup network
* @{
*/
#ifndef _KNOTD_RRL_H_
#define _KNOTD_RRL_H_
#include <stdint.h>
#include "common/sockaddr.h"
#include "libknot/packet/packet.h"
typedef struct rrl_item {
union {
uint32_t v4;
uint64_t v6;
} ip; /* Query prefix */
uint16_t ntok; /* Tokens available */
uint16_t flags; /* Flags */
uint32_t time; /* Timestamp */
} rrl_item_t;
typedef struct rrl_table {
uint32_t rate; /* Configured RRL limit */
uint32_t seed; /* Pseudorandom seed for hashing. */
size_t size; /* Number of buckets */
rrl_item_t arr[]; /* Buckets */
} rrl_table_t;
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_destroy(rrl_table_t *rrl);
#endif /* _KNOTD_RRL_H_ */
/*! @} */
/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/socket.h>
#include "tests/knot/rrl_tests.h"
#include "knot/server/rrl.h"
#include "knot/common.h"
#include "libknot/packet/response.h"
static int rrl_tests_count(int argc, char *argv[]);
static int rrl_tests_run(int argc, char *argv[]);
/*
* Unit API.
*/
unit_api rrl_tests_api = {
"RRL",
&rrl_tests_count,
&rrl_tests_run
};
/*
* Unit implementation.
*/
static int rrl_tests_count(int argc, char *argv[])
{
return 4;
}
static int rrl_tests_run(int argc, char *argv[])
{
/* 1. create rrl table */
rrl_table_t *rrl = rrl_create(100);
ok(rrl != NULL, "rrl: create");
/* 2. set rate limit */
uint32_t rate = 10;
rrl_setrate(rrl, rate);
ok(rate == rrl_rate(rrl), "rrl: setrate");
/* 3. N unlimited requests. */
sockaddr_t addr;
sockaddr_set(&addr, AF_INET, "127.0.0.1", 0);
knot_packet_t *pkt = knot_packet_new(KNOT_PACKET_PREALLOC_NONE);
knot_response_init(pkt);
int ret = 0;
for (unsigned i = 0; i < rate; ++i) {
if (rrl_query(rrl, &addr, pkt) != KNOT_EOK) {
ret = KNOT_ELIMIT;
break;
}
}
ok(ret == 0, "rrl: unlimited requests");
/* 4 limited request */
ret = rrl_query(rrl, &addr, pkt);
ok(ret != 0, "rrl: throttled request");
rrl_destroy(rrl);
return 0;
}
/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _KNOTD_RRL_TESTS_H_
#define _KNOTD_RRL_TESTS_H_
#include "common/libtap/tap_unit.h"
/* Unit API. */
unit_api rrl_tests_api;
#endif /* _KNOTD_RRL_TESTS_H_ */
......@@ -30,6 +30,7 @@
#include "tests/knot/journal_tests.h"
#include "tests/knot/server_tests.h"
#include "tests/knot/conf_tests.h"
#include "tests/knot/rrl_tests.h"
// Run all loaded units
int main(int argc, char *argv[])
......@@ -43,19 +44,20 @@ int main(int argc, char *argv[])
// Build test set
unit_api *tests[] = {
/* Core data structures. */
&journal_tests_api, //! Journal unit
&slab_tests_api, //! SLAB allocator unit
&skiplist_tests_api, //! Skip list unit
&dthreads_tests_api, //! DThreads testing unit
&events_tests_api, //! Events testing unit
&acl_tests_api, //! ACLs
&fdset_tests_api, //! FDSET polling wrapper
&base64_tests_api, //! Base64 encoding
&base32hex_tests_api, //! Base32hex encoding
// &journal_tests_api, //! Journal unit
// &slab_tests_api, //! SLAB allocator unit
// &skiplist_tests_api, //! Skip list unit
// &dthreads_tests_api, //! DThreads testing unit
// &events_tests_api, //! Events testing unit
// &acl_tests_api, //! ACLs
// &fdset_tests_api, //! FDSET polling wrapper
// &base64_tests_api, //! Base64 encoding
// &base32hex_tests_api, //! Base32hex encoding
/* Server parts. */
&conf_tests_api, //! Configuration parser tests
&server_tests_api, //! Server unit
// /* Server parts. */
// &conf_tests_api, //! Configuration parser tests
// &server_tests_api, //! Server unit
&rrl_tests_api, //! RRL tests
NULL
};
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment