diff options
76 files changed, 1433 insertions, 496 deletions
@@ -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/Makefile.in b/Makefile.in index 934681a..21d7df3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.12.4 from Makefile.am. +# Makefile.in generated by automake 1.12.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2012 Free Software Foundation, Inc. @@ -1,5 +1,15 @@ -v1.2-rc2 - Feb 15, 2013 -------------------- +v1.2.0-rc3 - Mar 1, 2013 +------------------------ + +Features: + * Response rate limiting (see documentation) + +Bugfixes: + * Fixed OpenBSD build + * Responses to ANY should contain RRSIGs + +v1.2.0-rc2 - Feb 15, 2013 +------------------------- Bugfixes: * Fixed processing of some non-standard dnames. @@ -7,7 +17,7 @@ Bugfixes: * More compliant rcodes in case of DDNS/TSIG failures. * Correct processing of malformed DDNS prereq section. -v1.2-rc1 - Jan 4, 2013 +v1.2.0-rc1 - Jan 4, 2013 ------------------ Features: @@ -1,4 +1,4 @@ -# generated automatically by aclocal 1.12.4 -*- Autoconf -*- +# generated automatically by aclocal 1.12.6 -*- Autoconf -*- # Copyright (C) 1996-2012 Free Software Foundation, Inc. @@ -34,7 +34,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.12' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.12.4], [], +m4_if([$1], [1.12.6], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -50,7 +50,7 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.12.4])dnl +[AM_AUTOMAKE_VERSION([1.12.6])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for knot 1.2-rc2. +# Generated by GNU Autoconf 2.69 for knot 1.2.0-rc3. # # Report bugs to <knot-dns@labs.nic.cz>. # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='knot' PACKAGE_TARNAME='knot' -PACKAGE_VERSION='1.2-rc2' -PACKAGE_STRING='knot 1.2-rc2' +PACKAGE_VERSION='1.2.0-rc3' +PACKAGE_STRING='knot 1.2.0-rc3' PACKAGE_BUGREPORT='knot-dns@labs.nic.cz' PACKAGE_URL='' @@ -1319,7 +1319,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures knot 1.2-rc2 to adapt to many kinds of systems. +\`configure' configures knot 1.2.0-rc3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1389,7 +1389,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of knot 1.2-rc2:";; + short | recursive ) echo "Configuration of knot 1.2.0-rc3:";; esac cat <<\_ACEOF @@ -1509,7 +1509,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -knot configure 1.2-rc2 +knot configure 1.2.0-rc3 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2062,7 +2062,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by knot $as_me 1.2-rc2, which was +It was created by knot $as_me 1.2.0-rc3, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2889,7 +2889,7 @@ fi # Define the identity of the package. PACKAGE='knot' - VERSION='1.2-rc2' + VERSION='1.2.0-rc3' cat >>confdefs.h <<_ACEOF @@ -14068,7 +14068,7 @@ fi done -for ac_header in arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h sys/select.h sys/wait.h sys/stat.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h +for ac_header in arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h sys/select.h sys/wait.h sys/stat.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h signal.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -15342,7 +15342,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by knot $as_me 1.2-rc2, which was +This file was extended by knot $as_me 1.2.0-rc3, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -15408,7 +15408,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -knot config.status 1.2-rc2 +knot config.status 1.2.0-rc3 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 7a723e5..f593068 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # -*- Autoconf -*- AC_PREREQ([2.60]) -AC_INIT([knot], [1.2-rc2], [knot-dns@labs.nic.cz]) +AC_INIT([knot], [1.2.0-rc3], [knot-dns@labs.nic.cz]) AM_INIT_AUTOMAKE([gnu -Wall -Werror]) AC_CONFIG_SRCDIR([src/knot/main.c]) AC_CONFIG_HEADERS([src/config.h]) @@ -151,7 +151,7 @@ AC_SEARCH_LIBS([adler32], [z]) # Checks for header files. AC_HEADER_RESOLV -AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h sys/select.h sys/wait.h sys/stat.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h]) +AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h sys/select.h sys/wait.h sys/stat.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h signal.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL @@ -74,6 +74,9 @@ tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} rm -f "$tmpdepfile" +# Avoid interferences from the environment. +gccflag= dashmflag= + # Some modes work just like other modes, but use different flags. We # parameterize here, but still list the modes in the big case below, # to make depend.m4 easier to write. Note that we *cannot* use a case @@ -108,7 +111,7 @@ if test "$depmode" = msvc7msys; then fi if test "$depmode" = xlc; then - # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency informations. + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. gccflag=-qmakedep=gcc,-MF depmode=gcc fi @@ -142,13 +145,17 @@ gcc3) ;; gcc) +## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. +## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. +## (see the conditional assignment to $gccflag above). ## There are various ways to get dependency output from gcc. Here's ## why we pick this rather obscure method: ## - Don't want to use -MD because we'd like the dependencies to end ## up in a subdir. Having to rename by hand is ugly. ## (We might end up doing this anyway to support other compilers.) ## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like -## -MM, not -M (despite what the docs say). +## -MM, not -M (despite what the docs say). Also, it might not be +## supported by the other compilers which use the 'gcc' depmode. ## - Using -M directly means running the compiler twice (even worse ## than renaming). if test -z "$gccflag"; then diff --git a/doc/Makefile.in b/doc/Makefile.in index d11a23d..779e241 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.12.4 from Makefile.am. +# Makefile.in generated by automake 1.12.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2012 Free Software Foundation, Inc. diff --git a/doc/configuration.texi b/doc/configuration.texi index ae5a4a1..b979c38 100644 --- a/doc/configuration.texi +++ b/doc/configuration.texi @@ -12,6 +12,7 @@ In this chapter we provide suggested configurations and explain the meaning of i * Remote control interface:: * Enabling zone semantic checks:: * Creating IXFR differences from zone file changes:: +* Using Response Rate Limiting:: @end menu @node Minimal configuration @@ -256,3 +257,31 @@ If Knot is being run as a master server, experimental feature @code{ixfr-from-di can be enabled to create IXFR differences from changes made to the master zone file. See @ref{Controlling running daemon} for more information. For more about @code{zones} statement see @ref{zones}. +@node Using Response Rate Limiting +@section Using Response Rate Limiting + +Response rate limiting (RRL) is a method to combat recent DNS reflection amplification attacks. +These attacked rely on the fact that source address of a UDP query could be forged, +and without a worldwide deployment of BCP38, such a forgery could not be detected. +Attacker could then exploit DNS server responding to every query, potentially flooding the +victim with a large unsolicited DNS responses. + +As of Knot DNS version 1.2.0, RRL is compiled in, but disabled by default. +You can enable it with the @ref{rate-limit} option in the @ref{system} section. +Setting to a value greater than @code{0} means that every flow is allowed N responses per second, +(i.e. @code{rate-limit 50;} means @code{50} responses per second). +It is also possible to configure SLIP interval, which causes every Nth blocked response to be slipped +as a truncated response. Not that some error responses cannot be truncated and are slipped as-is. +For more information, refer to @ref{rate-limit-slip}. +It is advisable to not set slip interval to a value larger than 2, to allow legitimate clients +get at least some level of service. + +Example configuration: +@example +system @{ + rate-limit 200; # Each flow is allowed to 200 resp. per second + rate-limit-slip 2; # Every other response is slipped (default) +@} +@end example + + diff --git a/doc/introduction.texi b/doc/introduction.texi index 4217001..3644636 100644 --- a/doc/introduction.texi +++ b/doc/introduction.texi @@ -38,7 +38,6 @@ Knot DNS supports the following DNS features: @item Unknown RR types @end itemize -@* Server features: @itemize @@ -48,7 +47,6 @@ Server features: @item Semantic checks of zones @end itemize -@* For more info and downloads see @url{http://www.knot-dns.cz, www.knot-dns.cz}. diff --git a/doc/knot.texi b/doc/knot.texi index b0929ae..04e81b5 100644 --- a/doc/knot.texi +++ b/doc/knot.texi @@ -171,6 +171,12 @@ Statement Definition and Usage * pidfile:: * workers:: * user:: +* max-conn-idle:: +* max-conn-hs:: +* max-conn-reply:: +* rate-limit:: +* rate-limit-size:: +* rate-limit-slip:: @code{keys} Statement diff --git a/doc/reference.texi b/doc/reference.texi index f28608d..2d9ac7e 100644 --- a/doc/reference.texi +++ b/doc/reference.texi @@ -42,6 +42,9 @@ else. [ @code{max-conn-idle} ( @kbd{integer} | @kbd{integer}(@code{s} | @code{m} | @code{h} | @code{d})@code{;} ) ] [ @code{max-conn-hs} ( @kbd{integer} | @kbd{integer}(@code{s} | @code{m} | @code{h} | @code{d})@code{;} ) ] [ @code{max-conn-reply} ( @kbd{integer} | @kbd{integer}(@code{s} | @code{m} | @code{h} | @code{d})@code{;} ) ] + [ @code{rate-limit} @kbd{integer}@code{;} ] + [ @code{rate-limit-size} ( @kbd{integer} | @kbd{integer}(@code{s} | @code{m} | @code{h} | @code{d})@code{;} ) ] + [ @code{rate-limit-slip} @kbd{integer}@code{;} ] @code{@}} @end example @@ -56,9 +59,12 @@ else. * pidfile:: * workers:: * user:: -* max-conn-idle:: -* max-conn-hs:: -* max-conn-reply:: +* max-conn-idle:: +* max-conn-hs:: +* max-conn-reply:: +* rate-limit:: +* rate-limit-size:: +* rate-limit-slip:: @end menu @node identity @@ -178,6 +184,38 @@ that already made at least 1 meaningful query. Maximum time to wait for a reply to an issued SOA query. +@node rate-limit +@subsubsection rate-limit +@vindex rate-limit + +Rate limiting is based on a token bucket scheme, rate basically represents number of tokens available each second. +Each response is processed and classified (based on a several discriminators, f.e. source netblock, qtype, name, rcode, etc.). +Classified responses are then hashed and assigned to a bucket containing number of available tokens, timestamp and metadata. +When available tokens are exhausted, response is rejected or enters SLIP (server responds with a truncated response). +Number of available tokens is recalculated each second. + +Default value: @kbd{0 (disabled)} + +@node rate-limit-size +@subsubsection rate-limit-size +@vindex rate-limit-size + +Option controls the size of a hashtable of buckets. The larger the hashtable, the lesser probability of a hash collision, but +at the expense of additional memory costs. Each bucket is estimated roughly to 16B. +Size should be selected as a reasonably large prime due to the better hash function distribution properties. + +Default value: @kbd{1572869} + +@node rate-limit-slip +@subsubsection rate-limit-slip +@vindex rate-limit-slip + +As attacks using DNS/UDP are usually based on a forged source address, an attacker could deny services to the victim netblock +if all responses would be completely blocked. The idea behind SLIP mechanism is to send each Nth response as truncated, thus allowing +client to reconnect via TCP for at least some degree of service. It is worth noting, that some responses can't be truncated (f.e. SERVFAIL). + +Default value: @kbd{2} + @node system Example @subsection system Example diff --git a/doc/texinfo.tex b/doc/texinfo.tex index f458ba7..b5f3141 100644 --- a/doc/texinfo.tex +++ b/doc/texinfo.tex @@ -3,7 +3,7 @@ % Load plain if necessary, i.e., if running under initex. \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi % -\def\texinfoversion{2012-09-04.17} +\def\texinfoversion{2012-11-08.11} % % Copyright 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, % 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, @@ -2272,8 +2272,6 @@ end \gdef\markupsetcodequoteleft{\let`\codequoteleft} \gdef\markupsetcodequoteright{\let'\codequoteright} - -\gdef\markupsetnoligaturesquoteleft{\let`\noligaturesquoteleft} } \let\markupsetuplqcode \markupsetcodequoteleft @@ -2282,6 +2280,9 @@ end \let\markupsetuplqexample \markupsetcodequoteleft \let\markupsetuprqexample \markupsetcodequoteright % +\let\markupsetuplqkbd \markupsetcodequoteleft +\let\markupsetuprqkbd \markupsetcodequoteright +% \let\markupsetuplqsamp \markupsetcodequoteleft \let\markupsetuprqsamp \markupsetcodequoteright % @@ -2291,8 +2292,6 @@ end \let\markupsetuplqverbatim \markupsetcodequoteleft \let\markupsetuprqverbatim \markupsetcodequoteright -\let\markupsetuplqkbd \markupsetnoligaturesquoteleft - % Allow an option to not use regular directed right quote/apostrophe % (char 0x27), but instead the undirected quote from cmtt (char 0x0d). % The undirected quote is ugly, so don't make it the default, but it @@ -2382,8 +2381,7 @@ end \aftersmartic } -% like \smartslanted except unconditionally uses \ttsl, and no ic. -% @var is set to this for defun arguments. +% Unconditional use \ttsl, and no ic. @var is set to this for defuns. \def\ttslanted#1{{\ttsl #1}} % @cite is like \smartslanted except unconditionally use \sl. We never want @@ -2695,10 +2693,6 @@ end \let\email=\uref \fi -% @kbd is like @code, except that if the argument is just one @key command, -% then @kbd has no effect. -\def\kbd#1{{\setupmarkupstyle{kbd}\def\look{#1}\expandafter\kbdfoo\look??\par}} - % @kbdinputstyle -- arg is `distinct' (@kbd uses slanted tty font always), % `example' (@kbd uses ttsl only inside of @example and friends), % or `code' (@kbd uses normal tty font always). @@ -2722,11 +2716,17 @@ end % Default is `distinct'. \kbdinputstyle distinct +% @kbd is like @code, except that if the argument is just one @key command, +% then @kbd has no effect. +\def\kbd#1{{\def\look{#1}\expandafter\kbdsub\look??\par}} + \def\xkey{\key} -\def\kbdfoo#1#2#3\par{\def\one{#1}\def\three{#3}\def\threex{??}% -\ifx\one\xkey\ifx\threex\three \key{#2}% -\else{\tclose{\kbdfont\setupmarkupstyle{kbd}\look}}\fi -\else{\tclose{\kbdfont\setupmarkupstyle{kbd}\look}}\fi} +\def\kbdsub#1#2#3\par{% + \def\one{#1}\def\three{#3}\def\threex{??}% + \ifx\one\xkey\ifx\threex\three \key{#2}% + \else{\tclose{\kbdfont\setupmarkupstyle{kbd}\look}}\fi + \else{\tclose{\kbdfont\setupmarkupstyle{kbd}\look}}\fi +} % definition of @key that produces a lozenge. Doesn't adjust to text size. %\setfont\keyrm\rmshape{8}{1000}{OT1} @@ -3272,7 +3272,8 @@ end % Settings used for typesetting titles: no hyphenation, no indentation, % don't worry much about spacing, ragged right. This should be used % inside a \vbox, and fonts need to be set appropriately first. Because -% it is always used for titles, nothing else, we call \rmisbold. +% it is always used for titles, nothing else, we call \rmisbold. \par +% should be specified before the end of the \vbox, since a vbox is a group. % \def\raggedtitlesettings{% \rmisbold @@ -3289,7 +3290,7 @@ end \parseargdef\title{% \checkenv\titlepage - \vbox{\titlefonts \raggedtitlesettings #1} + \vbox{\titlefonts \raggedtitlesettings #1\par}% % print a rule at the page bottom also. \finishedtitlepagefalse \vskip4pt \hrule height 4pt width \hsize \vskip4pt @@ -4256,7 +4257,7 @@ end } \def\ifcmddefinedfail{\doignore{ifcommanddefined}} -% @ifcommandnotdefined CMD ... handlded similar to @ifclear above. +% @ifcommandnotdefined CMD ... handled similar to @ifclear above. \makecond{ifcommandnotdefined} \def\ifcommandnotdefined{% \parsearg{\doifcmddefined{\else \let\next=\ifcmdnotdefinedfail}}} @@ -5592,14 +5593,6 @@ end % Define @majorheading, @heading and @subheading -% NOTE on use of \vbox for chapter headings, section headings, and such: -% 1) We use \vbox rather than the earlier \line to permit -% overlong headings to fold. -% 2) \hyphenpenalty is set to 10000 because hyphenation in a -% heading is obnoxious; this forbids it. -% 3) Likewise, headings look best if no \parindent is used, and -% if justification is not attempted. Hence \raggedright. - \def\majorheading{% {\advance\chapheadingskip by 10pt \chapbreak }% \parsearg\chapheadingzzz @@ -5607,10 +5600,8 @@ end \def\chapheading{\chapbreak \parsearg\chapheadingzzz} \def\chapheadingzzz#1{% - {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 - \parindent=0pt\ptexraggedright - \rmisbold #1\hfill}}% - \bigskip \par\penalty 200\relax + \vbox{\chapfonts \raggedtitlesettings #1\par}% + \nobreak\bigskip \nobreak \suppressfirstparagraphindent } @@ -5769,8 +5760,7 @@ end % % Typeset the actual heading. \nobreak % Avoid page breaks at the interline glue. - \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \ptexraggedright - \hangindent=\wd0 \centerparametersmaybe + \vbox{\raggedtitlesettings \hangindent=\wd0 \centerparametersmaybe \unhbox0 #1\par}% }% \nobreak\bigskip % no page break after a chapter title @@ -5792,18 +5782,18 @@ end \def\setchapterstyle #1 {\csname CHAPF#1\endcsname} % \def\unnchfopen #1{% -\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 - \parindent=0pt\ptexraggedright - \rmisbold #1\hfill}}\bigskip \par\nobreak + \chapoddpage + \vbox{\chapfonts \raggedtitlesettings #1\par}% + \nobreak\bigskip\nobreak } \def\chfopen #1#2{\chapoddpage {\chapfonts \vbox to 3in{\vfil \hbox to\hsize{\hfil #2} \hbox to\hsize{\hfil #1} \vfil}}% \par\penalty 5000 % } \def\centerchfopen #1{% -\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 - \parindent=0pt - \hfill {\rmisbold #1}\hfill}}\bigskip \par\nobreak + \chapoddpage + \vbox{\chapfonts \raggedtitlesettings \hfill #1\hfill}% + \nobreak\bigskip \nobreak } \def\CHAPFopen{% \global\let\chapmacro=\chfopen @@ -6569,16 +6559,9 @@ end \makedispenvdef{quotation}{\quotationstart} % \def\quotationstart{% - {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip - \parindent=0pt - % - % @cartouche defines \nonarrowing to inhibit narrowing at next level down. + \indentedblockstart % same as \indentedblock, but increase right margin too. \ifx\nonarrowing\relax - \advance\leftskip by \lispnarrowing \advance\rightskip by \lispnarrowing - \exdentamount = \lispnarrowing - \else - \let\nonarrowing = \relax \fi \parsearg\quotationlabel } @@ -6604,6 +6587,32 @@ end \fi } +% @indentedblock is like @quotation, but indents only on the left and +% has no optional argument. +% +\makedispenvdef{indentedblock}{\indentedblockstart} +% +\def\indentedblockstart{% + {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip + \parindent=0pt + % + % @cartouche defines \nonarrowing to inhibit narrowing at next level down. + \ifx\nonarrowing\relax + \advance\leftskip by \lispnarrowing + \exdentamount = \lispnarrowing + \else + \let\nonarrowing = \relax + \fi +} + +% Keep a nonzero parskip for the environment, since we're doing normal filling. +% +\def\Eindentedblock{% + \par + {\parskip=0pt \afterenvbreak}% +} +\def\Esmallindentedblock{\Eindentedblock} + % LaTeX-like @verbatim...@end verbatim and @verb{<char>...<char>} % If we want to allow any <char> as delimiter, @@ -7082,7 +7091,10 @@ end \df \sl \hyphenchar\font=0 % % On the other hand, if an argument has two dashes (for instance), we - % want a way to get ttsl. Let's try @var for that. + % want a way to get ttsl. We used to recommend @var for that, so + % leave the code in, but it's strange for @var to lead to typewriter. + % Nowadays we recommend @code, since the difference between a ttsl hyphen + % and a tt hyphen is pretty tiny. @code also disables ?` !`. \def\var##1{{\setupmarkupstyle{var}\ttslanted{##1}}}% #1% \sl\hyphenchar\font=45 diff --git a/samples/Makefile.in b/samples/Makefile.in index a9402a6..44b12ae 100644 --- a/samples/Makefile.in +++ b/samples/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.12.4 from Makefile.am. +# Makefile.in generated by automake 1.12.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2012 Free Software Foundation, Inc. diff --git a/samples/knot.full.conf b/samples/knot.full.conf index 8b4571e..f6a9d89 100644 --- a/samples/knot.full.conf +++ b/samples/knot.full.conf @@ -60,6 +60,26 @@ system { # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day # Default: 10s max-conn-reply 10s; + + # Rate limit + # in queries / second + # Default: off (=0) + rate-limit 0; + + # Rate limit bucket size + # Number of hashtable buckets, set to reasonable value as default. + # We chose a reasonably large prime number as it's used for hashtable size, + # it is recommended to do so as well due to better distribution. + # Tweak if you experience a lot of hash collisions, estimated memory overhead + # is approx. 16B per bucket + # Default: 1572869 + rate-limit-size 1572869; + + # Rate limit SLIP + # Each Nth blocked response will be sent as truncated, this is a way to allow + # legitimate requests to get a chance to reconnect using TCP + # Default: 2 + rate-limit-slip 2; } # Section 'keys' contains list of TSIG keys diff --git a/src/Makefile.am b/src/Makefile.am index 4458615..963364d 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/Makefile.in b/src/Makefile.in index 7c00c90..4991a28 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.12.4 from Makefile.am. +# Makefile.in generated by automake 1.12.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2012 Free Software Foundation, Inc. @@ -88,12 +88,12 @@ libknotd_la_DEPENDENCIES = libknot.la libknots.la @LIBOBJS@ am_libknotd_la_OBJECTS = gatherer.lo stat.lo libknotd_la-cf-parse.lo \ libknotd_la-cf-lex.lo conf.lo logconf.lo process.lo remote.lo \ dthreads.lo journal.lo socket.lo server.lo udp-handler.lo \ - tcp-handler.lo xfr-handler.lo zones.lo notify.lo zone-load.lo \ - semantic-check.lo zone-dump.lo zone-dump-text.lo + tcp-handler.lo xfr-handler.lo zones.lo notify.lo rrl.lo \ + zone-load.lo semantic-check.lo zone-dump.lo zone-dump-text.lo libknotd_la_OBJECTS = $(am_libknotd_la_OBJECTS) libknots_la_DEPENDENCIES = @LIBOBJS@ -am_libknots_la_OBJECTS = slab.lo tap.lo mempattern.lo lists.lo \ - base64.lo heap.lo print.lo skip-list.lo base32hex.lo \ +am_libknots_la_OBJECTS = murmurhash3.lo slab.lo tap.lo mempattern.lo \ + lists.lo base64.lo heap.lo print.lo skip-list.lo base32hex.lo \ general-tree.lo evqueue.lo evsched.lo acl.lo sockaddr.lo \ ref.lo errors.lo errcode.lo dSFMT.lo prng.lo fdset.lo \ fdset_poll.lo fdset_kqueue.lo fdset_epoll.lo log.lo @@ -119,7 +119,8 @@ am_unittests_OBJECTS = acl_tests.$(OBJEXT) base32hex_tests.$(OBJEXT) \ skiplist_tests.$(OBJEXT) slab_tests.$(OBJEXT) \ fdset_tests.$(OBJEXT) conf_tests.$(OBJEXT) \ dthreads_tests.$(OBJEXT) journal_tests.$(OBJEXT) \ - server_tests.$(OBJEXT) unittests_main.$(OBJEXT) + server_tests.$(OBJEXT) rrl_tests.$(OBJEXT) \ + unittests_main.$(OBJEXT) nodist_unittests_OBJECTS = unittests_OBJECTS = $(am_unittests_OBJECTS) \ $(nodist_unittests_OBJECTS) @@ -426,6 +427,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 = \ @@ -564,6 +567,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 \ @@ -653,6 +658,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 \ @@ -919,6 +926,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logconf.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mempattern.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/murmurhash3.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/name-server.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node_tests.Po@am__quote@ @@ -944,6 +952,8 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response_tests.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response_tests_realdata.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrl_tests.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrset.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrset_tests.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rrset_tests_realdata.Po@am__quote@ @@ -1315,6 +1325,13 @@ notify.lo: knot/server/notify.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o notify.lo `test -f 'knot/server/notify.c' || echo '$(srcdir)/'`knot/server/notify.c +rrl.lo: knot/server/rrl.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrl.lo -MD -MP -MF $(DEPDIR)/rrl.Tpo -c -o rrl.lo `test -f 'knot/server/rrl.c' || echo '$(srcdir)/'`knot/server/rrl.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrl.Tpo $(DEPDIR)/rrl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/server/rrl.c' object='rrl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrl.lo `test -f 'knot/server/rrl.c' || echo '$(srcdir)/'`knot/server/rrl.c + zone-load.lo: knot/zone/zone-load.c @am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT zone-load.lo -MD -MP -MF $(DEPDIR)/zone-load.Tpo -c -o zone-load.lo `test -f 'knot/zone/zone-load.c' || echo '$(srcdir)/'`knot/zone/zone-load.c @am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/zone-load.Tpo $(DEPDIR)/zone-load.Plo @@ -1343,6 +1360,13 @@ zone-dump-text.lo: knot/zone/zone-dump-text.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o zone-dump-text.lo `test -f 'knot/zone/zone-dump-text.c' || echo '$(srcdir)/'`knot/zone/zone-dump-text.c +murmurhash3.lo: common/hattrie/murmurhash3.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT murmurhash3.lo -MD -MP -MF $(DEPDIR)/murmurhash3.Tpo -c -o murmurhash3.lo `test -f 'common/hattrie/murmurhash3.c' || echo '$(srcdir)/'`common/hattrie/murmurhash3.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/murmurhash3.Tpo $(DEPDIR)/murmurhash3.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/hattrie/murmurhash3.c' object='murmurhash3.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o murmurhash3.lo `test -f 'common/hattrie/murmurhash3.c' || echo '$(srcdir)/'`common/hattrie/murmurhash3.c + slab.lo: common/slab/slab.c @am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT slab.lo -MD -MP -MF $(DEPDIR)/slab.Tpo -c -o slab.lo `test -f 'common/slab/slab.c' || echo '$(srcdir)/'`common/slab/slab.c @am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/slab.Tpo $(DEPDIR)/slab.Plo @@ -1763,6 +1787,20 @@ server_tests.obj: tests/knot/server_tests.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o server_tests.obj `if test -f 'tests/knot/server_tests.c'; then $(CYGPATH_W) 'tests/knot/server_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/server_tests.c'; fi` +rrl_tests.o: tests/knot/rrl_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrl_tests.o -MD -MP -MF $(DEPDIR)/rrl_tests.Tpo -c -o rrl_tests.o `test -f 'tests/knot/rrl_tests.c' || echo '$(srcdir)/'`tests/knot/rrl_tests.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrl_tests.Tpo $(DEPDIR)/rrl_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/rrl_tests.c' object='rrl_tests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrl_tests.o `test -f 'tests/knot/rrl_tests.c' || echo '$(srcdir)/'`tests/knot/rrl_tests.c + +rrl_tests.obj: tests/knot/rrl_tests.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT rrl_tests.obj -MD -MP -MF $(DEPDIR)/rrl_tests.Tpo -c -o rrl_tests.obj `if test -f 'tests/knot/rrl_tests.c'; then $(CYGPATH_W) 'tests/knot/rrl_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/rrl_tests.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/rrl_tests.Tpo $(DEPDIR)/rrl_tests.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/knot/rrl_tests.c' object='rrl_tests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o rrl_tests.obj `if test -f 'tests/knot/rrl_tests.c'; then $(CYGPATH_W) 'tests/knot/rrl_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/knot/rrl_tests.c'; fi` + unittests_main.o: tests/unittests_main.c @am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unittests_main.o -MD -MP -MF $(DEPDIR)/unittests_main.Tpo -c -o unittests_main.o `test -f 'tests/unittests_main.c' || echo '$(srcdir)/'`tests/unittests_main.c @am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/unittests_main.Tpo $(DEPDIR)/unittests_main.Po diff --git a/src/common/acl.c b/src/common/acl.c index c8e0488..252f3f9 100644 --- a/src/common/acl.c +++ b/src/common/acl.c @@ -17,6 +17,7 @@ #include <string.h> #include <stdlib.h> #include <assert.h> +#include <sys/types.h> #include <sys/socket.h> #include "common/acl.h" diff --git a/src/common/acl.h b/src/common/acl.h index 7ce8f26..7d4adac 100644 --- a/src/common/acl.h +++ b/src/common/acl.h @@ -50,7 +50,7 @@ typedef struct acl_t { acl_rule_t default_rule; /*!< \brief Default rule. */ skip_list_t *rules; /*!< \brief Data container. */ skip_list_t *rules_pref; /*!< \brief Preferred data container. */ - const char name[]; /*!< \brief ACL semantic name. */ + char name[]; /*!< \brief ACL semantic name. */ } acl_t; /*! \brief Single ACL value. */ diff --git a/src/common/errcode.c b/src/common/errcode.c index ff9be63..75c4e13 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 0693a0d..b2afae5 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/common/fdset.c b/src/common/fdset.c index 6c5f0d3..c304840 100644 --- a/src/common/fdset.c +++ b/src/common/fdset.c @@ -205,7 +205,7 @@ int fdset_sweep(fdset_t* fdset, void(*cb)(fdset_t*, int, void*), void *data) } /* OpenBSD compatibility. */ -#ifndef HAVE_PSELECT +#if !defined(HAVE_PSELECT) || defined(PSELECT_COMPAT) /* * Like select(2) but set the signals to block while waiting in * select. This version is not entirely race condition safe. Only @@ -248,12 +248,12 @@ int fdset_sweep(fdset_t* fdset, void(*cb)(fdset_t*, int, void*), void *data) #include <signal.h> static int -pselect (int n, - fd_set *readfds, - fd_set *writefds, - fd_set *exceptfds, - const struct timespec *timeout, - const sigset_t *sigmask) +pselect_compat (int n, + fd_set *readfds, + fd_set *writefds, + fd_set *exceptfds, + const struct timespec *timeout, + const sigset_t *sigmask) { int result; sigset_t saved_sigmask; @@ -276,10 +276,17 @@ pselect (int n, return result; } -#endif +int fdset_pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + const struct timespec *timeout, const sigset_t *sigmask) +{ + return pselect_compat(n, readfds, writefds, exceptfds, timeout, sigmask); +} + +#else int fdset_pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask) { return pselect(n, readfds, writefds, exceptfds, timeout, sigmask); } +#endif diff --git a/src/common/fdset.h b/src/common/fdset.h index 12fc5c7..a589145 100644 --- a/src/common/fdset.h +++ b/src/common/fdset.h @@ -32,9 +32,16 @@ #ifndef _KNOTD_FDSET_H_ #define _KNOTD_FDSET_H_ +#include "config.h" #include <stddef.h> #ifdef HAVE_SYS_SELECT_H -#include <sys/select.h> + #include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif +#ifdef HAVE_SIGNAL_H + #include <signal.h> #endif #include "skip-list.h" #include "mempattern.h" diff --git a/src/common/fdset_kqueue.c b/src/common/fdset_kqueue.c index 108c572..a45da96 100644 --- a/src/common/fdset_kqueue.c +++ b/src/common/fdset_kqueue.c @@ -95,6 +95,7 @@ int fdset_kqueue_add(fdset_t *fdset, int fd, int events) int evfilt = EVFILT_READ; EV_SET(&fdset->events[fdset->nfds], fd, evfilt, EV_ADD|EV_ENABLE, 0, 0, 0); + memset(fdset->revents + fdset->nfds, 0, sizeof(struct kevent)); ++fdset->nfds; return 0; @@ -195,6 +196,7 @@ int fdset_kqueue_begin(fdset_t *fdset, fdset_it_t *it) /* Find first. */ it->pos = 0; + it->fd = -1; return fdset_next(fdset, it); } @@ -221,7 +223,7 @@ int fdset_kqueue_end(fdset_t *fdset, fdset_it_t *it) int fdset_kqueue_next(fdset_t *fdset, fdset_it_t *it) { - if (fdset == NULL || it == NULL || fdset->nfds < 1) { + if (fdset == NULL || it == NULL || fdset->polled < 1) { return -1; } diff --git a/src/common/hattrie/murmurhash3.c b/src/common/hattrie/murmurhash3.c new file mode 100644 index 0000000..cb24c8f --- /dev/null +++ b/src/common/hattrie/murmurhash3.c @@ -0,0 +1,77 @@ +/* This is MurmurHash3. The original C++ code was placed in the public domain + * by its author, Austin Appleby. */ + +#include "murmurhash3.h" + +static inline uint32_t fmix(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + + +static inline uint32_t rotl32(uint32_t x, int8_t r) +{ + return (x << r) | (x >> (32 - r)); +} + + +uint32_t hash(const char* data, size_t len_) +{ + const int len = (int) len_; + const int nblocks = len / 4; + + uint32_t h1 = 0xc062fb4a; + + uint32_t c1 = 0xcc9e2d51; + uint32_t c2 = 0x1b873593; + + //---------- + // body + + const uint32_t * blocks = (const uint32_t*) (data + nblocks * 4); + + int i; + for(i = -nblocks; i; i++) + { + uint32_t k1 = blocks[i]; + + k1 *= c1; + k1 = rotl32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = rotl32(h1, 13); + h1 = h1*5+0xe6546b64; + } + + //---------- + // tail + + const uint8_t * tail = (const uint8_t*)(data + nblocks*4); + + uint32_t k1 = 0; + + switch(len & 3) + { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = rotl32(k1,15); k1 *= c2; h1 ^= k1; + } + + //---------- + // finalization + + h1 ^= len; + + h1 = fmix(h1); + + return h1; +} + diff --git a/src/common/hattrie/murmurhash3.h b/src/common/hattrie/murmurhash3.h new file mode 100644 index 0000000..ada7e47 --- /dev/null +++ b/src/common/hattrie/murmurhash3.h @@ -0,0 +1,11 @@ + +#ifndef MURMURHASH3_H +#define MURMURHASH3_H + +#include <stdlib.h> +#include <stdint.h> + +uint32_t hash(const char* data, size_t len); + +#endif + diff --git a/src/common/latency.c b/src/common/latency.c index a563f58..7f5f5f8 100644 --- a/src/common/latency.c +++ b/src/common/latency.c @@ -17,6 +17,7 @@ #ifdef PROF_LATENCY #include <sys/resource.h> +#include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <stdlib.h> diff --git a/src/common/latency.h b/src/common/latency.h index d965c56..39c3093 100644 --- a/src/common/latency.h +++ b/src/common/latency.h @@ -35,6 +35,7 @@ /* Do not include from latency.c */ #include <sys/time.h> +#include <sys/types.h> #include <sys/socket.h> #include <pthread.h> diff --git a/src/common/slab/slab.c b/src/common/slab/slab.c index b581c42..83d7ac5 100644 --- a/src/common/slab/slab.c +++ b/src/common/slab/slab.c @@ -29,35 +29,9 @@ * Magic constants. */ #define SLAB_MAGIC 0x51 /*!< "Sl" magic byte (slab type). */ -#define LOBJ_MAGIC 0x0B /*!< "Ob" magic byte (object type). */ #define POISON_DWORD 0xdeadbeef /*!< Memory boundary guard magic. */ #define SLAB_MINCOLOR 64 /*!< Minimum space reserved for cache coloring. */ -#define SLAB_HEADER sizeof(slab_t) /*!< Slab header size. */ -#define ALIGN_PTRSZ __attribute__ ((__aligned__(sizeof(void*)))) -/*! \brief Fast cache id lookup table. - * - * Provides O(1) lookup. - * Filled with interesting values from default - * or on-demand. - */ -unsigned ALIGN_PTRSZ SLAB_CACHE_LUT[SLAB_SIZE] = { - [24] = SLAB_GP_COUNT + 1, - [800] = SLAB_GP_COUNT + 2 -}; - -/*! \brief Find the next highest power of 2. */ -static inline unsigned get_next_pow2(unsigned v) -{ - // Next highest power of 2 - --v; - v |= v >> 1; v |= v >> 2; - v |= v >> 4; v |= v >> 8; - v |= v >> 16; - ++v; - - return v; -} /*! \brief Return binary logarithm of a number, which is a power of 2. */ static inline unsigned fastlog2(unsigned v) @@ -71,45 +45,12 @@ static inline unsigned fastlog2(unsigned v) return r; } -/*! - * \brief Fast hashing function. - * - * Finds the next highest power of 2 and returns binary logarithm. - * Values are stored in LUT cache for future access. - */ -static unsigned slab_cache_id(unsigned size) -{ - // Assert cache id of the smallest bufsize is 0 - if(size <= SLAB_MIN_BUFLEN) { - return 0; - } - - // Check LUT - unsigned id = 0; - if ((id = SLAB_CACHE_LUT[size])) { - return id; - } else { - - // Compute binary logarithm - // Next highest power of 2 - id = fastlog2(get_next_pow2(size)); - - // Shift cacheid of SLAB_MIN_BUFLEN to 0 - id -= SLAB_EXP_OFFSET; - - // Store - SLAB_CACHE_LUT[size] = id; - } - - return id; -} - /* * Slab run-time constants. */ +size_t SLAB_SZ = 0; /*!< Slab size. */ size_t SLAB_MASK = 0; /*!< \brief Slab address mask (for computing offsets). */ -static unsigned SLAB_LOGSIZE = 0; /*!< \brief Binary logarithm of slab size. */ /*! * Depot is a caching sub-allocator of slabs. @@ -150,7 +91,7 @@ static void* slab_depot_alloc(size_t bufsize) } #else // MEM_SLAB_DEPOT - if(posix_memalign(&page, SLAB_SIZE, SLAB_SIZE) == 0) { + if(posix_memalign(&page, SLAB_SZ, SLAB_SZ) == 0) { ((slab_t*)page)->bufsize = 0; } else { page = 0; @@ -203,12 +144,18 @@ static void slab_depot_destroy() /*! \brief Initializes slab subsystem (it is called automatically). */ void __attribute__ ((constructor)) slab_init() { + long slab_size = sysconf(_SC_PAGESIZE); + if (slab_size < 0) { + slab_size = SLAB_MINSIZE; + } + // Fetch page size - SLAB_LOGSIZE = fastlog2(SLAB_SIZE); + SLAB_SZ = (size_t)slab_size; + unsigned slab_logsz = fastlog2(SLAB_SZ); // Compute slab page mask SLAB_MASK = 0; - for (int i = 0; i < SLAB_LOGSIZE; ++i) { + for (int i = 0; i < slab_logsz; ++i) { SLAB_MASK |= 1 << i; } SLAB_MASK = ~SLAB_MASK; @@ -220,10 +167,9 @@ void __attribute__ ((constructor)) slab_init() /*! \brief Deinitializes slab subsystem (it is called automatically). */ void __attribute__ ((destructor)) slab_deinit() { - // Deinitialize global allocator - if (SLAB_LOGSIZE) { + // Deinitialize depot + if (SLAB_MASK) { slab_depot_destroy(); - SLAB_LOGSIZE = SLAB_MASK = 0; } } @@ -334,7 +280,7 @@ static inline void slab_list_move(slab_t** target, slab_t* slab) slab_t* slab_create(slab_cache_t* cache) { - const size_t size = SLAB_SIZE; + const size_t size = SLAB_SZ; slab_t* slab = slab_depot_alloc(cache->bufsize); @@ -582,149 +528,4 @@ int slab_cache_reap(slab_cache_t* cache) return count; } -int slab_alloc_init(slab_alloc_t* alloc) -{ - // Invalidate - memset(alloc, 0, sizeof(slab_alloc_t)); - - // Initialize descriptors cache - slab_cache_init(&alloc->descriptors, sizeof(slab_cache_t)); - - return 0; -} - -void slab_alloc_destroy(slab_alloc_t* alloc) -{ - // Destroy all caches - for (unsigned i = 0; i < SLAB_CACHE_COUNT; ++i) { - if (alloc->caches[i] != 0) { - slab_cache_destroy(alloc->caches[i]); - } - } - - // Destroy cache for descriptors - slab_cache_destroy(&alloc->descriptors); -} - -void* slab_alloc_alloc(slab_alloc_t* alloc, size_t size) -{ - // Invalid size check - if (knot_unlikely(!size)) { - return 0; - } - -#ifdef MEM_POISON - // Reserve memory for poison - size += sizeof(int); -#endif - // Directly map large block - if (knot_unlikely(size > SLAB_SIZE/2)) { - - // Map block - size += sizeof(slab_obj_t); - slab_obj_t* p = 0; - p = malloc(size); - - dbg_mem("%s: mapping large block of %zu bytes at %p\n", - __func__, size, p + 1); - - /* Initialize. */ - p->magic = LOBJ_MAGIC; - p->size = size - sizeof(slab_obj_t); - -#ifdef MEM_POISON - // Reduce real size - p->size -= sizeof(int); - - // Memory barrier - int* pb = (int*)((char*)p + size - sizeof(int)); - *pb = POISON_DWORD; - mprotect(pb, sizeof(int), PROT_NONE); -#endif - - return p + 1; - } - - // Get cache id from size - unsigned cache_id = slab_cache_id(size); - - // Check if associated cache exists - if (knot_unlikely(alloc->caches[cache_id] == 0)) { - - // Assert minimum cache size - if (knot_unlikely(size < SLAB_MIN_BUFLEN)) { - size = SLAB_MIN_BUFLEN; - } - - // Calculate cache bufsize - size_t bufsize = size; - if (cache_id < SLAB_GP_COUNT) { - bufsize = get_next_pow2(size); - } - - // Create cache - dbg_mem("%s: creating cache of %zuB (req. %zuB) (id=%u)\n", - __func__, bufsize, size, cache_id); - - slab_cache_t* cache = slab_cache_alloc(&alloc->descriptors); - slab_cache_init(cache, bufsize); - alloc->caches[cache_id] = cache; - } - - // Allocate from cache - void* mem = slab_cache_alloc(alloc->caches[cache_id]); - -#ifdef MEM_POISON - // Memory barrier - //int* pb = (int*)((char*)mem + size - sizeof(int)); - //mprotect(pb, sizeof(int), PROT_NONE); -#endif - return mem; -} - -void *slab_alloc_realloc(slab_alloc_t* alloc, void *ptr, size_t size) -{ - // realloc(0) equals to free(ptr) - if (!size) { - slab_free(ptr); - return 0; - } - - // Allocate new buf - void *nptr = slab_alloc_alloc(alloc, size); - assert(nptr); - - // Copy memory if present - if (ptr) { - slab_t* slab = slab_from_ptr(ptr); - memcpy(nptr, ptr, slab->cache->bufsize); - - // Free old buf - slab_free(ptr); - } - - return nptr; -} - -void slab_alloc_stats(slab_alloc_t* alloc) -{ -#ifdef MEM_DEBUG - printf("Cache usage:\n"); - for (int i = 0; i < SLAB_CACHE_COUNT; ++i) { - - if (!alloc->caches[i]) - continue; - - slab_cache_t* cache = alloc->caches[i]; - unsigned free_s = slab_list_walk(cache->slabs_free); - unsigned full_s = slab_list_walk(cache->slabs_full); - printf("%4zu: allocs=%lu frees=%lu " - "(%u empty+partial, %u full)\n", - cache->bufsize, cache->stat_allocs, - cache->stat_frees, free_s, full_s); - } -#else - printf("Cache usage: not available, enable MEM_DEBUG and recompile.\n"); -#endif -} diff --git a/src/common/slab/slab.h b/src/common/slab/slab.h index 4ea7e31..75fcca6 100644 --- a/src/common/slab/slab.h +++ b/src/common/slab/slab.h @@ -89,14 +89,11 @@ #include <stdint.h> /* Constants. */ -#define SLAB_SIZE (4096*4) //!< Slab size (16K blocks) +#define SLAB_MINSIZE 4096 //!< Slab minimal size (4K blocks) #define SLAB_MIN_BUFLEN 8 //!< Minimal allocation block size is 8B. -#define SLAB_EXP_OFFSET 3 //!< Minimal allocation size is 8B = 2^3, exp is 3. -#define SLAB_GP_COUNT 10 //!< General-purpose caches count. -#define SLAB_US_COUNT 10 //!< User-specified caches count. #define SLAB_DEPOT_SIZE 16 //!< N slabs cached = N*SLAB_SIZE kB cap -#define SLAB_CACHE_COUNT (SLAB_GP_COUNT + SLAB_US_COUNT) //!< Slab cache count. struct slab_cache_t; +extern size_t SLAB_MASK; /* Macros. */ @@ -188,22 +185,6 @@ typedef struct slab_cache_t { } slab_cache_t; /*! - * \brief Slab allocator descriptor. - * - * \note For a number of slab caches, consult SLAB_GP_COUNT - * and a number of specific records in SLAB_CACHE_LUT lookup table. - * - * \warning It is currently not advised to use this general purpose allocator, - * as it usually doesn't yield an expected performance for higher - * bookkeeping costs and it also depends on the allocation behavior - * as well. Look for slab_cache for a specialized use in most cases. - */ -typedef struct slab_alloc_t { - slab_cache_t descriptors; /*!< Slab cache for cache descriptors. */ - slab_cache_t* caches[SLAB_CACHE_COUNT]; /*!< Number of slab caches. */ -} slab_alloc_t; - -/*! * \brief Create a slab of predefined size. * * At the moment, slabs are equal to page size and page size aligned. @@ -292,61 +273,6 @@ void* slab_cache_alloc(slab_cache_t* cache); */ int slab_cache_reap(slab_cache_t* cache); -/*! - * \brief Create a general purpose slab allocator. - * - * \note Please consult struct slab_alloc_t for performance hints. - * - * \retval 0 on success. - * \retval -1 on error. - */ -int slab_alloc_init(slab_alloc_t* alloc); - -/*! - * \brief Delete slab allocator. - * - * This destroys all associated caches and frees memory. - * - * \param alloc Given allocator instance. - */ -void slab_alloc_destroy(slab_alloc_t* alloc); - -/*! - * \brief Allocate a block of memory. - * - * Returns a block of allocated memory. - * - * \note At least SLAB_MIN_BUFSIZE bytes is allocated. - * - * \note Please consult struct slab_alloc_t for performance hints. - * - * \param alloc Allocator instance. - * \param size Requested block size. - * \retval Pointer to allocated memory. - * \retval NULL on error. - */ -void* slab_alloc_alloc(slab_alloc_t* alloc, size_t size); - -/*! - * \brief Reallocate data from one slab to another. - * - * \param alloc Allocator instance. - * \param ptr Pointer to allocated memory. - * \param size Requested memory block size. - * \retval Pointer to newly allocated memory. - * \retval NULL on error. - * - */ -void *slab_alloc_realloc(slab_alloc_t* alloc, void *ptr, size_t size); - -/*! - * - * \brief Dump allocator stats. - * - * \param alloc Allocator instance. - */ -void slab_alloc_stats(slab_alloc_t* alloc); - #endif /* _KNOTD_COMMON_SLAB_H_ */ /*! @} */ diff --git a/src/config.h.in b/src/config.h.in index bc3eb0e..a6f0975 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -135,6 +135,9 @@ /* Define to 1 if you have the `setgroups' function. */ #undef HAVE_SETGROUPS +/* Define to 1 if you have the <signal.h> header file. */ +#undef HAVE_SIGNAL_H + /* Define to 1 if you have the `socket' function. */ #undef HAVE_SOCKET diff --git a/src/knot.conf.5 b/src/knot.conf.5 index f99739c..734c812 100644 --- a/src/knot.conf.5 +++ b/src/knot.conf.5 @@ -1,4 +1,4 @@ -.TH "knot.conf" "5" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2-rc2" +.TH "knot.conf" "5" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2.0-rc3" .SH "NAME" .LP .B knot.conf @@ -73,6 +73,26 @@ serves as an example of the configuration for knotc(8) and knotd(8). # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day # Default: 10s max-conn-reply 10s; + + # Rate limit + # in queries / second + # Default: off (=0) + rate-limit 0; + + # Rate limit bucket size + # Number of hashtable buckets, set to reasonable value as default. + # We chose a reasonably large prime number as it's used for hashtable size, + # it is recommended to do so as well due to better distribution. + # Tweak if you experience a lot of hash collisions, estimated memory overhead + # is approx. 16B per bucket + # Default: 1572869 + rate-limit-size 1572869; + + # Rate limit SLIP + # Each Nth blocked response will be sent as truncated, this is a way to allow + # legitimate requests to get a chance to reconnect using TCP + # Default: 2 + rate-limit-slip 2; } # Section 'keys' contains list of TSIG keys diff --git a/src/knot/conf/cf-lex.l b/src/knot/conf/cf-lex.l index 4be9405..5a53917 100644 --- a/src/knot/conf/cf-lex.l +++ b/src/knot/conf/cf-lex.l @@ -95,6 +95,9 @@ ixfr-from-differences { lval.t = yytext; return BUILD_DIFFS; } max-conn-idle { lval.t = yytext; return MAX_CONN_IDLE; } max-conn-handshake { lval.t = yytext; return MAX_CONN_HS; } max-conn-reply { lval.t = yytext; return MAX_CONN_REPLY; } +rate-limit { lval.t = yytext; return RATE_LIMIT; } +rate-limit-size { lval.t = yytext; return RATE_LIMIT_SIZE; } +rate-limit-slip { lval.t = yytext; return RATE_LIMIT_SLIP; } interfaces { lval.t = yytext; return INTERFACES; } address { lval.t = yytext; return ADDRESS; } diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y index a0c6aaf..ad5659e 100644 --- a/src/knot/conf/cf-parse.y +++ b/src/knot/conf/cf-parse.y @@ -294,6 +294,9 @@ static int conf_mask(void* scanner, int nval, int prefixlen) { %token <tok> MAX_CONN_IDLE %token <tok> MAX_CONN_HS %token <tok> MAX_CONN_REPLY +%token <tok> RATE_LIMIT +%token <tok> RATE_LIMIT_SIZE +%token <tok> RATE_LIMIT_SLIP %token <tok> INTERFACES ADDRESS PORT %token <tok> IPA @@ -435,6 +438,10 @@ system: | system MAX_CONN_IDLE INTERVAL ';' { new_config->max_conn_idle = $3.i; } | system MAX_CONN_HS INTERVAL ';' { new_config->max_conn_hs = $3.i; } | system MAX_CONN_REPLY INTERVAL ';' { new_config->max_conn_reply = $3.i; } + | system RATE_LIMIT NUM ';' { new_config->rrl = $3.i; } + | system RATE_LIMIT_SIZE SIZE ';' { new_config->rrl_size = $3.l; } + | system RATE_LIMIT_SIZE NUM ';' { new_config->rrl_size = $3.i; } + | system RATE_LIMIT_SLIP NUM ';' { new_config->rrl_slip = $3.i; } ; keys: diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index ac36670..f93eefe 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.c @@ -181,6 +181,14 @@ static int conf_process(conf_t *conf) if (conf->ctl.iface && conf->ctl.iface->port <= 0) { conf->ctl.iface->port = REMOTE_DPORT; } + + /* Default RRL limits. */ + if (conf->rrl_slip < 0) { + conf->rrl_slip = CONFIG_RRL_SLIP; + } + if (conf->rrl_size == 0) { + conf->rrl_size = CONFIG_RRL_SIZE; + } // Postprocess zones int ret = KNOT_EOK; @@ -226,6 +234,10 @@ static int conf_process(conf_t *conf) // Default zone file if (zone->file == NULL) { zone->file = strcdup(zone->name, ".zone"); + if (!zone->file) { + ret = KNOT_ENOMEM; + continue; + } } // Relative zone filenames should be relative to storage diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h index a64388e..f56d92d 100644 --- a/src/knot/conf/conf.h +++ b/src/knot/conf/conf.h @@ -50,6 +50,8 @@ #define CONFIG_REPLY_WD 10 /*!< SOA/NOTIFY query timeout [s]. */ #define CONFIG_HANDSHAKE_WD 10 /*!< [secs] for connection to make a request.*/ #define CONFIG_IDLE_WD 60 /*!< [secs] of allowed inactivity between requests */ +#define CONFIG_RRL_SLIP 2 /*!< Default slip value. */ +#define CONFIG_RRL_SIZE 1572869 /*!< Htable default size. */ /*! * \brief Configuration for the interface @@ -180,6 +182,9 @@ typedef struct conf_t { int max_conn_idle; /*!< TCP idle timeout. */ int max_conn_hs; /*!< TCP of inactivity before first query. */ int max_conn_reply; /*!< TCP/UDP query timeout. */ + int rrl; /*!< Rate limit (in responses per second). */ + size_t rrl_size; /*!< Rate limit htable size. */ + int rrl_slip; /*!< Rate limit SLIP. */ /* * Log diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c index 92c5c09..3881ab9 100644 --- a/src/knot/ctl/knotc_main.c +++ b/src/knot/ctl/knotc_main.c @@ -684,6 +684,7 @@ int main(int argc, char **argv) /* Command not found. */ if (!cmd->name) { log_server_error("Invalid command: '%s'\n", argv[optind]); + free(config_fn); tsig_key_cleanup(&r_key); log_close(); return 1; @@ -775,7 +776,6 @@ static int cmd_start(int argc, char *argv[], unsigned flags, int jobs) } else { log_server_info("Forcing server start.\n"); pid_remove(pidfile); - pid = -1; } } else { /* Create empty PID file. */ diff --git a/src/knot/main.c b/src/knot/main.c index b3d8b8d..4359194 100644 --- a/src/knot/main.c +++ b/src/knot/main.c @@ -356,7 +356,6 @@ int main(int argc, char **argv) /* Close remote control interface */ if (remote > -1) { close(remote); - remote = -1; } if ((server_wait(server)) != KNOT_EOK) { diff --git a/src/knot/other/debug.h b/src/knot/other/debug.h index 1a8698e..c88e166 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/dthreads.c b/src/knot/server/dthreads.c index 82d7dd2..45fa866 100644 --- a/src/knot/server/dthreads.c +++ b/src/knot/server/dthreads.c @@ -258,10 +258,7 @@ static dthread_t *dt_create_thread(dt_unit_t *unit) static void dt_delete_thread(dthread_t **thread) { // Check - if (thread == 0) { - return; - } - if (*thread == 0) { + if (!thread || !*thread) { return; } diff --git a/src/knot/server/rrl.c b/src/knot/server/rrl.c new file mode 100644 index 0000000..70101c9 --- /dev/null +++ b/src/knot/server/rrl.c @@ -0,0 +1,416 @@ +/* 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 <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <assert.h> + +#include "knot/server/rrl.h" +#include "knot/common.h" +#include "libknot/consts.h" +#include "libknot/util/wire.h" +#include "common/hattrie/murmurhash3.h" +#include "common/prng.h" +#include "common/errors.h" + +/* Limits */ +#define RRL_CLSBLK_MAXLEN (4 + 8 + 1 + 256) +/* CIDR block prefix lengths for v4/v6 */ +#define RRL_V4_PREFIX ((uint32_t)0x00ffffff) /* /24 */ +#define RRL_V6_PREFIX ((uint64_t)0x00ffffffffffffff) /* /56 */ +/* Defaults */ +#define RRL_DEFAULT_RATE 100 +#define RRL_CAPACITY 4 /* N seconds. */ +#define RRL_SSTART 2 /* 1/Nth of the rate for slow start */ +#define RRL_PSIZE_LARGE 1024 +/* Enable RRL logging. */ +#define RRL_ENABLE_LOG + +/* RRL granular locking. */ +static int rrl_lock_mx(rrl_table_t *t, int lk_id) +{ + assert(lk_id > -1); + dbg_rrl_verb("%s: locking id '%d'\n", __func__, lk_id); + return pthread_mutex_lock(&t->lk[lk_id].mx); +} + +static int rrl_unlock_mx(rrl_table_t *t, int lk_id) +{ + assert(lk_id > -1); + dbg_rrl_verb("%s: unlocking id '%d'\n", __func__, lk_id); + return pthread_mutex_unlock(&t->lk[lk_id].mx); +} + +/* 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. */ + CLS_LARGE = 1 << 4, /* Response size over threshold (1024k). */ + CLS_WILDCARD = 1 << 5, /* Wildcard query. */ + CLS_ANY = 1 << 6, /* ANY query (spec. class). */ + CLS_DNSSEC = 1 << 7 /* DNSSEC related RR query (spec. class) */ +}; + +/* Classification string. */ +const error_table_t rrl_clsstr_tbl[] = { + {CLS_NORMAL, "POSITIVE" }, + {CLS_ERROR, "ERROR" }, + {CLS_NXDOMAIN,"NXDOMAIN"}, + {CLS_EMPTY, "EMPTY"}, + {CLS_LARGE, "LARGE"}, + {CLS_WILDCARD,"WILDCARD"}, + {CLS_ANY, "ANY"}, + {CLS_DNSSEC, "DNSSEC"}, + {CLS_NULL, "NULL"}, + {CLS_NULL, NULL} +}; +static inline const char *rrl_clsstr(int code) +{ + return error_to_str(rrl_clsstr_tbl, code); +} + +/* Bucket flags. */ +enum { + RRL_BF_NULL = 0 << 0, /* No flags. */ + RRL_BF_SSTART = 1 << 0, /* Bucket in slow-start after collision. */ + RRL_BF_ELIMIT = 1 << 1 /* Bucket is rate-limited. */ +}; + +static uint8_t rrl_clsid(rrl_req_t *p) +{ + /* Check error code */ + int ret = CLS_NULL; + switch (knot_wire_get_rcode(p->w)) { + 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 query type for spec. classes. */ + if (p->qst) { + switch(p->qst->qtype) { + case KNOT_RRTYPE_ANY: /* ANY spec. class */ + return CLS_ANY; + break; + case KNOT_RRTYPE_DNSKEY: + case KNOT_RRTYPE_RRSIG: + case KNOT_RRTYPE_DS: /* DNSSEC-related RR class. */ + return CLS_DNSSEC; + break; + default: + break; + } + } + + /* Check packet size for threshold. */ + if (p->len >= RRL_PSIZE_LARGE) { + return CLS_LARGE; + } + + /* Check ancount */ + if (knot_wire_get_ancount(p->w) == 0) { + return CLS_EMPTY; + } + + return ret; +} + +static int rrl_clsname(char *dst, size_t maxlen, uint8_t cls, + rrl_req_t *p, const knot_zone_t *z) +{ + 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: + if (p->qst) dn = p->qst->qname; + 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, const sockaddr_t *a, + rrl_req_t *p, const knot_zone_t *z, uint32_t seed) +{ + if (!dst || !p || !a || maxlen == 0) { + return KNOT_EINVAL; + } + + /* Class */ + uint8_t cls = rrl_clsid(p); + *dst = cls; + int blklen = sizeof(cls); + + /* Address (in network byteorder, adjust masks). */ + uint64_t nb = 0; + if (a->family == AF_INET6) { /* Take the /56 prefix. */ + nb = *((uint64_t*)&a->addr6.sin6_addr) & RRL_V6_PREFIX; + } 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, maxlen - blklen, cls, p, z); + if (len < 0) return len; + blklen += len; + + /* Seed. */ + 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, const sockaddr_t *a, rrl_req_t *p, + const knot_zone_t *zone, uint32_t stamp, int *lk) +{ + char buf[RRL_CLSBLK_MAXLEN]; + int len = rrl_classify(buf, sizeof(buf), a, p, zone, t->seed); + if (len < 0) { + return NULL; + } + + uint32_t id = hash(buf, len) % t->size; + + /* Check locking. */ + *lk = -1; + if (t->lk_count > 0) { + *lk = id % t->lk_count; + rrl_lock_mx(t, *lk); + } + + rrl_item_t *b = t->arr + id; + dbg_rrl("%s: classified pkt as '0x%x' bucket=%p\n", __func__, id, b); + + /* Inspect bucket state. */ + uint64_t nprefix = *((uint64_t*)(buf + sizeof(uint8_t))); + if (b->cls == CLS_NULL) { + b->cls = *buf; /* Stored as a first byte in clsblock. */ + b->flags = RRL_BF_NULL; + b->ntok = t->rate; + b->time = stamp; + b->pref = nprefix; /* Invalidate */ + } + /* Check for collisions. */ + if (b->pref != nprefix) { + dbg_rrl("%s: collision in bucket '0x%4x'\n", __func__, id); + if (!(b->flags & RRL_BF_SSTART)) { + b->pref = nprefix; + b->cls = *buf; + b->flags = RRL_BF_NULL; /* Reset flags. */ + b->time = stamp; /* Reset time */ + b->ntok = t->rate / RRL_SSTART; + b->flags |= RRL_BF_SSTART; + dbg_rrl("%s: bucket '0x%4x' slow-start\n", __func__, id); + } + } + + return b; +} + +static void rrl_log_state(const sockaddr_t *a, uint16_t flags, uint8_t cls) +{ +#ifdef RRL_ENABLE_LOG + char saddr[SOCKADDR_STRLEN]; + memset(saddr, 0, sizeof(saddr)); + sockaddr_tostr(a, saddr, sizeof(saddr)); + const char *what = "leaves"; + if (flags & RRL_BF_ELIMIT) { + what = "enters"; + } + + log_server_notice("Address '%s' %s rate-limiting (class '%s').\n", + saddr, what, rrl_clsstr(cls)); +#endif +} + +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 = 0; + t->seed = (uint32_t)(tls_rand() * (double)UINT32_MAX); + t->size = size; + dbg_rrl("%s: created table size '%zu'\n", __func__, t->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_setlocks(rrl_table_t *rrl, size_t granularity) +{ + if (!rrl) return KNOT_EINVAL; + assert(!rrl->lk); /* Cannot change while locks are used. */ + + /* Alloc new locks. */ + rrl->lk = malloc(granularity * sizeof(rrl_lock_t)); + if (!rrl->lk) return KNOT_ENOMEM; + memset(rrl->lk, 0, granularity * sizeof(rrl_lock_t)); + + /* Initialize. */ + for (size_t i = 0; i < granularity; ++i) { + if (pthread_mutex_init(&rrl->lk[i].mx, NULL) < 0) break; + ++rrl->lk_count; + } + /* Incomplete initialization */ + if (rrl->lk_count != granularity) { + for (size_t i = 0; i < rrl->lk_count; ++i) { + pthread_mutex_destroy(&rrl->lk[i].mx); + } + free(rrl->lk); + rrl->lk_count = 0; + dbg_rrl("%s: failed to init locks\n", __func__); + return KNOT_ERROR; + } + + dbg_rrl("%s: set granularity to '%zu'\n", __func__, granularity); + return KNOT_EOK; +} + +int rrl_query(rrl_table_t *rrl, const sockaddr_t *a, rrl_req_t *req, + const knot_zone_t *zone) +{ + if (!rrl || !req || !a) return KNOT_EINVAL; + + /* Calculate hash and fetch */ + int ret = KNOT_EOK; + int lock = -1; + uint32_t now = time(NULL); + rrl_item_t *b = rrl_hash(rrl, a, req, zone, now, &lock); + if (!b) { + assert(lock < 0); + dbg_rrl("%s: failed to compute bucket from packet\n", __func__); + return KNOT_ERROR; + } + + /* Calculate rate for dT */ + uint32_t dt = now - b->time; + if (dt > RRL_CAPACITY) { + dt = RRL_CAPACITY; + } + /* Visit bucket. */ + b->time = now; + dbg_rrl("%s: bucket=0x%x tokens=%hu flags=%x dt=%u\n", + __func__, (unsigned)(b - rrl->arr), b->ntok, b->flags, dt); + if (dt > 0) { /* Window moved. */ + + /* Check state change. */ + if ((b->ntok > 0 || dt > 1) && (b->flags & RRL_BF_ELIMIT)) { + b->flags &= ~RRL_BF_ELIMIT; + rrl_log_state(a, b->flags, b->cls); + } + + /* Add new tokens. */ + uint32_t dn = rrl->rate * dt; + if (b->flags & RRL_BF_SSTART) { /* Bucket in slow-start. */ + dn /= RRL_SSTART; + b->flags &= ~RRL_BF_SSTART; + dbg_rrl("%s: bucket '0x%x' slow-start finished\n", + __func__, (unsigned)(b - rrl->arr)); + } + b->ntok += dn; + if (b->ntok > RRL_CAPACITY * rrl->rate) { + b->ntok = RRL_CAPACITY * rrl->rate; + } + } + + /* Last item taken. */ + if (b->ntok == 1 && !(b->flags & RRL_BF_ELIMIT)) { + b->flags |= RRL_BF_ELIMIT; + rrl_log_state(a, b->flags, b->cls); + } + + /* Decay current bucket. */ + if (b->ntok > 0) { + --b->ntok; + } else if (b->ntok == 0) { + ret = KNOT_ELIMIT; + } + + /* Unlock bucket. */ + if (lock > -1) { + rrl_unlock_mx(rrl, lock); + } + + return ret; +} + +int rrl_destroy(rrl_table_t *rrl) +{ + if (rrl) { + dbg_rrl("%s: freeing table %p\n", __func__, rrl); + for (size_t i = 0; i < rrl->lk_count; ++i) { + pthread_mutex_destroy(&rrl->lk[i].mx); + } + free(rrl->lk); + } + + free(rrl); + return KNOT_EOK; +} diff --git a/src/knot/server/rrl.h b/src/knot/server/rrl.h new file mode 100644 index 0000000..e0f650d --- /dev/null +++ b/src/knot/server/rrl.h @@ -0,0 +1,143 @@ +/* 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 <pthread.h> +#include "common/sockaddr.h" +#include "libknot/packet/packet.h" +#include "libknot/zone/zone.h" + +/* Defaults */ +#define RRL_LOCK_GRANULARITY 10 /* Last digit granularity */ + +/*! + * \brief RRL hash bucket. + */ +typedef struct rrl_item { + uint64_t pref; /* Prefix associated. */ + uint16_t ntok; /* Tokens available */ + uint8_t cls; /* Bucket class */ + uint8_t flags; /* Flags */ + uint32_t time; /* Timestamp */ +} rrl_item_t; + +typedef struct rrl_lock { /* Wrapper around lock struct. */ + pthread_mutex_t mx; +} rrl_lock_t; + +/*! + * \brief RRL hash bucket table. + * + * Table is fixed size, so collisions may occur and are dealt with + * in a way, that hashbucket rate is reset and enters slow-start for 1 dt. + * When a bucket is in a slow-start mode, it cannot reset again for the time + * period. + * + * To avoid lock contention, N locks are created and distributed amongst buckets. + * As of now lock K for bucket N is calculated as K = N % (num_buckets). + */ + +typedef struct rrl_table { + uint32_t rate; /* Configured RRL limit */ + uint32_t seed; /* Pseudorandom seed for hashing. */ + rrl_lock_t *lk; /* Table locks. */ + size_t lk_count; /* Table lock count (granularity). */ + size_t size; /* Number of buckets */ + rrl_item_t arr[]; /* Buckets */ +} rrl_table_t; + +/*! + * \brief RRL request descriptor. + */ +typedef struct rrl_req { + const uint8_t *w; + uint16_t len; + unsigned flags; + const knot_question_t *qst; +} rrl_req_t; + +/*! + * \brief Create a RRL table. + * \param size Fixed hashtable size (reasonable large prime is recommended). + * \return created table or NULL. + */ +rrl_table_t *rrl_create(size_t size); + +/*! + * \brief Get RRL table default rate. + * \param rrl RRL table. + * \return rate + */ +uint32_t rrl_rate(rrl_table_t *rrl); + +/*! + * \brief Set RRL table default rate. + * + * \note When changing the rate, it is NOT applied to all buckets immediately. + * + * \param rrl RRL table. + * \param rate New rate (in pkts/sec). + * \return old rate + */ +uint32_t rrl_setrate(rrl_table_t *rrl, uint32_t rate); + +/*! + * \brief Set N distributed locks for the RRL table. + * + * \param rrl RRL table. + * \param granularity Number of created locks. + * \retval KNOT_EOK + * \retval KNOT_EINVAL + */ +int rrl_setlocks(rrl_table_t *rrl, size_t granularity); + +/*! + * \brief Query the RRL table for accept or deny, when the rate limit is reached. + * + * \param rrl RRL table. + * \param a Source address. + * \param req RRL request (containing resp., flags and question). + * \param zone Zone related to the response (or NULL). + * \retval KNOT_EOK if passed. + * \retval KNOT_ELIMIT when the limit is reached. + */ +int rrl_query(rrl_table_t *rrl, const sockaddr_t *a, rrl_req_t *req, + const knot_zone_t *zone); + +/*! + * \brief Destroy RRL table. + * \param rrl RRL table. + * \return KNOT_EOK + */ +int rrl_destroy(rrl_table_t *rrl); + + +#endif /* _KNOTD_RRL_H_ */ + +/*! @} */ diff --git a/src/knot/server/server.c b/src/knot/server/server.c index e42460f..08abe11 100644 --- a/src/knot/server/server.c +++ b/src/knot/server/server.c @@ -351,12 +351,20 @@ static int server_bind_handlers(server_t *server) WALK_LIST(n, *server->ifaces) { iface_t *iface = (iface_t*)n; + assert(iface); /* Create UDP handlers. */ dt_unit_t *unit = 0; if (!iface->handler[UDP_ID]) { unit = dt_create_coherent(thr_count, &udp_master, 0); + if (!unit) { + continue; + } h = server_create_handler(server, iface->fd[UDP_ID], unit); + if (!h) { + dt_delete(&unit); + continue; + } h->type = iface->type[UDP_ID]; h->iface = iface; @@ -370,7 +378,14 @@ static int server_bind_handlers(server_t *server) /* Create TCP handlers. */ if (!iface->handler[TCP_ID]) { unit = dt_create(tcp_unit_size); + if (!unit) { + continue; + } h = server_create_handler(server, iface->fd[TCP_ID], unit); + if (!h) { + dt_delete(&unit); + continue; + } tcp_loop_unit(h, unit); h->type = iface->type[TCP_ID]; h->iface = iface; @@ -397,6 +412,7 @@ server_t *server_create() ERR_ALLOC_FAILED; return NULL; } + memset(server, 0, sizeof(server_t)); server->state = ServerIdle; init_list(&server->handlers); @@ -408,6 +424,7 @@ server_t *server_create() server->sched = evsched_new(); dt_unit_t *unit = dt_create_coherent(1, evsched_run, 0); iohandler_t *h = server_create_handler(server, -1, unit); + h->data = server->sched; // Create name server @@ -738,6 +755,9 @@ void server_destroy(server_t **server) // Delete event scheduler evsched_delete(&(*server)->sched); + + /* Delete rate limiting table. */ + rrl_destroy((*server)->rrl); free(*server); @@ -753,6 +773,23 @@ int server_conf_hook(const struct conf_t *conf, void *data) if (!server) { return KNOT_EINVAL; } + + /* Rate limiting. */ + if (!server->rrl && conf->rrl > 0) { + server->rrl = rrl_create(conf->rrl_size); + if (!server->rrl) { + log_server_error("Couldn't init rate limiting table.\n"); + } else { + rrl_setlocks(server->rrl, RRL_LOCK_GRANULARITY); + } + } + if (server->rrl) { + if (rrl_rate(server->rrl) != conf->rrl) { + rrl_setrate(server->rrl, conf->rrl); + log_server_info("Rate limiting set to %u responses/sec.\n", + conf->rrl); + } /* At this point, old buckets will converge to new rate. */ + } /* Update bound sockets. */ int ret = KNOT_EOK; diff --git a/src/knot/server/server.h b/src/knot/server/server.h index fa7597f..6d2e06c 100644 --- a/src/knot/server/server.h +++ b/src/knot/server/server.h @@ -41,6 +41,7 @@ #include "knot/server/xfr-handler.h" #include "knot/server/socket.h" #include "knot/server/dthreads.h" +#include "knot/server/rrl.h" #include "libknot/zone/zonedb.h" #include "common/evsched.h" #include "common/lists.h" @@ -118,6 +119,9 @@ typedef struct server_t { /*! \brief List of interfaces. */ list* ifaces; + + /*! \brief Rate limiting. */ + rrl_table_t *rrl; } server_t; diff --git a/src/knot/server/socket.c b/src/knot/server/socket.c index 6a32d4d..db39a0d 100644 --- a/src/knot/server/socket.c +++ b/src/knot/server/socket.c @@ -23,6 +23,7 @@ #include <stdio.h> #include <netdb.h> #include <time.h> +#include <sys/types.h> #include <sys/socket.h> #ifdef HAVE_NETINET_IN_SYSTM_H #include <netinet/in_systm.h> diff --git a/src/knot/server/socket.h b/src/knot/server/socket.h index 8bbd6cc..153b066 100644 --- a/src/knot/server/socket.h +++ b/src/knot/server/socket.h @@ -34,6 +34,7 @@ #define _KNOTD_SOCKET_H_ /* POSIX only. */ +#include <sys/types.h> #include <sys/socket.h> #include "common/sockaddr.h" diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c index 06f42c2..8f3a324 100644 --- a/src/knot/server/tcp-handler.c +++ b/src/knot/server/tcp-handler.c @@ -18,10 +18,11 @@ #include <unistd.h> #include <fcntl.h> #include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> #include <netinet/tcp.h> #include <netinet/in.h> -#include <sys/socket.h> -#include <string.h> #include <stdio.h> #include <stdlib.h> #ifdef HAVE_CAP_NG_H diff --git a/src/knot/server/udp-handler.c b/src/knot/server/udp-handler.c index e34c710..7b5e701 100644 --- a/src/knot/server/udp-handler.c +++ b/src/knot/server/udp-handler.c @@ -23,8 +23,9 @@ #include <time.h> #include <unistd.h> #include <errno.h> -#include <arpa/inet.h> +#include <sys/types.h> #include <sys/socket.h> +#include <arpa/inet.h> #include <sys/poll.h> #include <sys/syscall.h> #include <netinet/in.h> @@ -56,20 +57,52 @@ #endif #endif +/* UDP request struct. */ +struct udp_req_t { + sockaddr_t *addr; + uint8_t *qbuf; + size_t qbuflen; + size_t *resplen; + rrl_table_t *rrl; + unsigned slip; +}; + /*! \brief Pointer to selected UDP master implementation. */ static int (*_udp_master)(dthread_t *, stat_t *) = 0; -///*! \brief Wrapper for UDP send. */ -//static int xfr_send_udp(int session, sockaddr_t *addr, uint8_t *msg, size_t msglen) -//{ -// return sendto(session, msg, msglen, 0, addr->ptr, addr->len); -//} +/*! \brief RRL reject procedure. */ +static size_t udp_rrl_reject(const knot_nameserver_t *ns, + const knot_packet_t *packet, + uint8_t* resp, size_t rlen, + uint8_t rcode, unsigned *slip) +{ + int n_slip = conf()->rrl_slip; /* Check SLIP. */ + if (n_slip > 0 && n_slip == ++*slip) { + knot_ns_error_response_from_query(ns, packet, rcode, resp, &rlen); + switch(rcode) { /* Do not set TC=1 to some RCODEs. */ + case KNOT_RCODE_FORMERR: + case KNOT_RCODE_REFUSED: + case KNOT_RCODE_SERVFAIL: + case KNOT_RCODE_NOTIMPL: + break; + default: + knot_wire_set_tc(resp); /* Set TC=1 */ + break; + } + + *slip = 0; /* Restart SLIP interval. */ + return rlen; + } + + return 0; /* Discard response. */ +} int udp_handle(int fd, uint8_t *qbuf, size_t qbuflen, size_t *resp_len, - sockaddr_t* addr, knot_nameserver_t *ns) + sockaddr_t* addr, knot_nameserver_t *ns, rrl_table_t *rrl, unsigned *slip) { #ifdef DEBUG_ENABLE_BRIEF char strfrom[SOCKADDR_STRLEN]; + memset(strfrom, 0, sizeof(strfrom)); sockaddr_tostr(addr, strfrom, sizeof(strfrom)); dbg_net("udp: fd=%d received %zd bytes from '%s@%d'.\n", fd, qbuflen, strfrom, sockaddr_portnum(addr)); @@ -93,44 +126,37 @@ int udp_handle(int fd, uint8_t *qbuf, size_t qbuflen, size_t *resp_len, return KNOT_EOK; /* Created error response. */ } - + + /* Prepare RRL structs. */ + rrl_req_t rrl_rq; + memset(&rrl_rq, 0, sizeof(rrl_req_t)); + rrl_rq.w = qbuf; /* Wire */ + /* Parse query. */ int res = knot_ns_parse_packet(qbuf, qbuflen, packet, &qtype); + if (rrl) rrl_rq.qst = &packet->question; if (knot_unlikely(res != KNOT_EOK)) { dbg_net("udp: failed to parse packet on fd=%d\n", fd); if (res > 0) { /* Returned RCODE */ -// int ret = knot_ns_error_response_from_query_wire(ns, -// qbuf, qbuflen, res, qbuf, resp_len); - int ret = knot_ns_error_response_from_query(ns, - packet, res, qbuf, resp_len); - - if (ret != KNOT_EOK) { + res = knot_ns_error_response_from_query( + ns, packet, res, qbuf, resp_len); + if (res != KNOT_EOK) { knot_packet_free(&packet); return KNOT_EMALF; } } else { - assert(res < 0); - int ret = knot_ns_error_response_from_query_wire( + res = knot_ns_error_response_from_query_wire( ns, qbuf, qbuflen, KNOT_RCODE_SERVFAIL, qbuf, resp_len); - - if (ret != KNOT_EOK) { + if (res != KNOT_EOK) { knot_packet_free(&packet); - return ret; + return res; } } - - knot_packet_free(&packet); - return KNOT_EOK; /* Created error response. */ } - + /* Handle query. */ -// server_t *srv = (server_t *)knot_ns_get_data(ns); -// knot_ns_xfr_t xfr; - res = KNOT_ERROR; switch(qtype) { - - /* Query types. */ case KNOT_QUERY_NORMAL: res = zones_normal_query_answer(ns, packet, addr, qbuf, resp_len, NS_TRANSPORT_UDP); @@ -139,33 +165,23 @@ int udp_handle(int fd, uint8_t *qbuf, size_t qbuflen, size_t *resp_len, /* RFC1034, p.28 requires reliable transfer protocol. * Bind responds with FORMERR. */ - /*! \note Draft exists for AXFR/UDP, but has not been standardized. */ knot_ns_error_response_from_query(ns, packet, KNOT_RCODE_FORMERR, qbuf, resp_len); res = KNOT_EOK; break; case KNOT_QUERY_IXFR: - /* According to RFC1035, respond with SOA. - * Draft proposes trying to fit response into one packet, - * but I have found no tool or slave server to actually attempt - * IXFR/UDP. - */ -// knot_packet_set_qtype(packet, KNOT_RRTYPE_SOA); + /* According to RFC1035, respond with SOA. */ res = zones_normal_query_answer(ns, packet, addr, qbuf, resp_len, NS_TRANSPORT_UDP); break; case KNOT_QUERY_NOTIFY: res = notify_process_request(ns, packet, addr, - qbuf, resp_len); + qbuf, resp_len); break; case KNOT_QUERY_UPDATE: -// dbg_net("udp: UPDATE query on fd=%d not implemented\n", fd); -// knot_ns_error_response_from_query(ns, packet, -// KNOT_RCODE_NOTIMPL, qbuf, -// resp_len); res = zones_process_update(ns, packet, addr, qbuf, resp_len, fd, NS_TRANSPORT_UDP); break; @@ -187,6 +203,20 @@ int udp_handle(int fd, uint8_t *qbuf, size_t qbuflen, size_t *resp_len, res = KNOT_EOK; break; } + + /* Process RRL. */ + if (rrl) { + rcu_read_lock(); + rrl_rq.flags = packet->flags; + if (rrl_query(rrl, addr, &rrl_rq, packet->zone) != KNOT_EOK) { + *resp_len = udp_rrl_reject(ns, packet, qbuf, + SOCKET_MTU_SZ, + knot_wire_get_rcode(qbuf), + slip); + } + rcu_read_unlock(); + } + knot_packet_free(&packet); @@ -239,6 +269,10 @@ static inline int udp_master_recvfrom(dthread_t *thread, stat_t *thread_stat) } else { sock = sock_dup; } + + /* Initialize RRL if configured. */ + unsigned rrl_slip = 0; + rrl_table_t *rrl = h->server->rrl; /* Loop until all data is read. */ ssize_t n = 0; @@ -268,7 +302,8 @@ static inline int udp_master_recvfrom(dthread_t *thread, stat_t *thread_stat) /* Handle received pkt. */ size_t resp_len = 0; - int rc = udp_handle(sock, qbuf, n, &resp_len, &addr, ns); + int rc = udp_handle(sock, qbuf, n, &resp_len, &addr, ns, + rrl, &rrl_slip); /* Send response. */ if (rc == KNOT_EOK && resp_len > 0) { @@ -413,6 +448,10 @@ static inline int udp_master_recvmmsg(dthread_t *thread, stat_t *thread_stat) cpu[0] = cpu[0] % cpcount; dt_setaffinity(thread, cpu, 2); } + + /* Initialize RRL if configured. */ + unsigned rrl_slip = 0; + rrl_table_t *rrl = h->server->rrl; /* Loop until all data is read. */ ssize_t n = 0; @@ -448,7 +487,7 @@ static inline int udp_master_recvmmsg(dthread_t *thread, stat_t *thread_stat) struct iovec *cvec = msgs[i].msg_hdr.msg_iov; size_t resp_len = msgs[i].msg_len; ret = udp_handle(sock, cvec->iov_base, resp_len, &resp_len, - addrs + i, ns); + addrs + i, ns, rrl, &rrl_slip); if (ret == KNOT_EOK) { msgs[i].msg_len = resp_len; iov[i].iov_len = resp_len; diff --git a/src/knot/server/udp-handler.h b/src/knot/server/udp-handler.h index 073a4d8..8292072 100644 --- a/src/knot/server/udp-handler.h +++ b/src/knot/server/udp-handler.h @@ -53,7 +53,8 @@ * \retval KNOT_ENOMEM */ int udp_handle(int sock, uint8_t *qbuf, size_t qbuflen, size_t *resp_len, - sockaddr_t* addr, knot_nameserver_t *ns); + sockaddr_t* addr, knot_nameserver_t *ns, + rrl_table_t *rrl, unsigned *slip); /*! * \brief UDP handler thread runnable. diff --git a/src/knot/server/xfr-handler.c b/src/knot/server/xfr-handler.c index 6a9a249..8e403c0 100644 --- a/src/knot/server/xfr-handler.c +++ b/src/knot/server/xfr-handler.c @@ -17,9 +17,10 @@ #include <config.h> #include <unistd.h> #include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> #include <netinet/tcp.h> #include <netinet/in.h> -#include <sys/socket.h> #include <string.h> #include <stdio.h> #include <stdlib.h> diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c index 935f3a4..a06484d 100644 --- a/src/knot/server/zones.c +++ b/src/knot/server/zones.c @@ -2527,6 +2527,7 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver, int ret = knot_ns_prep_normal_response(nameserver, query, &resp, &zone, (transport == NS_TRANSPORT_TCP) ? *rsize : 0); + query->zone = zone; // check for TSIG in the query // not required, TSIG is already found if it is there @@ -2638,6 +2639,7 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver, &answer_size, transport == NS_TRANSPORT_UDP); + query->flags = resp->flags; /* Copy markers. */ } dbg_zones_detail("rsize = %zu\n", *rsize); diff --git a/src/knot/stat/stat.c b/src/knot/stat/stat.c index a473085..01bbbc4 100644 --- a/src/knot/stat/stat.c +++ b/src/knot/stat/stat.c @@ -19,8 +19,9 @@ #include <pthread.h> #include <unistd.h> #include <stdbool.h> -#include <arpa/inet.h> +#include <sys/types.h> #include <sys/socket.h> +#include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> diff --git a/src/knot/stat/stat.h b/src/knot/stat/stat.h index 0cf1454..1780c3f 100644 --- a/src/knot/stat/stat.h +++ b/src/knot/stat/stat.h @@ -30,8 +30,9 @@ #include <time.h> #include <stdbool.h> #include <pthread.h> -#include <arpa/inet.h> +#include <sys/types.h> #include <sys/socket.h> +#include <arpa/inet.h> #include <netinet/in.h> #include "knot/stat/gatherer.h" diff --git a/src/knot/zone/zone-dump-text.c b/src/knot/zone/zone-dump-text.c index f231a70..0a1dcfd 100644 --- a/src/knot/zone/zone-dump-text.c +++ b/src/knot/zone/zone-dump-text.c @@ -649,6 +649,7 @@ char *rdata_nsap_to_string(knot_rdata_item_t item) char *converted = knot_hex_to_string(rdata_item_data(item), rdata_item_size(item)); if (converted == NULL) { + free(ret); return NULL; } diff --git a/src/knotc.8 b/src/knotc.8 index ba345ce..039a994 100644 --- a/src/knotc.8 +++ b/src/knotc.8 @@ -1,4 +1,4 @@ -.TH knotc "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2-rc2" +.TH knotc "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2.0-rc3" .SH NAME .B knotc \- Knot DNS control utility diff --git a/src/knotd.8 b/src/knotd.8 index 4c96cff..22d0dac 100644 --- a/src/knotd.8 +++ b/src/knotd.8 @@ -1,4 +1,4 @@ -.TH "knotd" "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2-rc2" +.TH "knotd" "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2.0-rc3" .SH NAME .B knotd \- Knot DNS daemon diff --git a/src/libknot/hash/cuckoo-hash-table.c b/src/libknot/hash/cuckoo-hash-table.c index 7d454a9..a2a6d3f 100644 --- a/src/libknot/hash/cuckoo-hash-table.c +++ b/src/libknot/hash/cuckoo-hash-table.c @@ -96,7 +96,9 @@ static const uint8_t FLAG_REHASH = 0x4; // 00000100 /*! \brief Clears the table / item flags. */ static inline void CLEAR_FLAGS(uint8_t *flags) { - *flags = (uint8_t)0x0; + if (flags) { + *flags = (uint8_t)0x0; + } } /*! \brief Returns the generation stored in the flags. */ @@ -120,7 +122,9 @@ static inline int IS_GENERATION1(uint8_t flags) /*! \brief Sets the generation stored in the flags to 1. */ static inline void SET_GENERATION1(uint8_t *flags) { - *flags = ((*flags) & ~FLAG_GENERATION2) | FLAG_GENERATION1; + if (flags) { + *flags = ((*flags) & ~FLAG_GENERATION2) | FLAG_GENERATION1; + } } /*! \brief Checks if the generation stored in the flags is 2. */ @@ -132,19 +136,26 @@ static inline int IS_GENERATION2(uint8_t flags) /*! \brief Sets the generation stored in the flags to 2. */ static inline void SET_GENERATION2(uint8_t *flags) { - *flags = ((*flags) & ~FLAG_GENERATION1) | FLAG_GENERATION2; + if (flags) { + *flags = ((*flags) & ~FLAG_GENERATION1) | FLAG_GENERATION2; + } } /*! \brief Sets the generation stored in the flags to the given generation. */ static inline void SET_GENERATION(uint8_t *flags, uint8_t generation) { - *flags = ((*flags) & ~FLAG_GENERATION_BOTH) | generation; + if (flags) { + *flags = ((*flags) & ~FLAG_GENERATION_BOTH) | generation; + } } /*! \brief Sets the generation stored in the flags to the next one (cyclic). */ static inline uint8_t SET_NEXT_GENERATION(uint8_t *flags) { - return ((*flags) ^= FLAG_GENERATION_BOTH); + if (flags) { + return ((*flags) ^= FLAG_GENERATION_BOTH); + } + return 0; } /*! \brief Returns the next generation to the one stored in flags (cyclic). */ @@ -156,13 +167,17 @@ static inline uint8_t NEXT_GENERATION(uint8_t flags) /*! \brief Sets the rehashing flag to the flags. */ static inline void SET_REHASHING_ON(uint8_t *flags) { - *flags = (*flags | FLAG_REHASH); + if (flags) { + *flags = (*flags | FLAG_REHASH); + } } /*! \brief Removes the rehashing flag from the flags. */ static inline void SET_REHASHING_OFF(uint8_t *flags) { - *flags = (*flags & ~FLAG_REHASH); + if (flags) { + *flags = (*flags & ~FLAG_REHASH); + } } /*! \brief Checks if the rehashing flag is set in the flags. */ @@ -1245,7 +1260,7 @@ int ck_shallow_copy(const ck_hash_table_t *from, ck_hash_table_t **to) // copy the stash - we must explicitly copy each stash item, but do not // copy the ck_hash_table_item_t within them. ck_stash_item_t *si = from->stash; - ck_stash_item_t **pos = &(*to)->stash; + ck_stash_item_t *last = NULL; dbg_ck_verb("Copying hash table stash.\n"); while (si != NULL) { ck_stash_item_t *si_new = (ck_stash_item_t *) @@ -1272,8 +1287,13 @@ int ck_shallow_copy(const ck_hash_table_t *from, ck_hash_table_t **to) si->item->key); si_new->item = si->item; - *pos = si_new; - pos = &si_new->next; + si_new->next = NULL; + if (last == NULL) { + (*to)->stash = si_new; + } else { + last->next = si_new; + } + last = si_new; si = si->next; dbg_ck_exec_detail( @@ -1290,8 +1310,6 @@ dbg_ck_exec_detail( ); } - *pos = NULL; - // there should be no item being hashed right now /*! \todo This operation should not be done while inserting / rehashing. */ @@ -1534,6 +1552,9 @@ int ck_apply(ck_hash_table_t *table, int ck_rehash(ck_hash_table_t *table) { dbg_ck_hash("Rehashing items in table.\n"); + if (!table) { + return -1; + } SET_REHASHING_ON(&table->generation); ck_stash_item_t *free_stash_items = NULL; diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c index 8238d7e..b81a2c0 100644 --- a/src/libknot/nameserver/name-server.c +++ b/src/libknot/nameserver/name-server.c @@ -189,6 +189,7 @@ static int ns_check_wildcard(const knot_dname_t *name, knot_packet_t *resp, assert(*rrset != NULL); if (knot_dname_is_wildcard((*rrset)->owner)) { + resp->flags |= KNOT_PF_WILDCARD; /* Mark */ knot_rrset_t *synth_rrset = ns_synth_from_wildcard(*rrset, name); if (synth_rrset == NULL) { @@ -254,7 +255,8 @@ static int ns_add_rrsigs(knot_rrset_t *rrset, knot_packet_t *resp, dbg_ns_detail("RRSIGS: %p\n", knot_rrset_rrsigs(rrset)); if (DNSSEC_ENABLED - && knot_query_dnssec_requested(knot_packet_query(resp)) + && (knot_query_dnssec_requested(knot_packet_query(resp)) + || knot_packet_qtype(resp) == KNOT_RRTYPE_ANY) && (rrsigs = knot_rrset_get_rrsigs(rrset)) != NULL) { if (name != NULL) { int ret = ns_check_wildcard(name, resp, &rrsigs); diff --git a/src/libknot/packet/packet.c b/src/libknot/packet/packet.c index 9b7e7c7..b6381c4 100644 --- a/src/libknot/packet/packet.c +++ b/src/libknot/packet/packet.c @@ -291,7 +291,6 @@ static int knot_packet_parse_question(const uint8_t *wire, size_t *pos, dbg_packet_verb("Parsing dname starting on position %zu and " "%zu bytes long.\n", *pos, i - *pos + 1); dbg_packet_verb("Alloc: %d\n", alloc); - size_t bp = *pos; if (alloc) { question->qname = knot_dname_parse_from_wire(wire, pos, i + 1, @@ -300,6 +299,7 @@ static int knot_packet_parse_question(const uint8_t *wire, size_t *pos, return KNOT_ENOMEM; } } else { + assert(question->qname != NULL); /* When alloc=0, must be set. */ void *parsed = knot_dname_parse_from_wire(wire, pos, i + 1, NULL, question->qname); @@ -344,6 +344,7 @@ static int knot_packet_realloc_rrsets(const knot_rrset_t ***rrsets, new_max_count * sizeof(knot_rrset_t *)); CHECK_ALLOC_LOG(new_rrsets, KNOT_ENOMEM); + memset(new_rrsets, 0, new_max_count * sizeof(knot_rrset_t *)); memcpy(new_rrsets, *rrsets, (*max_count) * sizeof(knot_rrset_t *)); *rrsets = new_rrsets; diff --git a/src/libknot/packet/packet.h b/src/libknot/packet/packet.h index 5a95bae..ba85aed 100644 --- a/src/libknot/packet/packet.h +++ b/src/libknot/packet/packet.h @@ -34,6 +34,7 @@ #include "rrset.h" #include "edns.h" #include "zone/node.h" +#include "zone/zone.h" /*----------------------------------------------------------------------------*/ /*! @@ -163,11 +164,24 @@ struct knot_packet { size_t tsig_size; /*!< Space to reserve for the TSIG RR. */ knot_rrset_t *tsig_rr; /*!< TSIG RR stored in the packet. */ + uint16_t flags; /*!< Packet flags. */ + const knot_zone_t *zone; /*!< Associated zone. */ }; typedef struct knot_packet knot_packet_t; /*----------------------------------------------------------------------------*/ + +/*! + * \brief Packet flags. + */ +enum { + KNOT_PF_NULL = 0 << 0, /*!< No flags. */ + KNOT_PF_QUERY = 1 << 0, /*!< Packet is query. */ + KNOT_PF_WILDCARD = 1 << 1, /*!< Query to wildcard name. */ + KNOT_PF_RESPONSE = 1 << 2 /*!< Packet is response. */ +}; + /*! * \brief Default sizes for response structure parts and steps for increasing * them. diff --git a/src/libknot/packet/response.c b/src/libknot/packet/response.c index 476c6b3..69678c7 100644 --- a/src/libknot/packet/response.c +++ b/src/libknot/packet/response.c @@ -368,6 +368,9 @@ static int knot_response_compress_dname(const knot_dname_t *dname, knot_compr_t *compr, uint8_t *dname_wire, size_t max, int compr_cs) { int size = 0; + if (!dname || !compr || !dname_wire) { + return KNOT_EINVAL; + } // try to find the name or one of its ancestors in the compr. table #ifdef COMPRESSION_PEDANTIC diff --git a/src/libknot/rdata.c b/src/libknot/rdata.c index 352bb6c..d59b4e0 100644 --- a/src/libknot/rdata.c +++ b/src/libknot/rdata.c @@ -215,6 +215,9 @@ int knot_rdata_from_wire(knot_rdata_t *rdata, const uint8_t *wire, int i = 0; uint8_t item_type; size_t parsed = 0; + if (!rdata || !wire || !pos || !desc) { + return KNOT_EINVAL; + } if (rdlength == 0) { rdata->items = NULL; diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c index 84d5075..f5a9f5f 100644 --- a/src/libknot/rrset.c +++ b/src/libknot/rrset.c @@ -346,6 +346,9 @@ knot_rdata_t *knot_rrset_get_rdata(knot_rrset_t *rrset) knot_rdata_t *knot_rrset_rdata_get_next(knot_rrset_t *rrset, knot_rdata_t *rdata) { + if (!rdata || !rrset) { + return NULL; + } if (rdata->next == rrset->rdata) { return NULL; } else { diff --git a/src/libknot/updates/ddns.c b/src/libknot/updates/ddns.c index 90a578f..79b28fb 100644 --- a/src/libknot/updates/ddns.c +++ b/src/libknot/updates/ddns.c @@ -2206,6 +2206,9 @@ static int knot_ddns_process_rr(const knot_rrset_t *rr, if (knot_rrset_class(rr) == knot_zone_contents_class(zone)) { return knot_ddns_process_add(rr, node, zone, changeset, changes, rr_copy); + } else if (node == NULL) { + // Removing from non-existing node, just ignore the entry + return KNOT_EOK; } else if (knot_rrset_class(rr) == KNOT_CLASS_NONE) { return knot_ddns_process_rem_rr(rr, node, zone, changeset, changes, qclass); diff --git a/src/libknot/zone/zone-contents.c b/src/libknot/zone/zone-contents.c index bdc268b..90aee0d 100644 --- a/src/libknot/zone/zone-contents.c +++ b/src/libknot/zone/zone-contents.c @@ -3158,7 +3158,7 @@ static void knot_zc_integrity_check_parent(const knot_node_t *node, != node) { char *wc = (knot_node_wildcard_child( check_data->parent) == NULL) - ? "none" + ? strdup("none") : knot_dname_to_str(knot_node_owner( knot_node_wildcard_child( check_data->parent))); @@ -3167,8 +3167,8 @@ static void knot_zc_integrity_check_parent(const knot_node_t *node, pname, wc, name); if (knot_node_wildcard_child( check_data->parent) != NULL) { - free(wc); } + free(wc); ++check_data->errors; } @@ -3229,6 +3229,9 @@ static int knot_zc_integrity_check_find_dname(const knot_zone_contents_t *zone, const char *node_name) { int ret = 0; + if (!zone || !to_find || !node_name) { + return KNOT_EINVAL; + } knot_dname_t *found = knot_dname_table_find_dname(zone->dname_table, (knot_dname_t *)to_find); diff --git a/src/libknot/zone/zone-tree.c b/src/libknot/zone/zone-tree.c index ceaa6a9..1d7991e 100644 --- a/src/libknot/zone/zone-tree.c +++ b/src/libknot/zone/zone-tree.c @@ -269,7 +269,7 @@ int knot_zone_tree_find_less_or_equal(knot_zone_tree_t *tree, return KNOT_EINVAL; } - knot_node_t *f, *p; + knot_node_t *f = NULL, *p = NULL; int ret = knot_zone_tree_get_less_or_equal(tree, owner, &f, &p); *found = f; diff --git a/src/libknot/zone/zone.h b/src/libknot/zone/zone.h index 31ff2ac..26a5e2a 100644 --- a/src/libknot/zone/zone.h +++ b/src/libknot/zone/zone.h @@ -193,7 +193,11 @@ void knot_zone_set_dtor(knot_zone_t *zone, int (*dtor)(struct knot_zone *)); * \param zone Zone. */ static inline unsigned knot_zone_flags(knot_zone_t *zone) { - return zone->flags; + if (zone) { + return zone->flags; + } else { + return 0; + } } /*! diff --git a/src/tests/common/events_tests.c b/src/tests/common/events_tests.c index 0acd706..a9b3de6 100644 --- a/src/tests/common/events_tests.c +++ b/src/tests/common/events_tests.c @@ -124,7 +124,7 @@ static int events_tests_run(int argc, char *argv[]) ok(s != 0, "evsched: new"); // 2. Schedule event to happen after N ms - int msecs = 50; + int msecs = 200; struct timeval st, rt; gettimeofday(&st, 0); e = evsched_schedule_cb(s, 0, (void*)0xcafe, msecs); @@ -139,7 +139,7 @@ static int events_tests_run(int argc, char *argv[]) // 4. Check receive time double passed = (rt.tv_sec - st.tv_sec) * 1000; passed += (rt.tv_usec - st.tv_usec) / 1000; - double margin = msecs * 0.2; + double margin = msecs * 0.4; double lb = msecs - margin, ub = msecs + margin; int in_bounds = (passed >= lb) && (passed <= ub); ok(in_bounds, "evsched: receive time %.1lfms is in <%.1lf,%.1lf>", diff --git a/src/tests/common/fdset_tests.c b/src/tests/common/fdset_tests.c index 08e0577..627829e 100644 --- a/src/tests/common/fdset_tests.c +++ b/src/tests/common/fdset_tests.c @@ -83,10 +83,9 @@ void* thr_action(void *arg) /* Write pattern. */ char pattern = WRITE_PATTERN; - int ret = write(*fd, &pattern, WRITE_PATTERN_LEN); - ret = ret; /* Use variable. */ + (void)write(*fd, &pattern, WRITE_PATTERN_LEN); - return 0; + return NULL; } static int fdset_tests_count(int argc, char *argv[]) diff --git a/src/tests/common/slab_tests.c b/src/tests/common/slab_tests.c index 5724a23..a5de1c2 100644 --- a/src/tests/common/slab_tests.c +++ b/src/tests/common/slab_tests.c @@ -48,7 +48,7 @@ unit_api slab_tests_api = { static int slab_tests_count(int argc, char *argv[]) { - return 7; + return 5; } static int slab_tests_run(int argc, char *argv[]) @@ -113,34 +113,5 @@ static int slab_tests_run(int argc, char *argv[]) slab_cache_destroy(&cache); ok(cache.bufsize == 0, "slab: freed cache"); - // 6. Greate GP allocator - slab_alloc_t alloc; - ret = slab_alloc_init(&alloc); - ok(ret == 0, "slab: created GP allocator"); - - // 7. Stress allocator - unsigned ncount = 0; - ptrs_i = 0; - for(int i = 0; i < alloc_count; ++i) { - double roll = rand() / (double) RAND_MAX; - size_t bsize = roll * 2048; - bsize = SLAB_MAX(bsize, 8); - if ((ptrs_i == 0) || (roll < 0.6)) { - void* m = slab_alloc_alloc(&alloc, bsize); - if (m == 0) { - ++ncount; - } else { - ptrs[ptrs_i++] = m; - } - } else { - slab_free(ptrs[--ptrs_i]); - } - } - - cmp_ok(ncount, "==", 0, "slab: GP allocator alloc/free working"); - - // 7. Destroy allocator - slab_alloc_destroy(&alloc); - return 0; } diff --git a/src/tests/knot/dthreads_tests.c b/src/tests/knot/dthreads_tests.c index 982329b..0b2cb01 100644 --- a/src/tests/knot/dthreads_tests.c +++ b/src/tests/knot/dthreads_tests.c @@ -159,8 +159,8 @@ static inline int dt_test_resize(dt_unit_t *unit, int size) _runnable_i = 0; for (int i = 0; i < size; ++i) { ret += dt_repurpose(unit->threads[i], &runnable, 0); - ret += dt_start_id(unit->threads[i]); } + ret += dt_start(unit); // Wait for finish ret += dt_join(unit); @@ -266,7 +266,7 @@ static int dt_tests_run(int argc, char *argv[]) pthread_mutex_init(&_runnable_mx, NULL); /* Test 1: Create unit */ - dt_unit_t *unit = dt_test_create(dt_optimal_size()); + dt_unit_t *unit = dt_test_create(2); ok(unit != 0, "dthreads: create unit (optimal size %d)", unit->size); skip(unit == 0, DT_TEST_COUNT - 1); @@ -316,7 +316,8 @@ static int dt_tests_run(int argc, char *argv[]) "dthreads: result %d is => %d", _runnable_i, expected_lo); /* Test 12: Compare counter #2. */ - int expected_hi = _runnable_cycles * unit->size; + /*! \note repurpose could trigger next run of the unit if both finished */ + int expected_hi = _runnable_cycles * (unit->size + unit->size - 1); cmp_ok(_runnable_i, "<=", expected_hi, "dthreads: result %d is <= %d", _runnable_i, expected_hi); diff --git a/src/tests/knot/rrl_tests.c b/src/tests/knot/rrl_tests.c new file mode 100644 index 0000000..35b481e --- /dev/null +++ b/src/tests/knot/rrl_tests.c @@ -0,0 +1,135 @@ +/* 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/types.h> +#include <sys/socket.h> +#include "tests/knot/rrl_tests.h" +#include "knot/server/rrl.h" +#include "knot/common.h" +#include "libknot/packet/response.h" +#include "libknot/packet/query.h" +#include "libknot/nameserver/name-server.h" + +/* Enable time-dependent tests. */ +//#define ENABLE_TIMED_TESTS + +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[]) +{ + int c = 6; +#ifndef ENABLE_TIMED_TESTS + c -= 2; +#endif + return c; +} + +static int rrl_tests_run(int argc, char *argv[]) +{ + /* Prepare query. */ + knot_question_t qst; + qst.qclass = KNOT_CLASS_IN; + qst.qtype = KNOT_RRTYPE_A; + qst.qname = knot_dname_new_from_str("beef.", 5, NULL); + knot_packet_t *query = knot_packet_new(KNOT_PACKET_PREALLOC_QUERY); + knot_query_init(query); + knot_packet_set_max_size(query, 512); + knot_query_set_question(query, &qst); + + /* Prepare response */ + knot_nameserver_t *ns = knot_ns_create(); + uint8_t rbuf[65535]; + size_t rlen = sizeof(rbuf); + memset(rbuf, 0, sizeof(rbuf)); + knot_ns_error_response_from_query(ns, query, KNOT_RCODE_NOERROR, rbuf, &rlen); + + rrl_req_t rq; + rq.w = rbuf; + rq.len = rlen; + rq.qst = &qst; + rq.flags = 0; + + /* 1. create rrl table */ + rrl_table_t *rrl = rrl_create(101); + 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. */ + 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); + sockaddr_set(&addr6, AF_INET6, "1122:3344:5566:7788::aabb", 0); + int ret = 0; + for (unsigned i = 0; i < rate; ++i) { + if (rrl_query(rrl, &addr, &rq, zone) != KNOT_EOK || + rrl_query(rrl, &addr6, &rq, zone) != KNOT_EOK) { + ret = KNOT_ELIMIT; + break; + } + } + ok(ret == 0, "rrl: unlimited IPv4/v6 requests"); + +#ifdef ENABLE_TIMED_TESTS + /* 4. limited request */ + ret = rrl_query(rrl, &addr, &rq, zone); + ok(ret != 0, "rrl: throttled IPv4 request"); + + /* 5. limited IPv6 request */ + ret = rrl_query(rrl, &addr6, &rq, zone); + ok(ret != 0, "rrl: throttled IPv6 request"); +#endif + + /* 6. invalid values. */ + ret = 0; + lives_ok( { + rrl_create(0); // NULL + ret += rrl_setrate(0, 0); // 0 + ret += rrl_rate(0); // 0 + ret += rrl_setlocks(0,0); // -1 + ret += rrl_query(0, 0, 0, 0); // -1 + ret += rrl_query(rrl, 0, 0, 0); // -1 + ret += rrl_query(rrl, (void*)0x1, 0, 0); // -1 + ret += rrl_destroy(0); // -1 + }, "dthreads: not crashed while executing functions on NULL context"); + + knot_dname_release(qst.qname); + knot_dname_release(apex); + knot_zone_deep_free(&zone, 0); + knot_ns_destroy(&ns); + knot_packet_free(&query); + 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 0000000..447b735 --- /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 17ea3b4..aee4bf9 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[]) @@ -56,6 +57,7 @@ int main(int argc, char *argv[]) /* Server parts. */ &conf_tests_api, //! Configuration parser tests &server_tests_api, //! Server unit + &rrl_tests_api, //! RRL tests NULL }; diff --git a/src/zcompile/parser-descriptor.c b/src/zcompile/parser-descriptor.c index bc3ee16..466beb4 100644 --- a/src/zcompile/parser-descriptor.c +++ b/src/zcompile/parser-descriptor.c @@ -460,6 +460,9 @@ uint16_t parser_rrtype_from_string(const char *name) char *end; long rrtype; parser_rrtype_descriptor_t *entry; + if (!name) { + return 0; + } entry = parser_rrtype_descriptor_by_name(name); if (entry) { diff --git a/src/zcompile/parser-util.c b/src/zcompile/parser-util.c index 4cdb4e3..b112ece 100644 --- a/src/zcompile/parser-util.c +++ b/src/zcompile/parser-util.c @@ -54,8 +54,9 @@ #include <unistd.h> #include <stdlib.h> #include <time.h> -#include <netinet/in.h> +#include <sys/types.h> #include <sys/socket.h> +#include <netinet/in.h> #include <netdb.h> //#include "common.h" |