diff --git a/Knot.files b/Knot.files index 37dc52ae8f93d784d6c35e9201f11d27a78d1b1e..c968d91a053fa04cf2e5cfde039c67eb813e0523 100644 --- a/Knot.files +++ b/Knot.files @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 44586152e4360622c6fce346cadb6a0bcb8b3b63..963364dddd8e6267004915e38db4e9daa1c2ca3e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/common/errcode.c b/src/common/errcode.c index ff9be63ec78714505249f1f319df82e96f9798e3..75c4e1388a332a7f492e61544dac957b54a1f6f2 100644 --- a/src/common/errcode.c +++ b/src/common/errcode.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} }; diff --git a/src/common/errcode.h b/src/common/errcode.h index 0693a0d086048e44b122bafb18d1ba4ca4ff6dea..b2afae5d43b5a692fea2166dc2748c9d8d3e1c80 100644 --- a/src/common/errcode.h +++ b/src/common/errcode.h @@ -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. */ diff --git a/src/knot/other/debug.h b/src/knot/other/debug.h index 1a8698e580624ccdaf7e9cf497fb51ddc061b1ca..c88e166837c60fbbee8118fc03e477c6ab953b1a 100644 --- a/src/knot/other/debug.h +++ b/src/knot/other/debug.h @@ -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. */ diff --git a/src/knot/server/rrl.c b/src/knot/server/rrl.c new file mode 100644 index 0000000000000000000000000000000000000000..504f69a5c75076555af1237508d74bcc53bfed90 --- /dev/null +++ b/src/knot/server/rrl.c @@ -0,0 +1,183 @@ +/* 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; +} diff --git a/src/knot/server/rrl.h b/src/knot/server/rrl.h new file mode 100644 index 0000000000000000000000000000000000000000..ff46363b148bffa4210e478296900b103db37bf0 --- /dev/null +++ b/src/knot/server/rrl.h @@ -0,0 +1,60 @@ +/* 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_ */ + +/*! @} */ diff --git a/src/tests/knot/rrl_tests.c b/src/tests/knot/rrl_tests.c new file mode 100644 index 0000000000000000000000000000000000000000..845004cb255b13944afd6d42539dd17817ffbdf6 --- /dev/null +++ b/src/tests/knot/rrl_tests.c @@ -0,0 +1,75 @@ +/* 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; +} diff --git a/src/tests/knot/rrl_tests.h b/src/tests/knot/rrl_tests.h new file mode 100644 index 0000000000000000000000000000000000000000..447b735d0be7af56ce08dbbc12b00c59d9b04166 --- /dev/null +++ b/src/tests/knot/rrl_tests.h @@ -0,0 +1,25 @@ +/* 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_ */ diff --git a/src/tests/unittests_main.c b/src/tests/unittests_main.c index 17ea3b41d8d378ccf7a0ec56409bdf7591a47cfa..bd89c0530d2aa3765918d50e068a956759b065f2 100644 --- a/src/tests/unittests_main.c +++ b/src/tests/unittests_main.c @@ -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 };