Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (8)
......@@ -19,14 +19,13 @@
#include "knot/modules/rrl/functions.h"
#include "knot/modules/rrl/kru.h"
#include "contrib/macros.h"
#include "contrib/musl/inet_ntop.h"
#include "contrib/sockaddr.h"
#include "contrib/time.h"
#include "libdnssec/random.h"
/* CIDR block prefix lengths for v4/v6 */
// Hardcoded also in unit tests.
// CIDR block prefix lengths for v4/v6 (hardcoded also in unit tests).
#define RRL_V4_PREFIXES (uint8_t[]) { 18, 20, 24, 32 }
#define RRL_V4_RATE_MULT (kru_price_t[]) { 768, 256, 32, 1 }
......@@ -41,10 +40,13 @@
#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC
#endif
#define RRL_LIMIT_KOEF 1/2 // Avoid probabilistic rounding wherever possible.
struct rrl_table {
kru_price_t v4_prices[RRL_V4_PREFIXES_CNT];
kru_price_t v6_prices[RRL_V6_PREFIXES_CNT];
uint32_t log_period;
bool rw_mode;
_Atomic uint32_t log_time;
_Alignas(64) uint8_t kru[];
};
......@@ -66,7 +68,8 @@ static void addr_tostr(char *dst, size_t maxlen, const struct sockaddr_storage *
}
}
static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss, const uint8_t prefix)
static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss,
const uint8_t prefix, bool rate)
{
if (mod == NULL) {
return;
......@@ -75,10 +78,12 @@ static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss,
char addr_str[SOCKADDR_STRLEN];
addr_tostr(addr_str, sizeof(addr_str), ss);
knotd_mod_log(mod, LOG_NOTICE, "address %s limited on /%d", addr_str, prefix);
knotd_mod_log(mod, LOG_NOTICE, "address %s limited on /%d by %s",
addr_str, prefix, rate ? "rate" : "time");
}
rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, uint32_t log_period)
rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit,
bool rw_mode, uint32_t log_period)
{
if (size == 0 || instant_limit == 0 || rate_limit == 0) {
return NULL;
......@@ -95,9 +100,12 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit
}
memset(rrl, 0, rrl_size);
const kru_price_t base_price = KRU_LIMIT / instant_limit;
const kru_price_t max_decay = rate_limit > 1000ll * instant_limit ? base_price :
(uint64_t) base_price * rate_limit / 1000;
assert(rate_limit <= 1000ll * instant_limit); // Ensured by config check.
kru_price_t base_price = KRU_LIMIT / instant_limit;
const kru_price_t max_decay = (uint64_t)base_price * rate_limit / 1000;
if (!rw_mode) {
base_price = base_price * RRL_LIMIT_KOEF;
}
if (!KRU.initialize((struct kru *)rrl->kru, capacity_log, max_decay)) {
free(rrl);
......@@ -112,9 +120,10 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit
rrl->v6_prices[i] = base_price / RRL_V6_RATE_MULT[i];
}
rrl->rw_mode = rw_mode;
rrl->log_period = log_period;
struct timespec now_ts = {0};
struct timespec now_ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts);
uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000;
rrl->log_time = now - log_period;
......@@ -124,42 +133,104 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit
int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod)
{
if (rrl == NULL || remote == NULL) {
return KNOT_EINVAL;
}
assert(rrl);
assert(remote);
struct timespec now_ts = {0};
struct timespec now_ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts);
uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000;
uint8_t limited_prefix;
uint16_t load = 0;
uint8_t prefix = 0;
_Alignas(16) uint8_t key[16] = { 0 };
if (remote->ss_family == AF_INET6) {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)remote;
memcpy(key, &ipv6->sin6_addr, 16);
limited_prefix = KRU.limited_multi_prefix_or((struct kru *)rrl->kru, now,
1, key, RRL_V6_PREFIXES, rrl->v6_prices, RRL_V6_PREFIXES_CNT, NULL);
if (rrl->rw_mode) {
prefix = KRU.limited_multi_prefix_or(
(struct kru *)rrl->kru, now, 1, key, RRL_V6_PREFIXES,
rrl->v6_prices, RRL_V6_PREFIXES_CNT, NULL);
} else {
load = KRU.load_multi_prefix_max(
(struct kru *)rrl->kru, now, 1, key, RRL_V6_PREFIXES,
NULL, RRL_V6_PREFIXES_CNT, &prefix);
}
} else {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)remote;
memcpy(key, &ipv4->sin_addr, 4);
limited_prefix = KRU.limited_multi_prefix_or((struct kru *)rrl->kru, now,
0, key, RRL_V4_PREFIXES, rrl->v4_prices, RRL_V4_PREFIXES_CNT, NULL);
if (rrl->rw_mode) {
prefix = KRU.limited_multi_prefix_or(
(struct kru *)rrl->kru, now, 0, key, RRL_V4_PREFIXES,
rrl->v4_prices, RRL_V4_PREFIXES_CNT, NULL);
} else {
load = KRU.load_multi_prefix_max(
(struct kru *)rrl->kru, now, 0, key, RRL_V4_PREFIXES,
NULL, RRL_V4_PREFIXES_CNT, &prefix);
}
}
if (rrl->rw_mode) {
if (prefix == 0) {
return KNOT_EOK;
}
} else {
if (load <= (1 << 16) * RRL_LIMIT_KOEF) {
return KNOT_EOK;
}
}
uint32_t log_time_orig = atomic_load_explicit(&rrl->log_time, memory_order_relaxed);
if (rrl->log_period && limited_prefix && (now - log_time_orig + 1024 >= rrl->log_period + 1024)) {
if (rrl->log_period && (now - log_time_orig + 1024 >= rrl->log_period + 1024)) {
do {
if (atomic_compare_exchange_weak_explicit(&rrl->log_time, &log_time_orig, now,
memory_order_relaxed, memory_order_relaxed)) {
rrl_log_limited(mod, remote, limited_prefix);
rrl_log_limited(mod, remote, prefix, rrl->rw_mode);
break;
}
} while (now - log_time_orig + 1024 >= rrl->log_period + 1024);
}
return limited_prefix ? KNOT_ELIMIT : KNOT_EOK;
return KNOT_ELIMIT;
}
void rrl_update(rrl_table_t *rrl, const struct sockaddr_storage *remote, size_t value)
{
assert(rrl);
assert(remote);
assert(!rrl->rw_mode);
struct timespec now_ts;
clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts);
uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000;
_Alignas(16) uint8_t key[16] = { 0 };
if (remote->ss_family == AF_INET6) {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)remote;
memcpy(key, &ipv6->sin6_addr, 16);
kru_price_t prices[RRL_V6_PREFIXES_CNT];
for (size_t i = 0; i < RRL_V6_PREFIXES_CNT; i++) {
prices[i] = MIN(value * rrl->v6_prices[i], (kru_price_t)-1LL);
}
(void)KRU.load_multi_prefix_max((struct kru *)rrl->kru, now,
1, key, RRL_V6_PREFIXES, prices,
RRL_V6_PREFIXES_CNT, NULL);
} else {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)remote;
memcpy(key, &ipv4->sin_addr, 4);
kru_price_t prices[RRL_V4_PREFIXES_CNT];
for (size_t i = 0; i < RRL_V4_PREFIXES_CNT; i++) {
prices[i] = MIN(value * rrl->v4_prices[i], (kru_price_t)-1LL);
}
(void)KRU.load_multi_prefix_max((struct kru *)rrl->kru, now,
0, key, RRL_V4_PREFIXES, prices,
RRL_V4_PREFIXES_CNT, NULL);
}
}
bool rrl_slip_roll(int n_slip)
......
......@@ -27,17 +27,21 @@ typedef struct rrl_table rrl_table_t;
* \brief Create a RRL table.
*
* \param size Fixed table size.
* \param rate Rate (in pkts/sec).
* \param instant_limit Instant limit (number).
* \param instant_limit Instant limit.
* \param rate_limit Rate limit.
* \param rw_mode If disabled, RW operation is divided into R and W operations.
* \param log_period If nonzero, maximum logging period (in milliseconds).
*
* \return created table or NULL.
*/
rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, uint32_t log_period);
rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit,
bool rw_mode, uint32_t log_period);
/*!
* \brief Query the RRL table for accept or deny, when the rate limit is reached.
*
* \note This function is common to both RW and non-RW modes!
*
* \param rrl RRL table.
* \param remote Source address.
* \param mod Query module (needed for logging).
......@@ -47,6 +51,17 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit
*/
int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod);
/*!
* \brief Update the RRL table.
*
* \note This function is only for the non-RW mode!
*
* \param rrl RRL table.
* \param remote Source address.
* \param value Value with which the table is updated.
*/
void rrl_update(rrl_table_t *rrl, const struct sockaddr_storage *remote, size_t value);
/*!
* \brief Roll a dice whether answer slips or not.
*
......
......@@ -14,12 +14,15 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "contrib/time.h"
#include "knot/include/module.h"
#include "knot/modules/rrl/functions.h"
#include "knot/modules/rrl/kru.h"
#define MOD_RATE_LIMIT "\x0A""rate-limit"
#define MOD_INSTANT_LIMIT "\x0D""instant-limit"
#define MOD_INST_LIMIT "\x0D""instant-limit"
#define MOD_T_RATE_LIMIT "\x0F""time-rate-limit"
#define MOD_T_INST_LIMIT "\x12""time-instant-limit"
#define MOD_SLIP "\x04""slip"
#define MOD_TBL_SIZE "\x0A""table-size"
#define MOD_WHITELIST "\x09""whitelist"
......@@ -27,8 +30,10 @@
#define MOD_DRY_RUN "\x07""dry-run"
const yp_item_t rrl_conf[] = {
{ MOD_INSTANT_LIMIT, YP_TINT, YP_VINT = { 1, (1ll << 32) / 768 - 1, 50 } },
{ MOD_RATE_LIMIT, YP_TINT, YP_VINT = { 1, ((1ll << 32) / 768 - 1) * 1000 } },
{ MOD_INST_LIMIT, YP_TINT, YP_VINT = { 1, (1ll << 32) / 768 - 1, 50 } },
{ MOD_RATE_LIMIT, YP_TINT, YP_VINT = { 0, ((1ll << 32) / 768 - 1) * 1000, 20 } },
{ MOD_T_INST_LIMIT, YP_TINT, YP_VINT = { 1, 1000000, 5000 } },
{ MOD_T_RATE_LIMIT, YP_TINT, YP_VINT = { 0, 1000000000, 4000 } },
{ MOD_SLIP, YP_TINT, YP_VINT = { 0, 100, 1 } },
{ MOD_TBL_SIZE, YP_TINT, YP_VINT = { 1, INT32_MAX, 524288 } },
{ MOD_WHITELIST, YP_TNET, YP_VNONE, YP_FMULTI },
......@@ -40,13 +45,16 @@ const yp_item_t rrl_conf[] = {
int rrl_conf_check(knotd_conf_check_args_t *args)
{
knotd_conf_t rate_limit = knotd_conf_check_item(args, MOD_RATE_LIMIT);
knotd_conf_t instant_limit = knotd_conf_check_item(args, MOD_INSTANT_LIMIT);
if (rate_limit.count == 0) {
args->err_str = "no rate limit specified";
knotd_conf_t inst_limit = knotd_conf_check_item(args, MOD_INST_LIMIT);
if (rate_limit.single.integer > 1000ll * inst_limit.single.integer) {
args->err_str = "rate limit is higher than 1000 times instant rate limit";
return KNOT_EINVAL;
}
if (rate_limit.single.integer > 1000ll * instant_limit.single.integer) {
args->err_str = "rate limit per millisecond is higher than instant limit";
knotd_conf_t t_rate_limit = knotd_conf_check_item(args, MOD_T_RATE_LIMIT);
knotd_conf_t t_inst_limit = knotd_conf_check_item(args, MOD_T_INST_LIMIT);
if (t_rate_limit.single.integer > 1000ll * t_inst_limit.single.integer) {
args->err_str = "time rate limit is higher than 1000 times time instant limit";
return KNOT_EINVAL;
}
......@@ -54,35 +62,116 @@ int rrl_conf_check(knotd_conf_check_args_t *args)
}
typedef struct {
rrl_table_t *rrl;
ALIGNED_CPU_CACHE // Ensures that one thread context occupies one cache line.
struct timespec start_time; // Start time of the measurement.
bool whitelist_checked; // Indication whether whitelist check took place.
bool skip; // Skip the rest of the module callbacks.
} thrd_ctx_t;
typedef struct {
rrl_table_t *rate_table;
rrl_table_t *time_table;
thrd_ctx_t *thrd_ctx;
int slip;
bool dry_run;
knotd_conf_t whitelist;
} rrl_ctx_t;
static uint32_t time_diff_us(const struct timespec *begin, const struct timespec *end)
{
struct timespec result = time_diff(begin, end);
return (result.tv_sec * 1000000) + (result.tv_nsec / 1000);
}
static knotd_proto_state_t protolimit_start(knotd_proto_state_t state,
knotd_qdata_params_t *params,
knotd_mod_t *mod)
{
rrl_ctx_t *ctx = knotd_mod_ctx(mod);
thrd_ctx_t *thrd = &ctx->thrd_ctx[params->thread_id];
thrd->skip = false;
// Check if a whitelisted client.
thrd->whitelist_checked = true;
if (knotd_conf_addr_range_match(&ctx->whitelist, params->remote)) {
thrd->skip = true;
return state;
}
// UDP time limiting not implemented (source address can be forged).
if (params->proto == KNOTD_QUERY_PROTO_UDP) {
return state;
}
// Check if the packet is limited.
if (rrl_query(ctx->time_table, params->remote, mod) != KNOT_EOK) {
thrd->skip = true;
knotd_mod_stats_incr(mod, params->thread_id, 2, 0, 1);
return ctx->dry_run ? state : KNOTD_PROTO_STATE_BLOCK;
} else {
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &thrd->start_time);
return state; // Not limited.
}
}
static knotd_proto_state_t protolimit_end(knotd_proto_state_t state,
knotd_qdata_params_t *params,
knotd_mod_t *mod)
{
rrl_ctx_t *ctx = knotd_mod_ctx(mod);
thrd_ctx_t *thrd = &ctx->thrd_ctx[params->thread_id];
if (thrd->skip || params->proto == KNOTD_QUERY_PROTO_UDP) {
return state;
}
// Update the time table.
struct timespec end_time;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end_time);
uint64_t diff = time_diff_us(&thrd->start_time, &end_time);
if (diff > 0) { // Zero KRU update is NOOP.
rrl_update(ctx->time_table, params->remote, diff);
}
return state;
}
static knotd_state_t ratelimit_apply(knotd_state_t state, knot_pkt_t *pkt,
knotd_qdata_t *qdata, knotd_mod_t *mod)
{
assert(pkt && qdata && mod);
rrl_ctx_t *ctx = knotd_mod_ctx(mod);
thrd_ctx_t *thrd = &ctx->thrd_ctx[qdata->params->thread_id];
if (thrd->skip) {
return state;
}
// Rate limit is applied to pure UDP only.
// Don't limit authorized operations.
if (qdata->params->flags & KNOTD_QUERY_FLAG_AUTHORIZED) {
thrd->skip = true;
return state;
}
// Rate limit is applied to UDP only.
if (qdata->params->proto != KNOTD_QUERY_PROTO_UDP) {
return state;
}
// Rate limit is not applied to responses with a valid cookie.
if (qdata->params->flags & KNOTD_QUERY_FLAG_COOKIE) {
// Check for whitelisted client IF PER-ZONE module (no proto callbacks).
if (!thrd->whitelist_checked &&
knotd_conf_addr_range_match(&ctx->whitelist, qdata->params->remote)) {
thrd->skip = true;
return state;
}
// Exempt clients.
if (knotd_conf_addr_range_match(&ctx->whitelist, knotd_qdata_remote_addr(qdata))) {
// Rate limit is not applied to responses with a valid cookie.
if (qdata->params->flags & KNOTD_QUERY_FLAG_COOKIE) {
return state;
}
if (rrl_query(ctx->rrl, knotd_qdata_remote_addr(qdata), mod) == KNOT_EOK) {
if (rrl_query(ctx->rate_table, knotd_qdata_remote_addr(qdata), mod) == KNOT_EOK) {
// Rate limiting not applied.
return state;
}
......@@ -103,7 +192,10 @@ static void ctx_free(rrl_ctx_t *ctx)
{
assert(ctx);
rrl_destroy(ctx->rrl);
free(ctx->thrd_ctx);
rrl_destroy(ctx->rate_table);
rrl_destroy(ctx->time_table);
knotd_conf_free(&ctx->whitelist);
free(ctx);
}
......@@ -114,19 +206,38 @@ int rrl_load(knotd_mod_t *mod)
return KNOT_ENOMEM;
}
uint32_t instant_limit = knotd_conf_mod(mod, MOD_INSTANT_LIMIT).single.integer;
uint32_t rate_limit = knotd_conf_mod(mod, MOD_RATE_LIMIT).single.integer;
size_t size = knotd_conf_mod(mod, MOD_TBL_SIZE).single.integer;
uint32_t log_period = knotd_conf_mod(mod, MOD_LOG_PERIOD).single.integer;
ctx->rrl = rrl_create(size, instant_limit, rate_limit, log_period);
if (ctx->rrl == NULL) {
ctx->dry_run = knotd_conf_mod(mod, MOD_DRY_RUN).single.boolean;
ctx->whitelist = knotd_conf_mod(mod, MOD_WHITELIST);
ctx->thrd_ctx = calloc(knotd_mod_threads(mod), sizeof(*ctx->thrd_ctx));
if (ctx->thrd_ctx == NULL) {
ctx_free(ctx);
return KNOT_ENOMEM;
}
ctx->slip = knotd_conf_mod(mod, MOD_SLIP).single.integer;
ctx->dry_run = knotd_conf_mod(mod, MOD_DRY_RUN).single.boolean;
ctx->whitelist = knotd_conf_mod(mod, MOD_WHITELIST);
size_t size = knotd_conf_mod(mod, MOD_TBL_SIZE).single.integer;
uint32_t log_period = knotd_conf_mod(mod, MOD_LOG_PERIOD).single.integer;
uint32_t rate_limit = knotd_conf_mod(mod, MOD_RATE_LIMIT).single.integer;
if (rate_limit > 0) {
uint32_t inst_limit = knotd_conf_mod(mod, MOD_INST_LIMIT).single.integer;
ctx->rate_table = rrl_create(size, inst_limit, rate_limit, true, log_period);
if (ctx->rate_table == NULL) {
ctx_free(ctx);
return KNOT_ENOMEM;
}
ctx->slip = knotd_conf_mod(mod, MOD_SLIP).single.integer;
}
uint32_t time_limit = knotd_conf_mod(mod, MOD_T_RATE_LIMIT).single.integer;
if (time_limit > 0) {
uint32_t inst_limit = knotd_conf_mod(mod, MOD_T_INST_LIMIT).single.integer;
ctx->time_table = rrl_create(size, inst_limit, time_limit, false, log_period);
if (ctx->time_table == NULL) {
ctx_free(ctx);
return KNOT_ENOMEM;
}
}
int ret = knotd_mod_stats_add(mod, "slipped", 1, NULL);
if (ret != KNOT_EOK) {
......@@ -138,6 +249,11 @@ int rrl_load(knotd_mod_t *mod)
ctx_free(ctx);
return ret;
}
ret = knotd_mod_stats_add(mod, "dropped-time", 1, NULL);
if (ret != KNOT_EOK) {
ctx_free(ctx);
return ret;
}
/* The explicit reference of the AVX2 variant ensures the optimized
* code isn't removed by linker if linking statically.
......@@ -149,16 +265,23 @@ int rrl_load(knotd_mod_t *mod)
knotd_mod_ctx_set(mod, ctx);
return knotd_mod_hook(mod, KNOTD_STAGE_BEGIN, ratelimit_apply);
if (rate_limit > 0) {
knotd_mod_hook(mod, KNOTD_STAGE_BEGIN, ratelimit_apply);
}
if (time_limit > 0) {
// Note that these two callbacks aren't executed IF PER-ZONE module!
knotd_mod_proto_hook(mod, KNOTD_STAGE_PROTO_BEGIN, protolimit_start);
knotd_mod_proto_hook(mod, KNOTD_STAGE_PROTO_END, protolimit_end);
}
return KNOT_EOK;
}
void rrl_unload(knotd_mod_t *mod)
{
rrl_ctx_t *ctx = knotd_mod_ctx(mod);
knotd_conf_free(&ctx->whitelist);
ctx_free(ctx);
ctx_free(knotd_mod_ctx(mod));
}
KNOTD_MOD_API(rrl, KNOTD_MOD_FLAG_SCOPE_ANY,
KNOTD_MOD_API(rrl, KNOTD_MOD_FLAG_SCOPE_ANY | KNOTD_MOD_FLAG_OPT_CONF,
rrl_load, rrl_unload, rrl_conf, rrl_conf_check);
......@@ -4,37 +4,52 @@
================================
Response rate limiting (RRL) is a method to combat DNS reflection amplification
attacks. These attacks rely on the fact that source address of a UDP query
attacks. These attacks rely on the fact that the source address of a UDP query
can be forged, and without a worldwide deployment of `BCP38
<https://tools.ietf.org/html/bcp38>`_, such a forgery cannot be prevented.
An attacker can use a DNS server (or multiple servers) as an amplification
source and can flood a victim with a large number of unsolicited DNS responses.
The RRL lowers the amplification factor of these attacks by sending some of
the responses as truncated or by dropping them altogether.
source to flood a victim with a large number of unsolicited DNS responses.
RRL lowers the amplification factor of these attacks by sending some
responses as truncated or by dropping them altogether.
This module can also help protect the server from excessive utilization by
limiting incoming packets (including handshakes) based on consumed time.
If a packet is time rate limited, it's dropped. This function works with
all supported non-UDP transport protocols and cannot be configured per zone.
.. NOTE::
The module introduces two statistics counters. The number of slipped and
dropped responses.
This module introduces three statistics counters:
- ``slipped`` – The number of slipped UDP responses.
- ``dropped`` – The number of dropped UDP responses due to the rate limit.
- ``dropped-time`` – The number of dropped non-UDP packets due to the time rate limit.
.. NOTE::
If the :ref:`Cookies<mod-cookies>` module is active, RRL is not applied
for responses with a valid DNS cookie.
to UDP responses with a valid DNS cookie.
Example
-------
You can enable RRL by setting the module globally or per zone.
You can enable RRL by setting the module globally
::
mod-rrl:
- id: default
rate-limit: 200 # Allow 200 resp/s for each flow
slip: 2 # Approximately every other response slips
template:
- id: default
global-module: mod-rrl/default # Enable RRL globally
global-module: mod-rrl # Default module configuration
or per zone
::
mod-rrl:
- id: custom
rate-limit: 200
zone:
- domain: example.com
module: mod-rrl/custom # Custom module configuration
Module reference
----------------
......@@ -45,8 +60,10 @@ Module reference
- id: STR
rate-limit: INT
instant-limit: INT
table-size: INT
slip: INT
time-rate-limit: INT
time-instant-limit: INT
table-size: INT
whitelist: ADDR[/INT] | ADDR-ADDR | STR ...
log-period: INT
dry-run: BOOL
......@@ -63,62 +80,43 @@ A module identifier.
rate-limit
..........
Maximal allowed number of queries per second from a single IPv6 or IPv4 address.
Maximal allowed number of UDP queries per second from a single IPv6 or IPv4 address.
Rate limiting is performed for the whole address and several chosen prefixes.
The limits of prefixes are constant multiples of `rate-limit`.
The limits of prefixes are constant multiples of :ref:`mod-rrl_rate-limit`.
The specific prefixes and multipliers, which might be adjusted in the future, are
for IPv6 /128: 1, /64: 2, /56: 3, /48: 4, /32: 64;
for IPv4 /32: 1, /24: 32, /20: 256, /18: 768.
With each host/network, a counter of unrestricted responses is associated
and it is lowered by a constant fraction of its value each millisecond;
a response is restricted if a counter would exceed its capacity otherwise.
With each host/network, a counter of unrestricted responses is associated;
if the counter would exceed its capacity, it is not incremented and the response is restricted.
Counters use exponential decay for lowering their values,
i.e. they are lowered by a constant fraction of their value each millisecond.
The specified rate limit is reached, when the number of queries is the same every millisecond;
sending many queries once a second or even a larger timespan leads to a more strict limiting.
*Required*
*Default:* ``20``
.. _mod-rrl_instant-limit:
instant-limit
.............
Maximal allowed number of queries at a single point in time from a single IPv6 address.
The limits for IPv4 addresses and prefixes use the same multipliers as for `rate-limit`.
Maximal allowed number of queries at a single point in time from a single IPv6 or IPv4 address.
The limits for prefixes use the same multipliers as for :ref:`mod-rrl_rate-limit`.
This limit is reached when many queries come from a new host/network,
or after a longer time of inactivity.
The `instant-limit` sets the actual capacity of each counter of responses,
and together with the `rate-limit` they set the fraction by which the counter
The :ref:`mod-rrl_instant-limit` sets the actual capacity of each counter of responses,
and together with the :ref:`mod-rrl_rate-limit` they set the fraction by which the counter
is periodically lowered.
The `instant-limit` may be at least `rate-limit / 1000`, at which point the
The :ref:`mod-rrl_instant-limit` may be at least :ref:`mod-rrl_rate-limit` **/ 1000**, at which point the
counters are zeroed each millisecond.
*Default:* ``50``
.. _mod-rrl_table-size:
table-size
..........
Maximal number of stored hosts/networks with their counters.
The data structure tries to store only the most frequent sources and the
table size is internally a little bigger,
so it is safe to set it according to the expected maximal number of limited sources.
Use `1.4 * maximum_qps / rate-limit`,
where `maximum_qps` is the number of queries which can be handled by the server per second.
There is at most `maximum_qps / rate-limit` limited hosts;
larger networks have higher limits and so require only a fraction of the value.
The value will be rounded up to the nearest power of two.
The memory occupied by the data structure is `8 * table-size B`.
*Default:* ``524288``
.. _mod-rrl_slip:
slip
......@@ -155,6 +153,49 @@ noting, that some responses can't be truncated (e.g. SERVFAIL).
*Default:* ``1``
.. _mod-rrl_time-rate-limit:
time-rate-limit
...............
This limit works similarly to :ref:`mod-rrl_rate-limit` but considers the time
consumed (in microseconds) by the remote over non-UDP transport protocols.
*Default:* ``4000`` (microseconds)
.. _mod-rrl_time-instant-limit:
time-instant-limit
..................
This limit works similarly to :ref:`mod-rrl_instant-limit` but considers the time
consumed (in microseconds) by the remote over non-UDP transport protocols.
*Default:* ``5000`` (microseconds)
.. _mod-rrl_table-size:
table-size
..........
Maximal number of stored hosts/networks with their counters.
The data structure tries to store only the most frequent sources,
so it is safe to set it according to the expected maximal number of limited ones.
Use `1.4 * maximum_qps / rate-limit`,
where `maximum_qps` is the number of queries which can be handled by the server per second.
There is at most `maximum_qps / rate-limit` limited hosts;
larger networks have higher limits and so require only a fraction of the value (handled by the `1.4` multiplier).
The value will be rounded up to the nearest power of two.
The same table size is used for both counting-based and time-based limiting;
the maximum number of time-limited hosts is expected to be lower, so it's not typically needed to be considered.
There is at most `1 000 000 * #cpus / time-rate-limit` of them.
The memory occupied by one table structure is `8 * table-size B`.
*Default:* ``524288``
.. _mod-rrl_whitelist:
whitelist
......@@ -179,6 +220,9 @@ and logging is disabled for the `log-period` milliseconds.
As long as limiting is needed, one source is logged each period
and sources with more blocked queries have greater probability to be chosen.
The approach is used by counting-based and time-based limiting separately,
so you can expect one message per `log-period` from each of them.
*Default:* ``0`` (disabled)
.. _mod-rrl_dry-run:
......
;; Zone dump (Knot DNS 3.4.dev0+1709037013.b484eb414)
;; Zone dump (Knot DNS 3.3.5+1717750418.dbce4141d)
example.com. 3600 SOA dns1.example.com. hostmaster.example.com. 2010111215 21600 3600 604800 86400
example.com. 3600 NS dns1.example.com.
example.com. 3600 DNSKEY 256 3 13 Xw53weVKxFPqiBIzviMAZvRgdruTyHpHC3uYf9Twr9ug+cSyrYx4tteWUWPzsXgYnjATplJYY47KBMirTCR9BA==
example.com. 3600 DNSKEY 256 3 13 qvLsJ9NpDpzxzHg91N7Fj++5SU6H5DdAAGmAaS/FGYu9H93V1hfZ5a7QPrN5E8ZKhWWGc9Li/OULloYer3btZg==
example.com. 3600 DNSKEY 257 3 13 0qemQB7NsmO+Q3EnIdMPfrlTMAJxxOAjrZVHKbtfmXmd9HUsjBOg8hI6o0h9kfMb/qM/OWJYq25C5nzwl1eJGw==
dns1.example.com. 3600 A 192.0.2.1
example.com. 3600 DNSKEY 256 3 13 jKYlXyLOQri9XEqYigkpWkFb7zTx09qFtmU8CEp7LvX+7HnwdM7K/Vnm38tBb99WRWBuE4SsotBf5a8oMUy0Ag==
example.com. 3600 DNSKEY 256 3 13 5E4GzQ+53lT6zR9mKCylMcxlLT1ArW63+2HP3WqmPAat3A8lGCEntNJKmQnQ74unWs8F23vG6KzR/8n9tGBoLg==
example.com. 3600 DNSKEY 257 3 13 bLlJWYN15IWnOINWgTL/WIIMshaHRFBr1ZNlqisoBqSMem83ZdK+0VpAyRvcXAxNp0LUy9TH5Hx3NNykT+DWrA==
dns1.example.com. 3600 A 127.0.0.1
;; DNSSEC signatures
example.com. 3600 RRSIG NS 13 2 3600 20241223152039 20240227135039 50687 example.com. +0LC6BlnPpDJoBc1siE+XPmyv589lY79MH1h2YwMtY6iEd35h2CeP6tH1l9y8kLabRCREyw0/DJaRsHNet6olQ==
example.com. 3600 RRSIG NS 13 2 3600 20440222113630 20240227100630 50687 example.com. Uc5BSG2ohqfkZaIfqa3JpsUBfv5av7j2xCd9AeGb0TyfdsTJeQPxbeSFtLCeeVt0YShGjxwUldnjxqOoZxKo9Q==
example.com. 3600 RRSIG SOA 13 2 3600 20241223152039 20240227135039 50687 example.com. baS1G2mzsLinOPmg3lqhLR/9kxJFzZnw07H0PzBBIcYGEe1myGg55+ZIJ+QR5cR13jqvHltRojL9bHWbFBCL5A==
example.com. 3600 RRSIG SOA 13 2 3600 20241223152039 20240227135039 50687 example.com. bfL8KJoz1sMRLx1MXATA5fLnNJDWtQkEugiHN1THyJUfm0oVQTfbVkrnFaUSwZHiovZ/s2rri1HNOdGnCm2wAA==
example.com. 3600 RRSIG NSEC 13 2 3600 20241223152039 20240227135039 50687 example.com. 9e8McGcJuWOtJ6mYlMOcktb1yDeNFDElLhmE3ULKSeUu+Dyyaye/rlK33qPptzXDoODQKG/0CCXRyz/nRgZ70g==
example.com. 3600 RRSIG NSEC 13 2 3600 20440222113630 20240227100630 50687 example.com. akUd7/c8/sJTqB04KO3kQZwwb0i/7l8mvg10e7Somtc5nP5/gKsBpFDPGpvchZpVrlXME+eq+oRNfp3fQfBmvA==
example.com. 3600 RRSIG DNSKEY 13 2 3600 20241223152039 20240227135039 50687 example.com. W9K24FgSjnUjv2lVyrC/ddZJfBMwkiu/NQsC2BpG2Sg69RUkI5FEnwZT7g1AfJEO+QWAqjNdZ/5iep+Lhpi3ow==
dns1.example.com. 3600 RRSIG A 13 3 3600 20241223152039 20240227135039 50687 example.com. JBBpxjsn+PETEDb//cbGfyaMuoOAuHcK6dX0MpJXTHl6WgNT05hFkfsW87BrG5VUv9Ffdg9wmECaiGQ1G4LXQA==
dns1.example.com. 3600 RRSIG A 13 3 3600 20440222113630 20240227100630 50687 example.com. Y8vvqbd55jo7zKx0uDVwaWScpGqti8sPXodjItsrN8gjA3d3A+7TVW71L9FSwww/Usl8cyXIC4ZUR5c7JIFNHQ==
dns1.example.com. 3600 RRSIG NSEC 13 3 3600 20241223152039 20240227135039 50687 example.com. Kn7GEREsUJqmgNyUKPhmlm1PRzIlhP2pORHCw+x699aDv/xtVprIT2d73SUWKW0PoCSU7SecsKiLPYx/t9ymIw==
dns1.example.com. 3600 RRSIG NSEC 13 3 3600 20440222113630 20240227100630 50687 example.com. Opl4Clj6bRS/iTsJdEpJMX76XAGVt/OJQS3hRdpwDROdaskd69ENPPtdnOR0aNlG6NeMBXzH8HkMOIDv6k50kg==
example.com. 3600 RRSIG NS 13 2 3600 20440814105153 20240819092153 8746 example.com. /ThrfNIP7sega/pbpnvkmmaRwzzVs+dxd34g0GMWtOXUXiSTBsDn2JLXogt9MZM98hyf9JWpjIhKtq8BJ2m8Ug==
example.com. 3600 RRSIG SOA 13 2 3600 20440814105153 20240819092153 8746 example.com. 9u55SiuKcXCHv5qXdjS8SG3I/b7eyDvlKhxo9Vkjb0zqZ90MtjK4Egu+j6Q7uhbGFbYqhilFO36vfhnH7gjn5g==
example.com. 3600 RRSIG NSEC 13 2 3600 20440814105153 20240819092153 8746 example.com. Oo4+DRECB5iwh0iHtgdsUKJqoVKibzDlwkoVzCBW6SiWNZG6u29lkIOn3NZrae9QHkDitBlLwB0+HkgV6US9ng==
example.com. 3600 RRSIG DNSKEY 13 2 3600 20440814105153 20240819092153 8746 example.com. tyXZXQm725nL/jDvbtuDjD4wMzM3Dv98TQmVn0xFnbmW950Vn2CpTOv2ZVyvWtOOFZUx7oh1j0LoZiXm/J0uKg==
dns1.example.com. 3600 RRSIG A 13 3 3600 20440814105153 20240819092153 8746 example.com. sfOJTECqDkVimZGLOAOT+TnuwF+ezLgA1sE/fPnrnV/0TZmYVojhTYFocvkhMlN1vTAC+U60X9WOw/IjjYh4Nw==
dns1.example.com. 3600 RRSIG NSEC 13 3 3600 20440814105153 20240819092153 8746 example.com. nY3y3MDThe2htlGGXVItiP+GYr2FAe8uRkgZF/WoEeyANRwydP91hZI4Ms7EPEApnHqOZtZ5fuElOHhflRsUfA==
;; DNSSEC NSEC chain
example.com. 3600 NSEC dns1.example.com. NS SOA RRSIG NSEC DNSKEY
dns1.example.com. 3600 NSEC example.com. A RRSIG NSEC
;; Written 19 records
;; Time 2024-02-27 16:20:39 CET
;; Written 14 records
;; Time 2024-08-19 13:01:16 CEST
......@@ -557,7 +557,7 @@ static knot_dname_t *tm_owner(const char *prefix, const knot_dname_t *apex)
static knot_dname_t *tm_owner_int(int x, const knot_dname_t *apex)
{
char buf[12] = { 0 };
char buf[13] = { 0 };
(void)snprintf(buf, sizeof(buf), "i%d", x);
return tm_owner(buf, apex);
}
......
......@@ -32,11 +32,6 @@ int fakeclock_gettime(clockid_t clockid, struct timespec *tp);
#include "knot/modules/rrl/functions.c"
#undef clock_gettime
#define RRL_TABLE_SIZE (1 << 20)
#define RRL_INSTANT_LIMIT (1 << 8)
#define RRL_RATE_LIMIT (1 << 17)
#define RRL_BASE_PRICE (KRU_LIMIT / RRL_INSTANT_LIMIT)
#define RRL_THREADS 4
//#define RRL_SYNC_WITH_REAL_TIME
......@@ -57,14 +52,17 @@ static inline kru_price_t get_mult(uint8_t prefixes[], kru_price_t mults[], size
return 0;
}
// Macro correction depending on the table mode.
int DIFF = 0;
// Instant limits and rate limits per msec.
#define INST(Vx, prefix) LIMIT(INSTANT, Vx, prefix)
#define RATEM(Vx, prefix) (LIMIT(RATE, Vx, prefix) / 1000)
#define INST(Vx, prefix) (LIMIT(INSTANT, Vx, prefix) + DIFF)
#define RATEM(Vx, prefix) (LIMIT(RATE, Vx, prefix) / 1000 + DIFF)
// Expected range of limits for parallel test.
#define RANGE_INST(Vx, prefix) INST(Vx, prefix) - 1, INST(Vx, prefix) + RRL_THREADS - 1
#define RANGE_RATEM(Vx, prefix) RATEM(Vx, prefix) - 1, RATEM(Vx, prefix)
#define RANGE_UNLIM(queries) queries, queries
#define RANGE_INST(Vx, prefix) INST(Vx, prefix) - 1, INST(Vx, prefix) + RRL_THREADS - 1
#define RANGE_RATEM(Vx, prefix) RATEM(Vx, prefix) - 1 - DIFF, RATEM(Vx, prefix) + RRL_THREADS - DIFF
#define RANGE_UNLIM(queries) queries, queries
/* Fix seed for randomness in RLL module. Change if improbable collisions arise. (one byte) */
#define RRL_SEED_GENERIC 1
......@@ -172,6 +170,9 @@ static void *rrl_runnable(void *arg)
if (rrl_query(d->rrl, &addr, NULL) == KNOT_EOK) {
atomic_fetch_add(&d->stages[si].hosts[hi].passed, 1);
if (!d->rrl->rw_mode) {
rrl_update(d->rrl, &addr, 1);
}
}
} while ((qi2 = (qi2 + d->prime) % (1 << BATCH_QUERIES_LOG)));
......@@ -200,6 +201,9 @@ void count_test(char *desc, int expected_passing, double margin_fract,
cnt = i;
break;
}
if (!rrl->rw_mode) {
rrl_update(rrl, &addr, 1);
}
}
if (expected_passing < 0) expected_passing = -1;
......@@ -213,12 +217,20 @@ void count_test(char *desc, int expected_passing, double margin_fract,
}
}
void test_rrl(void)
void test_rrl(bool rw_mode)
{
size_t RRL_TABLE_SIZE = (1 << 20);
uint32_t RRL_INSTANT_LIMIT = (1 << 7);
uint32_t RRL_RATE_LIMIT = (1 << 16);
if (rw_mode) {
RRL_INSTANT_LIMIT = (1 << 8);
RRL_RATE_LIMIT = (1 << 17);
}
fakeclock_init();
/* create rrl table */
rrl = rrl_create(RRL_TABLE_SIZE, RRL_INSTANT_LIMIT, RRL_RATE_LIMIT, 0);
rrl = rrl_create(RRL_TABLE_SIZE, RRL_INSTANT_LIMIT, RRL_RATE_LIMIT, rw_mode, 0);
ok(rrl != NULL, "rrl(%s): create", impl_name);
assert(rrl);
......@@ -276,7 +288,7 @@ void test_rrl(void)
count_test("IPv6 instant limit /64 not applied on /63", -1, 0,
AF_INET6, "8000:0:0:1::", 0, 0);
count_test("IPv6 instant limit /56", INST(V6, 56) - INST(V6, 64) - 1, 0,
count_test("IPv6 instant limit /56", INST(V6, 56) - INST(V6, 64) - 1, rw_mode ? 0 : 0.01,
AF_INET6, "8000:0:0:00%02x:%02x00::", 0x02, 0xff);
count_test("IPv6 instant limit /56 not applied on /55", -1, 0,
......@@ -288,7 +300,7 @@ void test_rrl(void)
count_test("IPv6 instant limit /48 not applied on /47", -1, 0,
AF_INET6, "8000:0:1::", 0, 0);
count_test("IPv6 instant limit /32", INST(V6, 32) - INST(V6, 48) - 1, 0.001,
count_test("IPv6 instant limit /32", INST(V6, 32) - INST(V6, 48) - 1, rw_mode ? 0.001 : 0,
AF_INET6, "8000:0:%02x%02x::", 0x02, 0xff);
count_test("IPv6 instant limit /32 not applied on /31", -1, 0,
......@@ -370,24 +382,36 @@ void test_rrl(void)
rrl_destroy(rrl);
}
int main(int argc, char *argv[])
void test_rrl_mode(bool test_avx2, bool rw_mode)
{
plan_lazy();
dnssec_crypto_init();
assert(KRU_GENERIC.initialize != KRU_AVX2.initialize);
bool test_avx2 = (KRU.initialize == KRU_AVX2.initialize);
if (!rw_mode) {
DIFF = 1;
}
KRU = KRU_GENERIC;
impl_name = "KRU_GENERIC";
test_rrl();
test_rrl(rw_mode);
if (test_avx2) {
KRU = KRU_AVX2;
impl_name = "KRU_AVX2";
test_rrl();
test_rrl(rw_mode);
} else {
diag("AVX2 NOT available");
}
}
int main(int argc, char *argv[])
{
plan_lazy();
dnssec_crypto_init();
assert(KRU_GENERIC.initialize != KRU_AVX2.initialize);
bool test_avx2 = (KRU.initialize == KRU_AVX2.initialize);
test_rrl_mode(test_avx2, true);
test_rrl_mode(test_avx2, false);
dnssec_crypto_cleanup();
return 0;
......