diff --git a/ChangeLog b/ChangeLog index b421f01e88f934ceda0ee34eb8119f82bf1aeb96..d69f49912c0be0343d002d98383bb6d4260e8e15 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,83 @@ +501. [func] tomek + Added DHCPv6 allocation engine, now used in the processing of DHCPv6 + messages. + (Trac #2414, git b3526430f02aa3dc3273612524d23137b8f1fe87) + +500. [bug] jinmei + Corrected the autoconf example in the examples directory so it can + use the configured path to Boost to check availability of the BIND 10 + library. Previously the sample configure script could fail if + Boost is installed in an uncommon place. Also, it now provides a + helper m4 function and example usage for embedding the library + path to executable (using linker options like -Wl,-R) to help + minimize post-build hassles. + (Trac #2356, git 36514ddc884c02a063e166d44319467ce6fb1d8f) + +499. [func] team + The b10-auth 'loadzone' command now uses the internal thread + introduced in 495 to (re)load a zone in the background, so that + query processing isn't blocked while loading a zone. + (Trac #2213, git 686594e391c645279cc4a95e0e0020d1c01fba7e) + +498. [func] marcin + Implemented DHCPv6 option values configuration using configuration + manager. In order to set values for data fields carried by the + particular option, user specifies the string of hexadecimal digits + that is in turn converted to binary data and stored into option buffer. + More user friendly way of option content specification is planned. + (Trac #2318, git e75c686cd9c14f4d6c2a242a0a0853314704fee9) + +497. [bug] jinmei + Fixed several issues in isc-sysinfo: + - make sure it doesn't report a negative value for free memory + size (this happened on FreeBSD, but can possibly occur on other + BSD variants) + - correctly identifies the SMP support in kernel on FreeBSD + - print more human readable uptime as well as the time in seconds + (Trac #2297, git 59a449f506948e2371ffa87dcd19059388bd1657) + +496. [func] tomek + DHCPv6 Allocation Engine implemented. It allows address allocation + from the configured subnets/pools. It currently features a single + allocator: IterativeAllocator, which assigns addresses iteratively. + Other allocators (hashed, random) are planned. + (Trac #2324, git 8aa188a10298e3a55b725db36502a99d2a8d638a) + +495. [func] team + b10-auth now handles reconfiguration of data sources in + background using a separate thread. This means even if the new + configuration includes a large amount of data to be loaded into + memory (very large zones and/or a very large number of zones), + the reconfiguration doesn't block query handling. + (Multiple Trac tickets up to #2211) + +494. [bug] jinmei + Fixed a problem that shutting down BIND 10 kept some of the + processes alive. It was two-fold: when the main bind10 process + started as a root, started b10-sockcreator with the privilege, and + then dropped the privilege, the bind10 process cannot kill the + sockcreator via signal any more (when it has to), but it kept + sending the signal and didn't stop. Also, when running on Python + 3.1 (or older), the sockcreator had some additional file + descriptor open, which prevented it from exiting even after the + bind10 process terminated. Now the bind10 process simply gives up + killing a subprocess if it fails due to lack of permission, and it + makes sure the socket creator is spawned without any unnecessary + FDs open. + (Trac #1858, git 405d85c8a0042ba807a3a123611ff383c4081ee1) + +493. [build] jinmei + Fixed build failure with newer versions of clang++. These + versions are stricter regarding "unused variable" and "unused + (driver) arguments" warnings, and cause fatal build error + with -Werror. The affected versions of clang++ include Apple's + customized version 4.1 included in Xcode 4.5.1. So this fix + will solve build errors for Mac OS X that uses newer versions of + Xcode. + (Trac #2340, git 55be177fc4f7537143ab6ef5a728bd44bdf9d783, + 3e2a372012e633d017a97029d13894e743199741 and commits before it + with [2340] in the commit log) + 492. [func] tomek libdhcpsrv: The DHCP Configuration Manager is now able to store information about IPv4 subnets and pools. It is still not possible @@ -27,7 +107,7 @@ 488. [build] jinmei On configure, changed the search order for Python executable. - It first ties more specific file names such as "python3.2" before + It first tries more specific file names such as "python3.2" before more generic "python3". This will prevent configure failure on Mac OS X that installs Python3 via recent versions of Homebrew. (Trac #2339, git 88db890d8d1c64de49be87f03c24a2021bcf63da) diff --git a/Makefile.am b/Makefile.am index 1ed0d6325ffa9aabea8da6fad2352fed2c0822a2..2f3ce852047c2dc615242735d18b3c127383f049 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -ACLOCAL_AMFLAGS = -I m4macros ${ACLOCAL_FLAGS} +ACLOCAL_AMFLAGS = -I m4macros -I examples/m4 ${ACLOCAL_FLAGS} # ^^^^^^^^ This has to be the first line and cannot come later in this # Makefile.am due to some bork in some versions of autotools. diff --git a/configure.ac b/configure.ac index 1c7c0d81c30c7514a3b3f2aed8f79d94492be4bf..f4324996010d87928a1578bccdff3a0573dcdbe1 100644 --- a/configure.ac +++ b/configure.ac @@ -12,6 +12,20 @@ AC_CONFIG_MACRO_DIR([m4macros]) # Checks for programs. AC_PROG_CXX +# Enable low-performing debugging facilities? This option optionally +# enables some debugging aids that perform slowly and hence aren't built +# by default. +AC_ARG_ENABLE([debug], + AS_HELP_STRING([--enable-debug], + [enable debugging (default is no)]), + [case "${enableval}" in + yes) debug_enabled=yes ;; + no) debug_enabled=no ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;; + esac],[debug_enabled=no]) +AM_CONDITIONAL([DEBUG_ENABLED], [test x$debug_enabled = xyes]) +AM_COND_IF([DEBUG_ENABLED], [AC_DEFINE([ENABLE_DEBUG], [1], [Enable low-performing debugging facilities?])]) + # Libtool configuration # @@ -50,25 +64,9 @@ AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes") # Linker options -# check -R and -Wl,-R rather than gcc specific -rpath to be as portable -# as possible. -AC_MSG_CHECKING([whether -R flag is available in linker]) -LDFLAGS_SAVED="$LDFLAGS" -LDFLAGS="$LDFLAGS -R/usr/lib" -AC_TRY_LINK([],[], - [ AC_MSG_RESULT(yes) - rpath_flag=-R - ],[ AC_MSG_RESULT(no) - AC_MSG_CHECKING([whether -Wl,-R flag is available in linker]) - LDFLAGS="$LDFLAGS_SAVED -Wl,-R" - AC_TRY_LINK([], [], - [ AC_MSG_RESULT(yes) - rpath_flag=-Wl,-R - ],[ AC_MSG_RESULT(no) - rpath_flag=no - ]) - ]) -LDFLAGS=$LDFLAGS_SAVED +# check -R, "-Wl,-R" or -rpath (we share the AX function defined in +# examples/m4) +AX_ISC_RPATH # Compiler dependent settings: define some mandatory CXXFLAGS here. # We also use a separate variable B10_CXXFLAGS. This will (and should) be @@ -203,6 +201,10 @@ case "$host" in CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__" # "now" binding is necessary to prevent deadlocks in C++ static initialization code LDFLAGS="$LDFLAGS -z now" + # Destroying locked mutexes, condition variables being waited + # on, etc. are undefined behavior on Solaris, so we set it as + # such here. + AC_DEFINE([HAS_UNDEFINED_PTHREAD_BEHAVIOR], [1], [Does this platform have some undefined pthreads behavior?]) ;; *-apple-darwin*) # Starting with OSX 10.7 (Lion) we must choose which IPv6 API to use @@ -314,10 +316,10 @@ fi # modules, we embed the path to the modules when possible. We do this even # when the path is known in the common operational environment (e.g. when # it's stored in a common "hint" file) for simplicity. -if test $rpath_flag != no; then +if test "x$ISC_RPATH_FLAG" != "x"; then python_rpath= for flag in ${PYTHON_LDFLAGS}; do - python_rpath="${python_rpath} `echo $flag | sed -ne "s/^\(\-L\)/${rpath_flag}/p"`" + python_rpath="${python_rpath} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`" done PYTHON_LDFLAGS="${PYTHON_LDFLAGS} ${python_rpath}" fi @@ -683,10 +685,10 @@ for flag in ${BOTAN_LIBS}; do done # See python_rpath for some info on why we do this -if test $rpath_flag != no; then +if test "x$ISC_RPATH_FLAG" != "x"; then BOTAN_RPATH= for flag in ${BOTAN_LIBS}; do - BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne "s/^\(\-L\)/${rpath_flag}/p"`" + BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`" done AC_SUBST(BOTAN_RPATH) @@ -1121,6 +1123,13 @@ AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Chec AC_PATH_PROG(VALGRIND, valgrind, no) AM_CONDITIONAL(HAVE_VALGRIND, test "x$VALGRIND" != "xno") +# Also check for valgrind headers +# We could consider adding them to the source code tree, as this +# is the encouraged method of using them; they are BSD-licensed. +# However, until we find that this is a problem, we just use +# the system-provided ones, if available +AC_CHECK_HEADERS(valgrind/valgrind.h, [AC_DEFINE([HAVE_VALGRIND_HEADERS], [1], [Check valgrind headers])]) + found_valgrind="not found" if test "x$VALGRIND" != "xno"; then found_valgrind="found" @@ -1159,6 +1168,7 @@ AC_CONFIG_FILES([Makefile src/bin/bindctl/Makefile src/bin/bindctl/tests/Makefile src/bin/cfgmgr/Makefile + src/bin/cfgmgr/local_plugins/Makefile src/bin/cfgmgr/plugins/Makefile src/bin/cfgmgr/plugins/tests/Makefile src/bin/cfgmgr/tests/Makefile @@ -1327,6 +1337,7 @@ AC_OUTPUT([doc/version.ent src/bin/zonemgr/tests/zonemgr_test src/bin/zonemgr/run_b10-zonemgr.sh src/bin/sysinfo/sysinfo.py + src/bin/sysinfo/run_sysinfo.sh src/bin/stats/stats.py src/bin/stats/stats_httpd.py src/bin/bind10/bind10_src.py @@ -1405,6 +1416,7 @@ AC_OUTPUT([doc/version.ent chmod +x src/bin/loadzone/run_loadzone.sh chmod +x src/bin/loadzone/tests/correct/correct_test.sh chmod +x src/bin/loadzone/tests/error/error_test.sh + chmod +x src/bin/sysinfo/run_sysinfo.sh chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh chmod +x src/bin/msgq/run_msgq.sh chmod +x src/bin/msgq/tests/msgq_test @@ -1480,6 +1492,7 @@ Features: $enable_features Developer: + Enable Debugging: $debug_enabled Google Tests: $enable_gtest Valgrind: $found_valgrind C++ Code Coverage: $USE_LCOV diff --git a/doc/Doxyfile b/doc/Doxyfile index f6b9fa00948b628c7efcb31ca8a7bf2e04b14b68..cc3b5959d70e58d43fff364c680d8cebc1d5bc1c 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -580,7 +580,7 @@ INPUT = ../src/lib/exceptions ../src/lib/cc \ ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \ ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \ ../src/lib/util/threads/ ../src/lib/resolve ../src/lib/acl \ - ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \ + ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \ ../tests/tools/perfdhcp devel # This tag can be used to specify the character encoding of the source files diff --git a/doc/devel/02-dhcp.dox b/doc/devel/02-dhcp.dox index a4bbff9955baa3d743a8473424663f6b0670e211..5c59daafd2b6edcf2b6107c1326eb5c1eb7f7894 100644 --- a/doc/devel/02-dhcp.dox +++ b/doc/devel/02-dhcp.dox @@ -57,76 +57,4 @@ * that does not support msgq. That is useful for embedded environments. * It may also be useful in validation. * - * @page libdhcp libdhcp++ - * - * @section libdhcpIntro Libdhcp++ Library Introduction - * - * libdhcp++ is an all-purpose DHCP-manipulation library, written in - * C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6 - * options parsing and ssembly, interface detection (currently on - * Linux systems only) and socket operations. Following classes are - * implemented: - * - * - isc::dhcp::Pkt4 - represents DHCPv4 packet. - * - isc::dhcp::Pkt6 - represents DHCPv6 packet. - * - * There are two pointer types defined: Pkt4Ptr and Pkt6Ptr. They are - * smart pointer and are using boost::shared_ptr. There are not const - * versions defined, as we assume that hooks can modify any aspect of - * the packet at almost any stage of processing. - * - * Both packets use collection of Option objects to represent DHCPv4 - * and DHCPv6 options. The base class -- Option -- can be used to - * represent generic option that contains collection of - * bytes. Depending on if the option is instantiated as v4 or v6 - * option, it will adjust its header (DHCPv4 options use 1 octet for - * type and 1 octet for length, while DHCPv6 options use 2 bytes for - * each). - * - * There are many specialized classes that are intended to handle options with - * specific content: - * - isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses; - * - isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses; - * - isc::dhcp::Option6IAAddr -- DHCPv6 option, represents IAADDR_OPTION (an option that - * contains IPv6 address with extra parameters); - * - isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions. - * - * All options can store sub-options (i.e. options that are stored within option - * rather than in a message directly). This functionality is commonly used in - * DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(), - * isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used - * for that purpose. - * - * @section libdhcpIfaceMgr Interface Manager - * - * Interface Manager (or IfaceMgr) is an abstraction layer about low-level - * network operations. In particlar, it provides information about existing - * network interfaces See isc::dhcp::IfaceMgr::Iface class and - * isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface(). - * - * Currently there is interface detection is implemented in Linux only. There - * are plans to implement such support for other OSes, but they remain low - * priority for now. - * - * Generic parts of the code are isc::dhcp::IfaceMgr class in - * src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate - * files, e.g. iface_mgr_linux.cc. Such separation should be maintained when - * additional code will be developed. - * - * For systems that interface detection is not supported on, there is a stub - * mechanism implemented. It assumes that interface name is read from a text - * file. This is a temporary solution and will be removed as soon as proper - * interface detection is implemented. It is not going to be developed further. - * To use this feature, store interfaces.txt file. It uses a simple syntax. - * Each line represents an interface name, followed by IPv4 or IPv6 address - * that follows it. This is usually link-local IPv6 address that the server - * should bind to. In theory this mechanism also supports IPv4, but it was - * never tested. The code currently supports only a single interface defined - * that way. - * - * Another useful methods are dedicated to transmission - * (isc::dhcp::IfaceMgr::send(), 2 overloads) and reception - * (isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()). - * Note that receive4() and receive6() methods may return NULL, e.g. - * when timeout is reached or if dhcp daemon receives a signal. */ \ No newline at end of file diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox index a36d2270aa347cb588f5aef06bfc38a206a40b26..db42c147dbbc66ff3f02e123848fe88055f5044b 100644 --- a/doc/devel/mainpage.dox +++ b/doc/devel/mainpage.dox @@ -29,6 +29,10 @@ * - @subpage libdhcp * - @subpage libdhcpIntro * - @subpage libdhcpIfaceMgr + * - @subpage libdhcpsrv + * - @subpage leasemgr + * - @subpage cfgmgr + * - @subpage allocengine * - @subpage dhcp-database-backends * - @subpage perfdhcpInternals * diff --git a/examples/README b/examples/README index 65f777b6938e4f05018c18391468fac8378e9a20..08f53faba0eddb27744a20b6c017fba2619ffb48 100644 --- a/examples/README +++ b/examples/README @@ -30,3 +30,18 @@ to the configure.ac file: sinclude(m4/ax_boost_include.m4) sinclude(m4/ax_isc_bind10.m4) (and same for other m4 files as they are added under m4/) + +On some systems, espeically if you have installed the BIND 10 +libraries in an uncommon path, programs linked with the BIND 10 +library may not work at run time due to the "missing" shared library. +Normally, you should be able to avoid this problem by making sure +to invoking the program explicitly specifying the path to the library, +e.g., "LD_LIBRARY_PATH=/usr/local/lib/bind10 ./my_bind10_app", or +you may not even notice the issue if you have installed BIND 10 +library in a common library path on your system (sometimes you may +still need to run ldconfig(8) beforehand). Another option is to embed +the path to the library in your program. While this approach is +controversial, and some people rather choose the alternatives, we +provide a helper tool in case you want to use this option: see the +lines using BIND10_RPATH in the sample configure.ac file of this +directory. diff --git a/examples/configure.ac b/examples/configure.ac index 937968760d4d881d81f01b3cc7df2ac6d23067c0..37515d94724bebabba3bd3b1467e23c9062316e7 100644 --- a/examples/configure.ac +++ b/examples/configure.ac @@ -14,7 +14,21 @@ AC_LANG([C++]) # Checks for BIND 10 headers and libraries AX_ISC_BIND10 -# For the example host program, we require the BIND 10 DNS library +# We use -R, -rpath etc so the resulting program will be more likekly to +# "just work" by default. Embedding a specific library path is a controversial +# practice, though; if you don't like it you can remove the following setting. +if test "x$BIND10_RPATH" != "x"; then + LDFLAGS="$LDFLAGS $BIND10_RPATH" +fi + +# For the example host program, we require some socket API library +# and the BIND 10 DNS library. + +# In practice, these are specific to Solaris, but wouldn't do any harm for +# others except for the checking overhead. +AC_SEARCH_LIBS(inet_pton, [nsl]) +AC_SEARCH_LIBS(recvfrom, [socket]) + if test "x$BIND10_DNS_LIB" = "x"; then AC_MSG_ERROR([unable to find BIND 10 DNS library needed to build 'host']) fi diff --git a/examples/m4/ax_boost_include.m4 b/examples/m4/ax_boost_include.m4 index e41614d884753da3b3b32c3ee6dbc8528020a66a..77d19cafde5dbe56676d7f6c30854f895e08a21c 100644 --- a/examples/m4/ax_boost_include.m4 +++ b/examples/m4/ax_boost_include.m4 @@ -34,7 +34,7 @@ if test -z "$with_boost_include"; then fi done fi -CPPFLAGS_SAVES="$CPPFLAGS" +CPPFLAGS_SAVED="$CPPFLAGS" if test "${boost_include_path}" ; then BOOST_CPPFLAGS="-I${boost_include_path}" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" @@ -57,7 +57,7 @@ AC_TRY_COMPILE([ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"], [AC_MSG_RESULT(yes)]) -CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF" +CPPFLAGS="$CPPFLAGS_SAVED $CPPFLAGS_BOOST_THREADCONF" AC_SUBST(BOOST_CPPFLAGS) AC_LANG_RESTORE diff --git a/examples/m4/ax_isc_bind10.m4 b/examples/m4/ax_isc_bind10.m4 index 63e028c407b6b6d0e29d52ec5fb13e4680dfd745..75c37c5f703ff4dc43b550d7bb5475acaafe9ff8 100644 --- a/examples/m4/ax_isc_bind10.m4 +++ b/examples/m4/ax_isc_bind10.m4 @@ -1,4 +1,4 @@ -dnl @synopsis AX_BIND10 +dnl @synopsis AX_ISC_BIND10 dnl dnl @summary figure out how to build C++ programs using ISC BIND 10 libraries dnl @@ -20,9 +20,18 @@ dnl Checks for other BIND 10 module libraries are option, as not all dnl applications need all libraries. The main configure.ac can handle any dnl missing library as fatal by checking whether the corresponding dnl BIND10_xxx_LIB is defined. +dnl +dnl In addition, it sets the BIND10_RPATH variable to a usable linker option +dnl to embed the path to the BIND 10 library to the programs that are to be +dnl linked with the library. If the developer wants to use the option, +dnl it can be used as follows: +dnl if test "x$BIND10_RPATH" != "x"; then +dnl LDFLAGS="$LDFLAGS $BIND10_RPATH" +dnl fi AC_DEFUN([AX_ISC_BIND10], [ AC_REQUIRE([AX_BOOST_INCLUDE]) +AC_REQUIRE([AX_ISC_RPATH]) AC_LANG_SAVE AC_LANG([C++]) @@ -42,19 +51,20 @@ if test "$bind10_inc_path" = "no"; then fi done fi -CPPFLAGS_SAVES="$CPPFLAGS" +CPPFLAGS_SAVED="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" # boost headers will be used in buffer.h if test "${bind10_inc_path}" != "no"; then BIND10_CPPFLAGS="-I${bind10_inc_path}" CPPFLAGS="$CPPFLAGS $BIND10_CPPFLAGS" fi AC_CHECK_HEADERS([util/buffer.h],, - AC_MSG_ERROR([Missing a commonly used BIND 10 header files])) -CPPFLAGS="$CPPFLAGS_SAVES" + AC_MSG_ERROR([Missing a commonly used BIND 10 header file])) +CPPFLAGS="$CPPFLAGS_SAVED" AC_SUBST(BIND10_CPPFLAGS) # Check for BIND10 libraries CPPFLAGS_SAVED="$CPPFLAGS" -CPPFLAGS="$CPPFLAGS $BIND10_CPPFLAGS" +CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS $BIND10_CPPFLAGS" AC_ARG_WITH(bind10-lib, AS_HELP_STRING([--with-bind10-lib=PATH], @@ -70,21 +80,25 @@ fi # make sure we have buildable libraries AC_MSG_CHECKING([for BIND 10 common library]) BIND10_COMMON_LIB="-lb10-util -lb10-exceptions" -LDFLAGS="$LDFLAGS $BIND10_LDFLAGS" +LDFLAGS_SAVED="$LDFLAGS" +LDFLAGS_CHECK_COMMON="$LDFLAGS $BIND10_LDFLAGS" +LIBS_SAVED="$LIBS" LIBS="$LIBS $BIND10_COMMON_LIB" for d in $bind10_lib_dirs do - LDFLAGS_SAVED="$LDFLAGS" - LDFLAGS="$LDFLAGS -L$d" + LDFLAGS="$LDFLAGS_CHECK_COMMON -L$d" AC_TRY_LINK([ #include ],[ isc::util::OutputBuffer buffer(0); -], [BIND10_LDFLAGS="-L${d}"]) +], [BIND10_LDFLAGS="-L${d}" + if test "x$ISC_RPATH_FLAG" != "x"; then + BIND10_RPATH="${ISC_RPATH_FLAG}${d}" + fi + ]) if test "x$BIND10_LDFLAGS" != "x"; then break fi - LDFLAGS="$LDFLAGS_SAVED" done if test "x$BIND10_LDFLAGS" != "x"; then AC_MSG_RESULT(yes) @@ -94,7 +108,7 @@ else fi # restore LIBS once at this point -LIBS="$LIBS_SAVES" +LIBS="$LIBS_SAVED" AC_SUBST(BIND10_LDFLAGS) AC_SUBST(BIND10_COMMON_LIB) @@ -111,12 +125,12 @@ isc::dns::RRType rrtype(1); ], [BIND10_DNS_LIB="-lb10-dns++" AC_MSG_RESULT(yes)], [AC_MSG_RESULT(no)]) -LIBS="$LIBS_SAVES" +LIBS="$LIBS_SAVED" AC_SUBST(BIND10_DNS_LIB) # Restore other flags CPPFLAGS="$CPPFLAGS_SAVED" -LDFLAGS="$LDFLAGS_SAVES" +LDFLAGS="$LDFLAGS_SAVED" AC_LANG_RESTORE ])dnl AX_ISC_BIND10 diff --git a/examples/m4/ax_isc_rpath.m4 b/examples/m4/ax_isc_rpath.m4 new file mode 100644 index 0000000000000000000000000000000000000000..91d9b8af1f2e247207462731d78bc717cb92da4f --- /dev/null +++ b/examples/m4/ax_isc_rpath.m4 @@ -0,0 +1,46 @@ +dnl @synopsis AX_ISC_RPATH +dnl +dnl @summary figure out whether and which "rpath" linker option is available +dnl +dnl This macro checks if the linker supports an option to embed a path +dnl to a runtime library (often installed in an uncommon place), such as +dnl gcc's -rpath option. If found, it sets the ISC_RPATH_FLAG variable to +dnl the found option flag. The main configure.ac can use it as follows: +dnl if test "x$ISC_RPATH_FLAG" != "x"; then +dnl LDFLAGS="$LDFLAGS ${ISC_RPATH_FLAG}/usr/local/lib/some_library" +dnl fi + +AC_DEFUN([AX_ISC_RPATH], [ + +# We'll tweak both CXXFLAGS and CCFLAGS so this function will work whichever +# language is used in the main script. Note also that it's not LDFLAGS; +# technically this is a linker flag, but we've noticed $LDFLAGS can be placed +# where the compiler could interpret it as a compiler option, leading to +# subtle failure mode. So, in the check below using the compiler flag is +# safer (in the actual Makefiles the flag should be set in LDFLAGS). +CXXFLAGS_SAVED="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS -Wl,-R/usr/lib" +CCFLAGS_SAVED="$CCFLAGS" +CCFLAGS="$CCFLAGS -Wl,-R/usr/lib" + +# check -Wl,-R and -R rather than gcc specific -rpath to be as portable +# as possible. -Wl,-R seems to be safer, so we try it first. In some cases +# -R is not actually recognized but AC_TRY_LINK doesn't fail due to that. +AC_MSG_CHECKING([whether -Wl,-R flag is available in linker]) +AC_TRY_LINK([],[], + [ AC_MSG_RESULT(yes) + ISC_RPATH_FLAG=-Wl,-R + ],[ AC_MSG_RESULT(no) + AC_MSG_CHECKING([whether -R flag is available in linker]) + CXXFLAGS="$CXXFLAGS_SAVED -R" + CCFLAGS="$CCFLAGS_SAVED -R" + AC_TRY_LINK([], [], + [ AC_MSG_RESULT([yes; note that -R is more sensitive about the position in option arguments]) + ISC_RPATH_FLAG=-R + ],[ AC_MSG_RESULT(no) ]) + ]) + +CXXFLAGS=$CXXFLAGS_SAVED +CCFLAGS=$CCFLAGS_SAVED + +])dnl AX_ISC_RPATH diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am index 9eee9d4d54af5fcfb79743762da1ea5453418971..7d29fccdcb9c16d3583c0811331b5856b68044c9 100644 --- a/src/bin/auth/Makefile.am +++ b/src/bin/auth/Makefile.am @@ -54,7 +54,8 @@ b10_auth_SOURCES += auth_log.cc auth_log.h b10_auth_SOURCES += auth_config.cc auth_config.h b10_auth_SOURCES += command.cc command.h b10_auth_SOURCES += common.h common.cc -b10_auth_SOURCES += statistics.cc statistics.h +b10_auth_SOURCES += statistics.cc statistics.h statistics_items.h +b10_auth_SOURCES += datasrc_clients_mgr.h b10_auth_SOURCES += datasrc_config.h datasrc_config.cc b10_auth_SOURCES += main.cc @@ -73,7 +74,6 @@ b10_auth_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la b10_auth_LDADD += $(top_builddir)/src/lib/log/libb10-log.la b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libb10-xfr.la b10_auth_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la -b10_auth_LDADD += $(top_builddir)/src/lib/statistics/libb10-statistics.la b10_auth_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la b10_auth_LDADD += $(SQLITE_LIBS) diff --git a/src/bin/auth/auth.spec.pre.in b/src/bin/auth/auth.spec.pre.in index a471b7a87ff3b84478a4588e8ad37c678851a949..30a455d8e0198cc3bf68a0d03944f84bde5c46a4 100644 --- a/src/bin/auth/auth.spec.pre.in +++ b/src/bin/auth/auth.spec.pre.in @@ -145,7 +145,7 @@ "item_type": "integer", "item_optional": false, "item_default": 0, - "item_title": "Queries TCP ", + "item_title": "Queries TCP", "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially" }, { @@ -180,14 +180,6 @@ "item_title": "Received status requests", "item_description": "The number of total request counts whose opcode is status" }, - { - "item_name": "opcode.reserved3", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 3", - "item_description": "The number of total request counts whose opcode is 3 (reserved)" - }, { "item_name": "opcode.notify", "item_type": "integer", @@ -205,84 +197,12 @@ "item_description": "The number of total request counts whose opcode is update" }, { - "item_name": "opcode.reserved6", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 6", - "item_description": "The number of total request counts whose opcode is 6 (reserved)" - }, - { - "item_name": "opcode.reserved7", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 7", - "item_description": "The number of total request counts whose opcode is 7 (reserved)" - }, - { - "item_name": "opcode.reserved8", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 8", - "item_description": "The number of total request counts whose opcode is 8 (reserved)" - }, - { - "item_name": "opcode.reserved9", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 9", - "item_description": "The number of total request counts whose opcode is 9 (reserved)" - }, - { - "item_name": "opcode.reserved10", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 10", - "item_description": "The number of total request counts whose opcode is 10 (reserved)" - }, - { - "item_name": "opcode.reserved11", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 11", - "item_description": "The number of total request counts whose opcode is 11 (reserved)" - }, - { - "item_name": "opcode.reserved12", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 12", - "item_description": "The number of total request counts whose opcode is 12 (reserved)" - }, - { - "item_name": "opcode.reserved13", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 13", - "item_description": "The number of total request counts whose opcode is 13 (reserved)" - }, - { - "item_name": "opcode.reserved14", - "item_type": "integer", - "item_optional": true, - "item_default": 0, - "item_title": "Received requests opcode 14", - "item_description": "The number of total request counts whose opcode is 14 (reserved)" - }, - { - "item_name": "opcode.reserved15", + "item_name": "opcode.other", "item_type": "integer", "item_optional": true, "item_default": 0, - "item_title": "Received requests opcode 15", - "item_description": "The number of total request counts whose opcode is 15 (reserved)" + "item_title": "Received requests opcode other", + "item_description": "The number of total request counts whose opcode is other (not well-known)" }, { "item_name": "rcode.noerror", @@ -373,52 +293,68 @@ "item_description": "The number of total responses with rcode 10 (NOTZONE)" }, { - "item_name": "rcode.reserved11", + "item_name": "rcode.badsigvers", "item_type": "integer", "item_optional": true, "item_default": 0, - "item_title": "Sent response with rcode 11", - "item_description": "The number of total responses with rcode 11 (reserved)" + "item_title": "Sent 'EDNS version not implemented' response", + "item_description": "The number of total responses with rcode 16 (BADVERS)" }, { - "item_name": "rcode.reserved12", + "item_name": "rcode.badkey", "item_type": "integer", "item_optional": true, "item_default": 0, - "item_title": "Sent response with rcode 12", - "item_description": "The number of total responses with rcode 12 (reserved)" + "item_title": "Sent 'Key not recognized' response", + "item_description": "The number of total responses with rcode 17 (BADKEY)" }, { - "item_name": "rcode.reserved13", + "item_name": "rcode.badtime", "item_type": "integer", "item_optional": true, "item_default": 0, - "item_title": "Sent response with rcode 13", - "item_description": "The number of total responses with rcode 13 (reserved)" + "item_title": "Sent 'Signature out of time window' response", + "item_description": "The number of total responses with rcode 18 (BADTIME)" }, { - "item_name": "rcode.reserved14", + "item_name": "rcode.badmode", "item_type": "integer", "item_optional": true, "item_default": 0, - "item_title": "Sent response with rcode 14", - "item_description": "The number of total responses with rcode 14 (reserved)" + "item_title": "Sent 'Bad TKEY Mode' response", + "item_description": "The number of total responses with rcode 19 (BADMODE)" }, { - "item_name": "rcode.reserved15", + "item_name": "rcode.badname", "item_type": "integer", "item_optional": true, "item_default": 0, - "item_title": "Sent response with rcode 15", - "item_description": "The number of total responses with rcode 15 (reserved)" + "item_title": "Sent 'Duplicate key name' response", + "item_description": "The number of total responses with rcode 20 (BADNAME)" }, { - "item_name": "rcode.badvers", + "item_name": "rcode.badalg", "item_type": "integer", "item_optional": true, "item_default": 0, - "item_title": "Sent 'EDNS version not implemented' response", - "item_description": "The number of total responses with rcode 16 (BADVERS)" + "item_title": "Sent 'Algorithm not supported' response", + "item_description": "The number of total responses with rcode 21 (BADALG)" + }, + { + "item_name": "rcode.badtrunc", + "item_type": "integer", + "item_optional": true, + "item_default": 0, + "item_title": "Sent 'Bad Truncation' response", + "item_description": "The number of total responses with rcode 22 (BADTRUNC)" + }, + { + "item_name": "rcode.other", + "item_type": "integer", + "item_optional": true, + "item_default": 0, + "item_title": "Sent responses with rcode other", + "item_description": "The number of total responses with rcode other (not well-known)" } ] } diff --git a/src/bin/auth/auth_config.h b/src/bin/auth/auth_config.h index 8e816a3fb47b35c82942e42e869c7c8c1e3dc9fd..57fd270faacffa7eae79f2dad36a4b864247acf6 100644 --- a/src/bin/auth/auth_config.h +++ b/src/bin/auth/auth_config.h @@ -18,8 +18,8 @@ #include -#ifndef __CONFIG_H -#define __CONFIG_H 1 +#ifndef CONFIG_H +#define CONFIG_H 1 class AuthSrv; @@ -195,7 +195,7 @@ void configureAuthServer(AuthSrv& server, AuthConfigParser* createAuthConfigParser(AuthSrv& server, const std::string& config_id); -#endif // __CONFIG_H +#endif // CONFIG_H // Local Variables: // mode: c++ diff --git a/src/bin/auth/auth_log.cc b/src/bin/auth/auth_log.cc index d41eaeab7797a9ef1dbeb36f47623beccb87abc6..fae7bd35a3df91ccf0776601f63ec03d809dbbed 100644 --- a/src/bin/auth/auth_log.cc +++ b/src/bin/auth/auth_log.cc @@ -21,6 +21,12 @@ namespace auth { isc::log::Logger auth_logger("auth"); +const int DBG_AUTH_START = DBGLVL_START_SHUT; +const int DBG_AUTH_SHUT = DBGLVL_START_SHUT; +const int DBG_AUTH_OPS = DBGLVL_COMMAND; +const int DBG_AUTH_DETAIL = DBGLVL_TRACE_BASIC; +const int DBG_AUTH_MESSAGES = DBGLVL_TRACE_DETAIL_DATA; + } // namespace auth } // namespace isc diff --git a/src/bin/auth/auth_log.h b/src/bin/auth/auth_log.h index 33d44328771b95f234b57a32ab2d910f0aa56d94..52b973ecc9a75e93ad64d743a0fa06bb07463f35 100644 --- a/src/bin/auth/auth_log.h +++ b/src/bin/auth/auth_log.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __AUTH_LOG__H -#define __AUTH_LOG__H +#ifndef AUTH_LOG_H +#define AUTH_LOG_H #include #include @@ -28,21 +28,21 @@ namespace auth { /// output. // Debug messages indicating normal startup are logged at this debug level. -const int DBG_AUTH_START = DBGLVL_START_SHUT; +extern const int DBG_AUTH_START; // Debug messages upon shutdown -const int DBG_AUTH_SHUT = DBGLVL_START_SHUT; +extern const int DBG_AUTH_SHUT; // Debug level used to log setting information (such as configuration changes). -const int DBG_AUTH_OPS = DBGLVL_COMMAND; +extern const int DBG_AUTH_OPS; // Trace detailed operations, including errors raised when processing invalid // packets. (These are not logged at severities of WARN or higher for fear // that a set of deliberately invalid packets set to the authoritative server // could overwhelm the logging.) -const int DBG_AUTH_DETAIL = DBGLVL_TRACE_BASIC; +extern const int DBG_AUTH_DETAIL; // This level is used to log the contents of packets received and sent. -const int DBG_AUTH_MESSAGES = DBGLVL_TRACE_DETAIL_DATA; +extern const int DBG_AUTH_MESSAGES; /// Define the logger for the "auth" module part of b10-auth. We could define /// a logger in each file, but we would want to define a common name to avoid @@ -53,4 +53,4 @@ extern isc::log::Logger auth_logger; } // namespace nsas } // namespace isc -#endif // __AUTH_LOG__H +#endif // AUTH_LOG_H diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes index ae7be1e3edad2cdc2194a8c89ea934759af812e6..37804995f782ea774ef9ce721fabfcc6ae4ee7e1 100644 --- a/src/bin/auth/auth_messages.mes +++ b/src/bin/auth/auth_messages.mes @@ -47,16 +47,116 @@ available. It is issued during server startup is an indication that the initialization is proceeding normally. % AUTH_CONFIG_LOAD_FAIL load of configuration failed: %1 -An attempt to configure the server with information from the configuration -database during the startup sequence has failed. (The reason for -the failure is given in the message.) The server will continue its -initialization although it may not be configured in the desired way. +An attempt to configure the server with information from the +configuration database during the startup sequence has failed. The +server will continue its initialization although it may not be +configured in the desired way. The reason for the failure is given in +the message. One common reason is that the server failed to acquire a +socket bound to a privileged port (53 for DNS). In that case the +reason in the log message should show something like "permission +denied", and the solution would be to restart BIND 10 as a super +(root) user. % AUTH_CONFIG_UPDATE_FAIL update of configuration failed: %1 At attempt to update the configuration the server with information from the configuration database has failed, the reason being given in the message. +% AUTH_DATASRC_CLIENTS_BUILDER_COMMAND data source builder received command: %1 +A debug message, showing when the separate thread for maintaining data +source clients receives a command from the manager. + +% AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR command execution failure: %1 +The separate thread for maintaining data source clients failed to complete a +command given by the main thread. In most cases this is some kind of +configuration or temporary error such as an attempt to load a non-existent +zone or a temporary DB connection failure. So the event is just logged and +the thread keeps running. In some rare cases, however, this may indicate an +internal bug and it may be better to restart the entire program, so the log +message should be carefully examined. + +% AUTH_DATASRC_CLIENTS_BUILDER_FAILED data source builder thread stopped due to an exception: %1 +The separate thread for maintaining data source clients has been +terminated due to some uncaught exception. When this happens, the +thread immediately terminates the entire process because the manager +cannot always catch this condition in a timely fashion and it would be +worse to keep running with such a half-broken state. This is really +an unexpected event and should generally indicate an internal bug. +It's advisable to file a bug report when this message is logged (and +b10-auth subsequently stops). + +% AUTH_DATASRC_CLIENTS_BUILDER_FAILED_UNEXPECTED data source builder thread stopped due to an unexpected exception +This is similar to AUTH_DATASRC_CLIENTS_BUILDER_FAILED, but the +exception type indicates it's not thrown either within the BIND 10 +implementation or other standard-compliant libraries. This may rather +indicate some run time failure than program errors. As in the other +failure case, the thread terminates the entire process immediately +after logging this message. + +% AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE loaded zone %1/%2 +This debug message is issued when the separate thread for maintaining data +source clients successfully loaded the named zone of the named class as a +result of the 'loadzone' command. + +% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR Error in data source configuration: %1 +The thread for maintaining data source clients has received a command to +reconfigure, but the parameter data (the new configuration) contains an +error. The most likely cause is that the datasource-specific configuration +data is not what the data source expects. The system is still running with +the data sources that were previously configured (i.e. as if the +configuration has not changed), and the configuration data needs to be +checked. +The specific problem is printed in the log message. + +% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_DATASRC_ERROR Error setting up data source: %1 +The thread for maintaining data source clients has received a command to +reconfigure, but a data source failed to set up. This may be a problem with +the data that is configured (e.g. unreadable files, inconsistent data, +parser problems, database connection problems, etc.), but it could be a bug +in the data source implementation as well. The system is still running with +the data sources that were previously configured (i.e. as if the +configuration has not changed). +The specific problem is printed in the log message. + +% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_ERROR Internal error setting up data source: %1 +The thread for maintaining data source clients has received a command to +reconfigure, but raised an exception while setting up data sources. This is +most likely an internal error in a data source, or a bug in the data source +or the system itself, but it is probably a good idea to verify the +configuration first. The system is still running with the data sources that +were previously configured (i.e. as if the configuration has not changed). +The specific problem is printed in the log message. + +% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_STARTED data source reconfiguration started +The thread for maintaining data source clients has received a command to +reconfigure, and has now started this process. + +% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed succesfully +The thread for maintaining data source clients has finished reconfiguring +the data source clients, and is now running with the new configuration. + +% AUTH_DATASRC_CLIENTS_BUILDER_STARTED data source builder thread started +A separate thread for maintaining data source clients has been started. + +% AUTH_DATASRC_CLIENTS_BUILDER_STOPPED data source builder thread stopped +The separate thread for maintaining data source clients has been stopped. + +% AUTH_DATASRC_CLIENTS_SHUTDOWN_ERROR error on waiting for data source builder thread: %1 +This indicates that the separate thread for maintaining data source +clients had been terminated due to an uncaught exception, and the +manager notices that at its own termination. This is not an expected +event, because the thread is implemented so it catches all exceptions +internally. So, if this message is logged it's most likely some internal +bug, and it would be nice to file a bug report. + +% AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR Unexpected error on waiting for data source builder thread +Some exception happens while waiting for the termination of the +separate thread for maintaining data source clients. This shouldn't +happen in normal conditions; it should be either fatal system level +errors such as severe memory shortage or some internal bug. If that +happens, and if it's not in the middle of terminating b10-auth, it's +probably better to stop and restart it. + % AUTH_DATA_SOURCE data source database file: %1 This is a debug message produced by the authoritative server when it accesses a datebase data source, listing the file that is being accessed. @@ -83,11 +183,6 @@ has requested the keyring holding TSIG keys from the configuration database. It is issued during server startup is an indication that the initialization is proceeding normally. -% AUTH_LOAD_ZONE loaded zone %1/%2 -This debug message is issued during the processing of the 'loadzone' command -when the authoritative server has successfully loaded the named zone of the -named class. - % AUTH_MEM_DATASRC_DISABLED memory data source is disabled for class %1 This is a debug message reporting that the authoritative server has discovered that the memory data source is disabled for the given class. diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc index e73eb528dbfdc44fe36c00f656c0d795e637d0c6..dca8fd0fc245ce37e2f6b8445dbd480872d278d9 100644 --- a/src/bin/auth/auth_srv.cc +++ b/src/bin/auth/auth_srv.cc @@ -26,7 +26,6 @@ #include #include -#include #include #include @@ -53,6 +52,7 @@ #include #include #include +#include #include #include @@ -85,6 +85,7 @@ using namespace isc::xfr; using namespace isc::asiolink; using namespace isc::asiodns; using namespace isc::server_common::portconfig; +using isc::auth::statistics::Counters; namespace { // A helper class for cleaning up message renderer. @@ -260,7 +261,7 @@ public: AbstractSession* xfrin_session_; /// Query counters for statistics - AuthCounters counters_; + Counters counters_; /// Addresses we listen on AddressList listen_addresses_; @@ -268,24 +269,8 @@ public: /// The TSIG keyring const shared_ptr* keyring_; - /// The data source client list - AuthSrv::DataSrcClientListsPtr datasrc_client_lists_; - - shared_ptr getDataSrcClientList( - const RRClass& rrclass) - { - // TODO: Debug-build only check - if (!mutex_.locked()) { - isc_throw(isc::Unexpected, "Not locked!"); - } - const std::map >:: - const_iterator it(datasrc_client_lists_->find(rrclass)); - if (it == datasrc_client_lists_->end()) { - return (shared_ptr()); - } else { - return (it->second); - } - } + /// The data source client list manager + auth::DataSrcClientsMgr datasrc_clients_mgr_; /// Bind the ModuleSpec object in config_session_ with /// isc:config::ModuleSpec::validateStatistics. @@ -301,29 +286,29 @@ public: /// \brief Resume the server /// - /// This is a wrapper call for DNSServer::resume(done), if 'done' is true, - /// the Rcode set in the given Message is counted in the statistics - /// counter. + /// This is a wrapper call for DNSServer::resume(done). Query/Response + /// statistics counters are incremented in this method. /// /// This method is expected to be called by processMessage() /// /// \param server The DNSServer as passed to processMessage() /// \param message The response as constructed by processMessage() - /// \param done If true, the Rcode from the given message is counted, - /// this value is then passed to server->resume(bool) + /// \param stats_attrs Query/response attributes for statistics which is + /// not in \p messsage. + /// Note: This parameter is modified inside this method + /// to store whether the answer has been sent and + /// the response is truncated. + /// \param done If true, it indicates there is a response. + /// this value will be passed to server->resume(bool) void resumeServer(isc::asiodns::DNSServer* server, isc::dns::Message& message, - bool done); - - mutable util::thread::Mutex mutex_; + statistics::QRAttributes& stats_attrs, + const bool done); private: bool xfrout_connected_; AbstractXfroutClient& xfrout_client_; - /// Increment query counter - void incCounter(const int protocol); - // validateStatistics bool validateStatistics(isc::data::ConstElementPtr data) const; @@ -336,8 +321,6 @@ AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client, xfrin_session_(NULL), counters_(), keyring_(NULL), - datasrc_client_lists_(new std::map >()), ddns_base_forwarder_(ddns_forwarder), ddns_forwarder_(NULL), xfrout_connected_(false), @@ -488,6 +471,11 @@ AuthSrv::getIOService() { return (impl_->io_service_); } +isc::auth::DataSrcClientsMgr& +AuthSrv::getDataSrcClientsMgr() { + return (impl_->datasrc_clients_mgr_); +} + void AuthSrv::setXfrinSession(AbstractSession* xfrin_session) { impl_->xfrin_session_ = xfrin_session; @@ -509,6 +497,12 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message, OutputBuffer& buffer, DNSServer* server) { InputBuffer request_buffer(io_message.getData(), io_message.getDataSize()); + statistics::QRAttributes stats_attrs; + + // statistics: check transport carrying the message (IP, transport) + stats_attrs.setQueryIPVersion(io_message.getRemoteEndpoint().getFamily()); + stats_attrs.setQueryTransportProtocol( + io_message.getRemoteEndpoint().getProtocol()); // First, check the header part. If we fail even for the base header, // just drop the message. @@ -518,13 +512,13 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message, // Ignore all responses. if (message.getHeaderFlag(Message::HEADERFLAG_QR)) { LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_RECEIVED); - impl_->resumeServer(server, message, false); + impl_->resumeServer(server, message, stats_attrs, false); return; } } catch (const Exception& ex) { LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_HEADER_PARSE_FAIL) .arg(ex.what()); - impl_->resumeServer(server, message, false); + impl_->resumeServer(server, message, stats_attrs, false); return; } @@ -535,13 +529,13 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message, LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PROTOCOL_ERROR) .arg(error.getRcode().toText()).arg(error.what()); makeErrorMessage(impl_->renderer_, message, buffer, error.getRcode()); - impl_->resumeServer(server, message, true); + impl_->resumeServer(server, message, stats_attrs, true); return; } catch (const Exception& ex) { LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_ERROR) .arg(ex.what()); makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL()); - impl_->resumeServer(server, message, true); + impl_->resumeServer(server, message, stats_attrs, true); return; } // other exceptions will be handled at a higher layer. @@ -564,21 +558,35 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message, **impl_->keyring_)); tsig_error = tsig_context->verify(tsig_record, io_message.getData(), io_message.getDataSize()); + // statistics: check TSIG attributes + // SIG(0) is currently not implemented in Auth, but it is implemented + // in BIND 9. At the point we support it, the code to check if the + // signature is valid would be around here. + stats_attrs.setQuerySig(true, false, + tsig_error == TSIGError::NOERROR()); } if (tsig_error != TSIGError::NOERROR()) { makeErrorMessage(impl_->renderer_, message, buffer, tsig_error.toRcode(), tsig_context); - impl_->resumeServer(server, message, true); + impl_->resumeServer(server, message, stats_attrs, true); return; } const Opcode opcode = message.getOpcode(); bool send_answer = true; try { - // update per opcode statistics counter. This can only be reliable - // after TSIG check succeeds. - impl_->counters_.inc(message.getOpcode()); + // statistics: check EDNS + // note: This can only be reliable after TSIG check succeeds. + ConstEDNSPtr edns = message.getEDNS(); + if (edns != NULL) { + stats_attrs.setQueryEDNS(true, edns->getVersion() == 0); + stats_attrs.setQueryDO(edns->getDNSSECAwareness()); + } + + // statistics: check OpCode + // note: This can only be reliable after TSIG check succeeds. + stats_attrs.setQueryOpCode(opcode.getCode()); if (opcode == Opcode::NOTIFY()) { send_answer = impl_->processNotify(io_message, message, buffer, @@ -620,7 +628,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message, LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE_UNKNOWN); makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL()); } - impl_->resumeServer(server, message, send_answer); + impl_->resumeServer(server, message, stats_attrs, send_answer); } bool @@ -637,24 +645,21 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message, message.setHeaderFlag(Message::HEADERFLAG_AA); message.setRcode(Rcode::NOERROR()); - // Increment query counter. - incCounter(io_message.getSocket().getProtocol()); - if (remote_edns) { EDNSPtr local_edns = EDNSPtr(new EDNS()); local_edns->setDNSSECAwareness(dnssec_ok); local_edns->setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE); message.setEDNS(local_edns); } - // Lock the client lists and keep them under the lock until the processing - // and rendering is done (this is the same mutex as from - // AuthSrv::getDataSrcClientListMutex()). - isc::util::thread::Mutex::Locker locker(mutex_); + // Get access to data source client list through the holder and keep the + // holder until the processing and rendering is done to avoid inter-thread + // race. + auth::DataSrcClientsMgr::Holder datasrc_holder(datasrc_clients_mgr_); try { const ConstQuestionPtr question = *message.beginQuestion(); const shared_ptr - list(getDataSrcClientList(question->getClass())); + list(datasrc_holder.findClientList(question->getClass())); if (list) { const RRType& qtype = question->getType(); const Name& qname = question->getName(); @@ -690,9 +695,6 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message, OutputBuffer& buffer, auto_ptr tsig_context) { - // Increment query counter. - incCounter(io_message.getSocket().getProtocol()); - if (io_message.getSocket().getProtocol() == IPPROTO_UDP) { LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_UDP); makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(), @@ -824,19 +826,6 @@ AuthSrvImpl::processUpdate(const IOMessage& io_message) { return (false); } -void -AuthSrvImpl::incCounter(const int protocol) { - // Increment query counter. - if (protocol == IPPROTO_UDP) { - counters_.inc(AuthCounters::SERVER_UDP_QUERY); - } else if (protocol == IPPROTO_TCP) { - counters_.inc(AuthCounters::SERVER_TCP_QUERY); - } else { - // unknown protocol - isc_throw(Unexpected, "Unknown protocol: " << protocol); - } -} - void AuthSrvImpl::registerStatisticsValidator() { counters_.registerStatisticsValidator( @@ -854,10 +843,15 @@ AuthSrvImpl::validateStatistics(isc::data::ConstElementPtr data) const { } void -AuthSrvImpl::resumeServer(DNSServer* server, Message& message, bool done) { +AuthSrvImpl::resumeServer(DNSServer* server, Message& message, + statistics::QRAttributes& stats_attrs, + const bool done) { if (done) { - counters_.inc(message.getRcode()); + stats_attrs.answerWasSent(); + // isTruncated from MessageRenderer + stats_attrs.setResponseTruncated(renderer_.isTruncated()); } + counters_.inc(stats_attrs, message); server->resume(done); } @@ -880,21 +874,6 @@ ConstElementPtr AuthSrv::getStatistics() const { return (impl_->counters_.getStatistics()); } -uint64_t -AuthSrv::getCounter(const AuthCounters::ServerCounterType type) const { - return (impl_->counters_.getCounter(type)); -} - -uint64_t -AuthSrv::getCounter(const Opcode opcode) const { - return (impl_->counters_.getCounter(opcode)); -} - -uint64_t -AuthSrv::getCounter(const Rcode rcode) const { - return (impl_->counters_.getCounter(rcode)); -} - const AddressList& AuthSrv::getListenAddresses() const { return (impl_->listen_addresses_); @@ -933,26 +912,6 @@ AuthSrv::destroyDDNSForwarder() { } } -AuthSrv::DataSrcClientListsPtr -AuthSrv::swapDataSrcClientLists(DataSrcClientListsPtr new_lists) { - // TODO: Debug-build only check - if (!impl_->mutex_.locked()) { - isc_throw(isc::Unexpected, "Not locked!"); - } - std::swap(new_lists, impl_->datasrc_client_lists_); - return (new_lists); -} - -shared_ptr -AuthSrv::getDataSrcClientList(const RRClass& rrclass) { - return (impl_->getDataSrcClientList(rrclass)); -} - -util::thread::Mutex& -AuthSrv::getDataSrcClientListMutex() const { - return (impl_->mutex_); -} - void AuthSrv::setTCPRecvTimeout(size_t timeout) { dnss_->setTCPRecvTimeout(timeout); diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h index 0849bdd1d0330d16a84c5506431a1adb254ed37b..ebd303447fe2b381165dfad13588db4058128c18 100644 --- a/src/bin/auth/auth_srv.h +++ b/src/bin/auth/auth_srv.h @@ -12,13 +12,14 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __AUTH_SRV_H -#define __AUTH_SRV_H 1 +#ifndef AUTH_SRV_H +#define AUTH_SRV_H 1 #include #include #include +#include #include #include @@ -34,7 +35,9 @@ #include #include + #include +#include #include @@ -193,6 +196,11 @@ public: /// \brief Return pointer to the Checkin callback function isc::asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); } + /// \brief Return data source clients manager. + /// + /// \throw None + isc::auth::DataSrcClientsMgr& getDataSrcClientsMgr(); + /// \brief Set the communication session with a separate process for /// outgoing zone transfers. /// @@ -214,55 +222,11 @@ public: /// \brief Returns statistics data /// /// This function can throw an exception from - /// AuthCounters::getStatistics(). + /// Counters::getStatistics(). /// /// \return JSON format statistics data. isc::data::ConstElementPtr getStatistics() const; - /// \brief Get the value of counter in the AuthCounters. - /// - /// This function calls AuthCounters::getStatistics() and - /// returns its return value. - /// - /// This function never throws an exception as far as - /// AuthCounters::getStatistics() doesn't throw. - /// - /// Note: Currently this function is for testing purpose only. - /// - /// \param type Type of a counter to get the value of - /// - /// \return the value of the counter. - - uint64_t getCounter(const AuthCounters::ServerCounterType type) const; - - /// \brief Get the value of per Opcode counter in the Auth Counters. - /// - /// This function calls AuthCounters::getCounter(isc::dns::Opcode) and - /// returns its return value. - /// - /// \note This is a tentative interface as an attempt of experimentally - /// supporting more statistics counters. This should eventually be more - /// generalized. In any case, this method is mainly for testing. - /// - /// \throw None - /// \param opcode The opcode of the counter to get the value of - /// \return the value of the counter. - uint64_t getCounter(const isc::dns::Opcode opcode) const; - - /// \brief Get the value of per Rcode counter in the Auth Counters. - /// - /// This function calls AuthCounters::getCounter(isc::dns::Rcode) and - /// returns its return value. - /// - /// \note This is a tentative interface as an attempt of experimentally - /// supporting more statistics counters. This should eventually be more - /// generalized. In any case, this method is mainly for testing. - /// - /// \throw None - /// \param rcode The rcode of the counter to get the value of - /// \return the value of the counter. - uint64_t getCounter(const isc::dns::Rcode rcode) const; - /** * \brief Set and get the addresses we listen on. */ @@ -302,83 +266,10 @@ public: /// If there was no forwarder yet, this method does nothing. void destroyDDNSForwarder(); - /// \brief Shortcut typedef used for swapDataSrcClientLists(). - typedef boost::shared_ptr > > - DataSrcClientListsPtr; - - /// \brief Swap the currently used set of data source client lists with - /// given one. - /// - /// The "set" of lists is actually given in the form of map from - /// RRClasses to shared pointers to isc::datasrc::ConfigurableClientList. - /// - /// This method returns the swapped set of lists, which was previously - /// used by the server. - /// - /// This method is intended to be used by a separate method to update - /// the data source configuration "at once". The caller must hold - /// a lock for the mutex object returned by \c getDataSrcClientListMutex() - /// before calling this method. - /// - /// The ownership of the returned pointer is transferred to the caller. - /// The caller is generally expected to release the resources used in - /// the old lists. Note that it could take longer time if some of the - /// data source clients contain a large size of in-memory data. - /// - /// The caller can pass a NULL pointer. This effectively disables - /// any data source for the server. - /// - /// \param new_lists Shared pointer to a new set of data source client - /// lists. - /// \return The previous set of lists. It can be NULL. - DataSrcClientListsPtr swapDataSrcClientLists(DataSrcClientListsPtr - new_lists); - - /// \brief Returns the currently used client list for the class. - /// - /// \param rrclass The class for which to get the list. - /// \return The list, or NULL if no list is set for the class. - boost::shared_ptr - getDataSrcClientList(const isc::dns::RRClass& rrclass); - - /// \brief Return a mutex for the client lists. - /// - /// Background loading of data uses threads. Therefore we need to protect - /// the client lists by a mutex, so they don't change (or get destroyed) - /// during query processing. Get (and lock) this mutex whenever you do - /// something with the lists and keep it locked until you finish. This - /// is correct: - /// \code - /// { - /// Mutex::Locker locker(auth->getDataSrcClientListMutex()); - /// boost::shared_ptr - /// list(auth->getDataSrcClientList(RRClass::IN())); - /// // Do some processing here - /// } - /// \endcode - /// - /// But this is not (it releases the mutex too soon): - /// \code - /// boost::shared_ptr list; - /// { - /// Mutex::Locker locker(auth->getDataSrcClientListMutex()); - /// list = auth->getDataSrcClientList(RRClass::IN())); - /// } - /// // Do some processing here - /// \endcode - /// - /// \note This method is const even if you are allowed to modify - /// (lock) the mutex. It's because locking of the mutex is not really - /// a modification of the server object and it is needed to protect the - /// lists even on read-only operations. - isc::util::thread::Mutex& getDataSrcClientListMutex() const; - /// \brief Sets the timeout for incoming TCP connections /// /// Incoming TCP connections that have not sent their data - /// withing this time are dropped. + /// within this time are dropped. /// /// \param timeout The timeout (in milliseconds). If se to /// zero, no timeouts are used, and the connection will remain @@ -393,7 +284,7 @@ private: isc::asiodns::DNSServiceBase* dnss_; }; -#endif // __AUTH_SRV_H +#endif // AUTH_SRV_H // Local Variables: // mode: c++ diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am index c525b665d330d361aa49fcd1ebd5ac14aa3e0ccf..27ddfc554c83a288577530fa98dd8b4aa2a399ee 100644 --- a/src/bin/auth/benchmarks/Makefile.am +++ b/src/bin/auth/benchmarks/Makefile.am @@ -15,7 +15,7 @@ query_bench_SOURCES = query_bench.cc query_bench_SOURCES += ../query.h ../query.cc query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc query_bench_SOURCES += ../auth_config.h ../auth_config.cc -query_bench_SOURCES += ../statistics.h ../statistics.cc +query_bench_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h query_bench_SOURCES += ../auth_log.h ../auth_log.cc query_bench_SOURCES += ../datasrc_config.h ../datasrc_config.cc @@ -34,7 +34,6 @@ query_bench_LDADD += $(top_builddir)/src/lib/nsas/libb10-nsas.la query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la query_bench_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la query_bench_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la -query_bench_LDADD += $(top_builddir)/src/lib/statistics/libb10-statistics.la query_bench_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la query_bench_LDADD += $(SQLITE_LIBS) diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc index 37976a3358ef56b46e71126cd14730bf165d8704..77b3377434dc62b612bf283786bd633c00004cbb 100644 --- a/src/bin/auth/benchmarks/query_bench.cc +++ b/src/bin/auth/benchmarks/query_bench.cc @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -33,6 +32,7 @@ #include #include #include +#include #include #include @@ -127,9 +127,9 @@ public: OutputBuffer& buffer) : QueryBenchMark(queries, query_message, buffer) { - isc::util::thread::Mutex::Locker locker( - server_->getDataSrcClientListMutex()); - server_->swapDataSrcClientLists( + // Note: setDataSrcClientLists() may be deprecated, but until then + // we use it because we want to be synchronized with the server. + server_->getDataSrcClientsMgr().setDataSrcClientLists( configureDataSource( Element::fromJSON("{\"IN\":" " [{\"type\": \"sqlite3\"," @@ -148,9 +148,7 @@ public: OutputBuffer& buffer) : QueryBenchMark(queries, query_message, buffer) { - isc::util::thread::Mutex::Locker locker( - server_->getDataSrcClientListMutex()); - server_->swapDataSrcClientLists( + server_->getDataSrcClientsMgr().setDataSrcClientLists( configureDataSource( Element::fromJSON("{\"IN\":" " [{\"type\": \"MasterFiles\"," diff --git a/src/bin/auth/command.cc b/src/bin/auth/command.cc index d26cf091de4627bf3c5d508606a6f2c173cc84b3..9fbfb42c5c1bdaf2bea9e0e85f27ed80d082d7ee 100644 --- a/src/bin/auth/command.cc +++ b/src/bin/auth/command.cc @@ -15,13 +15,13 @@ #include #include #include +#include #include #include #include #include #include -#include #include @@ -176,54 +176,7 @@ public: virtual ConstElementPtr exec(AuthSrv& server, isc::data::ConstElementPtr args) { - if (args == NULL) { - isc_throw(AuthCommandError, "Null argument"); - } - - ConstElementPtr class_elem = args->get("class"); - RRClass zone_class(class_elem ? RRClass(class_elem->stringValue()) : - RRClass::IN()); - - ConstElementPtr origin_elem = args->get("origin"); - if (!origin_elem) { - isc_throw(AuthCommandError, "Zone origin is missing"); - } - Name origin(origin_elem->stringValue()); - - // We're going to work with the client lists. They may be used - // from a different thread too, protect them. - isc::util::thread::Mutex::Locker locker( - server.getDataSrcClientListMutex()); - const boost::shared_ptr - list(server.getDataSrcClientList(zone_class)); - - if (!list) { - isc_throw(AuthCommandError, "There's no client list for " - "class " << zone_class); - } - - switch (list->reload(origin)) { - case ConfigurableClientList::ZONE_RELOADED: - // Everything worked fine. - LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE) - .arg(zone_class).arg(origin); - return (createAnswer()); - case ConfigurableClientList::ZONE_NOT_FOUND: - isc_throw(AuthCommandError, "Zone " << origin << "/" << - zone_class << " was not found in any configured " - "data source. Configure it first."); - case ConfigurableClientList::ZONE_NOT_CACHED: - isc_throw(AuthCommandError, "Zone " << origin << "/" << - zone_class << " is not served from memory, but " - "directly from the data source. It is not possible " - "to reload it into memory. Configure it to be cached " - "first."); - case ConfigurableClientList::CACHE_DISABLED: - // This is an internal error. Auth server must have the cache - // enabled. - isc_throw(isc::Unexpected, "Cache disabled in client list of " - "class " << zone_class); - } + server.getDataSrcClientsMgr().loadZone(args); return (createAnswer()); } }; diff --git a/src/bin/auth/command.h b/src/bin/auth/command.h index 7db5cd697a0ad70984660d9fbfcbceffd4b6a04e..5058b8194305c59cf0a8b40c25f1fe79a36b27c1 100644 --- a/src/bin/auth/command.h +++ b/src/bin/auth/command.h @@ -16,8 +16,8 @@ #include -#ifndef __COMMAND_H -#define __COMMAND_H 1 +#ifndef COMMAND_H +#define COMMAND_H 1 class AuthSrv; @@ -54,7 +54,7 @@ isc::data::ConstElementPtr execAuthServerCommand(AuthSrv& server, const std::string& command_id, isc::data::ConstElementPtr args); -#endif // __COMMAND_H +#endif // COMMAND_H // Local Variables: // mode: c++ diff --git a/src/bin/auth/common.h b/src/bin/auth/common.h index 0964217336fe5a644fb50ecb0901e3b57409b292..908a7d16212132cbe7c4948ea154543b88ed4981 100644 --- a/src/bin/auth/common.h +++ b/src/bin/auth/common.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __COMMON_H -#define __COMMON_H 1 +#ifndef COMMON_H +#define COMMON_H 1 #include #include @@ -62,7 +62,7 @@ extern const char* const AUTH_NAME; /// This is sent to interested modules (currently only b10-ddns) extern const char* const AUTH_STARTED_NOTIFICATION; -#endif // __COMMON_H +#endif // COMMON_H // Local Variables: // mode: c++ diff --git a/src/bin/auth/datasrc_clients_mgr.h b/src/bin/auth/datasrc_clients_mgr.h new file mode 100644 index 0000000000000000000000000000000000000000..38bf162539e7dc1a6406f3762f03c0b1c470a6a2 --- /dev/null +++ b/src/bin/auth/datasrc_clients_mgr.h @@ -0,0 +1,660 @@ +// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef DATASRC_CLIENTS_MGR_H +#define DATASRC_CLIENTS_MGR_H 1 + +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace isc { +namespace auth { + +/// \brief An exception that is thrown if initial checks for a command fail +/// +/// This is raised *before* the command to the thread is constructed and +/// sent, so the application can still handle them (and therefore it is +/// public, as opposed to InternalCommandError). +/// +/// And example of its use is currently in loadZone(). +class CommandError : public isc::Exception { +public: + CommandError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +namespace datasrc_clientmgr_internal { +// This namespace is essentially private for DataSrcClientsMgr(Base) and +// DataSrcClientsBuilder(Base). This is exposed in the public header +// only because these classes are templated (for testing purposes) and +// class internal has to be defined here. + +/// \brief ID of commands from the DataSrcClientsMgr to DataSrcClientsBuilder. +enum CommandID { + NOOP, ///< Do nothing. Only useful for tests; no argument + RECONFIGURE, ///< Reconfigure the datasource client lists, + /// the argument to the command is the full new + /// datasources configuration. + LOADZONE, ///< Load a new version of zone into a memory, + /// the argument to the command is a map containing 'class' + /// and 'origin' elements, both should have been validated. + SHUTDOWN, ///< Shutdown the builder; no argument + NUM_COMMANDS +}; + +/// \brief The data type passed from DataSrcClientsMgr to +/// DataSrcClientsBuilder. +/// +/// The first element of the pair is the command ID, and the second element +/// is its argument. If the command doesn't take an argument it should be +/// a null pointer. +typedef std::pair Command; +} // namespace datasrc_clientmgr_internal + +/// \brief Frontend to the manager object for data source clients. +/// +/// This class provides interfaces for configuring and updating a set of +/// data source clients "in the background". The user of this class can +/// assume any operation on this class can be done effectively non-blocking, +/// not suspending any delay-sensitive operations such as DNS query +/// processing. The only exception is the time when this class object +/// is destroyed (normally as a result of an implicit call to the destructor); +/// in the current implementation it can take time depending on what is +/// running "in the background" at the time of the call. +/// +/// Internally, an object of this class invokes a separate thread to perform +/// time consuming operations such as loading large zone data into memory, +/// but such details are completely hidden from the user of this class. +/// +/// This class is templated only so that we can test the class without +/// involving actual threads or mutex. Normal applications will only +/// need one specific specialization that has a typedef of +/// \c DataSrcClientsMgr. +template +class DataSrcClientsMgrBase : boost::noncopyable { +private: + typedef std::map > + ClientListsMap; + +public: + /// \brief Thread-safe accessor to the data source client lists. + /// + /// This class provides a simple wrapper for searching the client lists + /// stored in the DataSrcClientsMgr in a thread-safe manner. + /// It ensures the result of \c getClientList() can be used without + /// causing a race condition with other threads that can possibly use + /// the same manager throughout the lifetime of the holder object. + /// + /// This also means the holder object is expected to have a short lifetime. + /// The application shouldn't try to keep it unnecessarily long. + /// It's normally expected to create the holder object on the stack + /// of a small scope and automatically let it be destroyed at the end + /// of the scope. + class Holder { + public: + Holder(DataSrcClientsMgrBase& mgr) : + mgr_(mgr), locker_(mgr_.map_mutex_) + {} + + /// \brief Find a data source client list of a specified RR class. + /// + /// It returns a pointer to the list stored in the manager if found, + /// otherwise it returns NULL. The manager keeps the ownership of + /// the pointed object. Also, it's not safe to get access to the + /// object beyond the scope of the holder object. + /// + /// \note Since the ownership isn't transferred the return value + /// could be a bare pointer (and it's probably better in several + /// points). Unfortunately, some unit tests currently don't work + /// unless this method effectively shares the ownership with the + /// tests. That's the only reason why we return a shared pointer + /// for now. We should eventually fix it and change the return value + /// type (see Trac ticket #2395). Other applications must not + /// assume the ownership is actually shared. + boost::shared_ptr findClientList( + const dns::RRClass& rrclass) + { + const ClientListsMap::const_iterator + it = mgr_.clients_map_->find(rrclass); + if (it == mgr_.clients_map_->end()) { + return (boost::shared_ptr()); + } else { + return (it->second); + } + } + private: + DataSrcClientsMgrBase& mgr_; + typename MutexType::Locker locker_; + }; + + /// \brief Constructor. + /// + /// It internally invokes a separate thread and waits for further + /// operations from the user application. + /// + /// This method is basically exception free except in case of really + /// rare system-level errors. When that happens the only reasonable + /// action that the application can take would be to terminate the program + /// in practice. + /// + /// \throw std::bad_alloc internal memory allocation failure. + /// \throw isc::Unexpected general unexpected system errors. + DataSrcClientsMgrBase() : + clients_map_(new ClientListsMap), + builder_(&command_queue_, &cond_, &queue_mutex_, &clients_map_, + &map_mutex_), + builder_thread_(boost::bind(&BuilderType::run, &builder_)) + {} + + /// \brief The destructor. + /// + /// It tells the internal thread to stop and waits for it completion. + /// In the current implementation, it can block for some unpredictably + /// long period depending on what the thread is doing at that time + /// (in future we may want to implement a rapid way of killing the thread + /// and/or provide a separate interface for waiting so that the application + /// can choose the timing). + /// + /// The waiting operation can result in an exception, but this method + /// catches any of them so this method itself is exception free. + ~DataSrcClientsMgrBase() { + // We share class member variables with the builder, which will be + // invalidated after the call to the destructor, so we need to make + // sure the builder thread is terminated. Depending on the timing + // this could take a long time; if we don't want that to happen in + // this context, we may want to introduce a separate 'shutdown()' + // method. + // Also, since we don't want to propagate exceptions from a destructor, + // we catch any possible ones. In fact the only really expected one + // is Thread::UncaughtException when the builder thread died due to + // an exception. We specifically log it and just ignore others. + try { + sendCommand(datasrc_clientmgr_internal::SHUTDOWN, + data::ConstElementPtr()); + builder_thread_.wait(); + } catch (const util::thread::Thread::UncaughtException& ex) { + // technically, logging this could throw, which will be propagated. + // But such an exception would be a fatal one anyway, so we + // simply let it go through. + LOG_ERROR(auth_logger, AUTH_DATASRC_CLIENTS_SHUTDOWN_ERROR). + arg(ex.what()); + } catch (...) { + LOG_ERROR(auth_logger, + AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR); + } + + cleanup(); // see below + } + + /// \brief Handle new full configuration for data source clients. + /// + /// This method simply passes the new configuration to the builder + /// and immediately returns. This method is basically exception free + /// as long as the caller passes a non NULL value for \c config_arg; + /// it doesn't validate the argument further. + /// + /// \brief isc::InvalidParameter config_arg is NULL. + /// \brief std::bad_alloc + /// + /// \param config_arg The new data source configuration. Must not be NULL. + void reconfigure(data::ConstElementPtr config_arg) { + if (!config_arg) { + isc_throw(InvalidParameter, "Invalid null config argument"); + } + sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg); + reconfigureHook(); // for test's customization + } + + /// \brief Set the underlying data source client lists to new lists. + /// + /// This is provided only for some existing tests until we support a + /// cleaner way to use faked data source clients. Non test code or + /// newer tests must not use this. + void setDataSrcClientLists(datasrc::ClientListMapPtr new_lists) { + typename MutexType::Locker locker(map_mutex_); + clients_map_ = new_lists; + } + + /// \brief Instruct internal thread to (re)load a zone + /// + /// \param args Element argument that should be a map of the form + /// { "class": "IN", "origin": "example.com" } + /// (but class is optional and will default to IN) + /// + /// \exception CommandError if the args value is null, or not in + /// the expected format, or contains + /// a bad origin or class string + void + loadZone(data::ConstElementPtr args) { + if (!args) { + isc_throw(CommandError, "loadZone argument empty"); + } + if (args->getType() != isc::data::Element::map) { + isc_throw(CommandError, "loadZone argument not a map"); + } + if (!args->contains("origin")) { + isc_throw(CommandError, + "loadZone argument has no 'origin' value"); + } + // Also check if it really is a valid name + try { + dns::Name(args->get("origin")->stringValue()); + } catch (const isc::Exception& exc) { + isc_throw(CommandError, "bad origin: " << exc.what()); + } + + if (args->get("origin")->getType() != data::Element::string) { + isc_throw(CommandError, + "loadZone argument 'origin' value not a string"); + } + if (args->contains("class")) { + if (args->get("class")->getType() != data::Element::string) { + isc_throw(CommandError, + "loadZone argument 'class' value not a string"); + } + // Also check if it is a valid class + try { + dns::RRClass(args->get("class")->stringValue()); + } catch (const isc::Exception& exc) { + isc_throw(CommandError, "bad class: " << exc.what()); + } + } + + // Note: we could do some more advanced checks here, + // e.g. check if the zone is known at all in the configuration. + // For now these are skipped, but one obvious way to + // implement it would be to factor out the code from + // the start of doLoadZone(), and call it here too + + sendCommand(datasrc_clientmgr_internal::LOADZONE, args); + } + +private: + // This is expected to be called at the end of the destructor. It + // actually does nothing, but provides a customization point for + // specialized class for tests so that the tests can inspect the last + // state of the class. + void cleanup() {} + + // same as cleanup(), for reconfigure(). + void reconfigureHook() {} + + void sendCommand(datasrc_clientmgr_internal::CommandID command, + data::ConstElementPtr arg) + { + // The lock will be held until the end of this method. Only + // push_back has to be protected, but we can avoid having an extra + // block this way. + typename MutexType::Locker locker(queue_mutex_); + command_queue_.push_back( + datasrc_clientmgr_internal::Command(command, arg)); + cond_.signal(); + } + + // + // The following are shared with the builder. + // + // The list is used as a one-way queue: back-in, front-out + std::list command_queue_; + CondVarType cond_; // condition variable for queue operations + MutexType queue_mutex_; // mutex to protect the queue + datasrc::ClientListMapPtr clients_map_; + // map of actual data source client objects + MutexType map_mutex_; // mutex to protect the clients map + + BuilderType builder_; + ThreadType builder_thread_; // for safety this should be placed last +}; + +namespace datasrc_clientmgr_internal { + +/// \brief A class that maintains a set of data source clients. +/// +/// An object of this class is supposed to run on a dedicated thread, whose +/// main function is a call to its \c run() method. It runs in a loop +/// waiting for commands from the manager and handles each command (including +/// reloading a new version of zone data into memory or fully reconfiguration +/// of specific set of data source clients). When it receives a SHUTDOWN +/// command, it exits from the loop, which will terminate the thread. +/// +/// While this class is defined in a publicly visible namespace, it's +/// essentially private to \c DataSrcClientsMgr. Except for tests, +/// applications should not directly access this class. +/// +/// This class is templated so that we can test it without involving actual +/// threads or locks. +template +class DataSrcClientsBuilderBase : boost::noncopyable { +private: + typedef std::map > + ClientListsMap; + +public: + /// \brief Internal errors in handling commands. + /// + /// This exception is expected to be caught within the + /// \c DataSrcClientsBuilder implementation, but is defined as public + /// so tests can be checked it. + class InternalCommandError : public isc::Exception { + public: + InternalCommandError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} + }; + + /// \brief Constructor. + /// + /// It simply sets up a local copy of shared data with the manager. + /// + /// \throw None + DataSrcClientsBuilderBase(std::list* command_queue, + CondVarType* cond, MutexType* queue_mutex, + datasrc::ClientListMapPtr* clients_map, + MutexType* map_mutex + ) : + command_queue_(command_queue), cond_(cond), queue_mutex_(queue_mutex), + clients_map_(clients_map), map_mutex_(map_mutex) + {} + + /// \brief The main loop. + void run(); + + /// \brief Handle one command from the manager. + /// + /// This is a dedicated subroutine of run() and is essentially private, + /// but is defined as a separate public method so we can test each + /// command test individually. In any case, this class itself is + /// generally considered private. + /// + /// \return true if the builder should keep running; false otherwise. + bool handleCommand(const Command& command); + +private: + // NOOP command handler. We use this so tests can override it; the default + // implementation really does nothing. + void doNoop() {} + + void doReconfigure(const data::ConstElementPtr& config) { + if (config) { + LOG_INFO(auth_logger, + AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_STARTED); + try { + // Define new_clients_map outside of the block that + // has the lock scope; this way, after the swap, + // the lock is guaranteed to be released before + // the old data is destroyed, minimizing the lock + // duration. + datasrc::ClientListMapPtr new_clients_map = + configureDataSource(config); + { + typename MutexType::Locker locker(*map_mutex_); + new_clients_map.swap(*clients_map_); + } // lock is released by leaving scope + LOG_INFO(auth_logger, + AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS); + } catch (const datasrc::ConfigurableClientList::ConfigurationError& + config_error) { + LOG_ERROR(auth_logger, + AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR). + arg(config_error.what()); + } catch (const datasrc::DataSourceError& ds_error) { + LOG_ERROR(auth_logger, + AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_DATASRC_ERROR). + arg(ds_error.what()); + } catch (const isc::Exception& isc_error) { + LOG_ERROR(auth_logger, + AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_ERROR). + arg(isc_error.what()); + } + // other exceptions are propagated, see + // http://bind10.isc.org/ticket/2210#comment:13 + + // old clients_map_ data is released by leaving scope + } + } + + void doLoadZone(const isc::data::ConstElementPtr& arg); + boost::shared_ptr getZoneWriter( + datasrc::ConfigurableClientList& client_list, + const dns::RRClass& rrclass, const dns::Name& origin); + + // The following are shared with the manager + std::list* command_queue_; + CondVarType* cond_; + MutexType* queue_mutex_; + datasrc::ClientListMapPtr* clients_map_; + MutexType* map_mutex_; +}; + +// Shortcut typedef for normal use +typedef DataSrcClientsBuilderBase +DataSrcClientsBuilder; + +template +void +DataSrcClientsBuilderBase::run() { + LOG_INFO(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_STARTED); + + try { + bool keep_running = true; + while (keep_running) { + std::list current_commands; + { + // Move all new commands to local queue under the protection of + // queue_mutex_. + typename MutexType::Locker locker(*queue_mutex_); + while (command_queue_->empty()) { + cond_->wait(*queue_mutex_); + } + current_commands.swap(*command_queue_); + } // the lock is released here. + + while (keep_running && !current_commands.empty()) { + try { + keep_running = handleCommand(current_commands.front());; + } catch (const InternalCommandError& e) { + LOG_ERROR(auth_logger, + AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR). + arg(e.what()); + } + current_commands.pop_front(); + } + } + + LOG_INFO(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_STOPPED); + } catch (const std::exception& ex) { + // We explicitly catch exceptions so we can log it as soon as possible. + LOG_FATAL(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_FAILED). + arg(ex.what()); + std::terminate(); + } catch (...) { + LOG_FATAL(auth_logger, AUTH_DATASRC_CLIENTS_BUILDER_FAILED_UNEXPECTED); + std::terminate(); + } +} + +template +bool +DataSrcClientsBuilderBase::handleCommand( + const Command& command) +{ + const CommandID cid = command.first; + if (cid >= NUM_COMMANDS) { + // This shouldn't happen except for a bug within this file. + isc_throw(Unexpected, "internal bug: invalid command, ID: " << cid); + } + + const boost::array command_desc = { + {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"} + }; + LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC, + AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid)); + switch (command.first) { + case RECONFIGURE: + doReconfigure(command.second); + break; + case LOADZONE: + doLoadZone(command.second); + break; + case SHUTDOWN: + return (false); + case NOOP: + doNoop(); + break; + case NUM_COMMANDS: + assert(false); // we rejected this case above + } + return (true); +} + +template +void +DataSrcClientsBuilderBase::doLoadZone( + const isc::data::ConstElementPtr& arg) +{ + // We assume some basic level validation as this method can only be + // called via the manager in practice. manager is expected to do the + // minimal validation. + assert(arg); + assert(arg->get("origin")); + + // TODO: currently, we hardcode IN as the default for the optional + // 'class' argument. We should really derive this from the specification, + // but at the moment the config/command API does not allow that to be + // done easily. Once that is in place (tickets have yet to be created, + // as we need to do a tiny bit of design work for that), this + // code can be replaced with the original part: + // assert(arg->get("class")); + // const dns::RRClass(arg->get("class")->stringValue()); + isc::data::ConstElementPtr class_elem = arg->get("class"); + const dns::RRClass rrclass(class_elem ? + dns::RRClass(class_elem->stringValue()) : + dns::RRClass::IN()); + const dns::Name origin(arg->get("origin")->stringValue()); + ClientListsMap::iterator found = (*clients_map_)->find(rrclass); + if (found == (*clients_map_)->end()) { + isc_throw(InternalCommandError, "failed to load a zone " << origin << + "/" << rrclass << ": not configured for the class"); + } + + boost::shared_ptr client_list = + found->second; + assert(client_list); + + try { + boost::shared_ptr zwriter = + getZoneWriter(*client_list, rrclass, origin); + + zwriter->load(); // this can take time but doesn't cause a race + { // install() can cause a race and must be in a critical section + typename MutexType::Locker locker(*map_mutex_); + zwriter->install(); + } + LOG_DEBUG(auth_logger, DBG_AUTH_OPS, + AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE) + .arg(origin).arg(rrclass); + + // same as load(). We could let the destructor do it, but do it + // ourselves explicitly just in case. + zwriter->cleanup(); + } catch (const InternalCommandError& ex) { + throw; // this comes from getZoneWriter. just let it go through. + } catch (const isc::Exception& ex) { + // We catch our internal exceptions (which will be just ignored) and + // propagated others (which should generally be considered fatal and + // will make the thread terminate) + isc_throw(InternalCommandError, "failed to load a zone " << origin << + "/" << rrclass << ": error occurred in reload: " << + ex.what()); + } +} + +// A dedicated subroutine of doLoadZone(). Separated just for keeping the +// main method concise. +template +boost::shared_ptr +DataSrcClientsBuilderBase::getZoneWriter( + datasrc::ConfigurableClientList& client_list, + const dns::RRClass& rrclass, const dns::Name& origin) +{ + const datasrc::ConfigurableClientList::ZoneWriterPair writerpair = + client_list.getCachedZoneWriter(origin); + + switch (writerpair.first) { + case datasrc::ConfigurableClientList::ZONE_SUCCESS: + assert(writerpair.second); + return (writerpair.second); + case datasrc::ConfigurableClientList::ZONE_NOT_FOUND: + isc_throw(InternalCommandError, "failed to load zone " << origin + << "/" << rrclass << ": not found in any configured " + "data source."); + case datasrc::ConfigurableClientList::ZONE_NOT_CACHED: + isc_throw(InternalCommandError, "failed to load zone " << origin + << "/" << rrclass << ": not served from memory"); + case datasrc::ConfigurableClientList::CACHE_DISABLED: + // This is an internal error. Auth server must have the cache + // enabled. + isc_throw(InternalCommandError, "failed to load zone " << origin + << "/" << rrclass << ": internal failure, in-memory cache " + "is somehow disabled"); + } + + // all cases above should either return or throw, but some compilers + // still need a return statement + return (boost::shared_ptr()); +} +} // namespace datasrc_clientmgr_internal + +/// \brief Shortcut type for normal data source clients manager. +/// +/// In fact, for non test applications this is the only type of this kind +/// to be considered. +typedef DataSrcClientsMgrBase< + util::thread::Thread, + datasrc_clientmgr_internal::DataSrcClientsBuilder, + util::thread::Mutex, util::thread::CondVar> DataSrcClientsMgr; +} // namespace auth +} // namespace isc + +#endif // DATASRC_CLIENTS_MGR_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/bin/auth/datasrc_config.cc b/src/bin/auth/datasrc_config.cc index 62c3c7a6ba4dea306d95e0a97073f6670c6c6ad6..4869050e6a606948b959d1413131d96a7e392f4a 100644 --- a/src/bin/auth/datasrc_config.cc +++ b/src/bin/auth/datasrc_config.cc @@ -13,12 +13,11 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include "auth_srv.h" #include "datasrc_config.h" // This is a trivial specialization for the commonly used version. // Defined in .cc to avoid accidental creation of multiple copies. -AuthSrv::DataSrcClientListsPtr +isc::datasrc::ClientListMapPtr configureDataSource(const isc::data::ConstElementPtr& config) { return (configureDataSourceGeneric< isc::datasrc::ConfigurableClientList>(config)); diff --git a/src/bin/auth/datasrc_config.h b/src/bin/auth/datasrc_config.h index 5707c6c8a0dd9025caba9b215acdb4121f1cbbeb..1723161f660955b1fdaf540261e09b382ed8e5c4 100644 --- a/src/bin/auth/datasrc_config.h +++ b/src/bin/auth/datasrc_config.h @@ -12,10 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef DATASRC_CONFIG_H -#define DATASRC_CONFIG_H - -#include "auth_srv.h" +#ifndef AUTH_DATASRC_CONFIG_H +#define AUTH_DATASRC_CONFIG_H #include #include @@ -23,7 +21,7 @@ #include #include -#include +#include /// \brief Configure data source client lists /// @@ -48,6 +46,8 @@ /// \param config The configuration value to parse. It is in the form /// as an update from the config manager. /// \return A map from RR classes to configured lists. +/// \throw ConfigurationError if the config element is not in the expected +/// format (A map of lists) template boost::shared_ptr > > // = ListMap below @@ -58,7 +58,6 @@ configureDataSourceGeneric(const isc::data::ConstElementPtr& config) { boost::shared_ptr new_lists(new ListMap); - // Go through the configuration and create corresponding list. const Map& map(config->mapValue()); for (Map::const_iterator it(map.begin()); it != map.end(); ++it) { const isc::dns::RRClass rrclass(it->first); @@ -73,10 +72,10 @@ configureDataSourceGeneric(const isc::data::ConstElementPtr& config) { /// \brief Concrete version of configureDataSource() for the /// use with authoritative server implementation. -AuthSrv::DataSrcClientListsPtr +isc::datasrc::ClientListMapPtr configureDataSource(const isc::data::ConstElementPtr& config); -#endif // DATASRC_CONFIG_H +#endif // AUTH_DATASRC_CONFIG_H // Local Variables: // mode: c++ diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index 99080662ff86ef3ee8dd3766ec20a71cac5d750c..1fe0f481a242117a800e1c6ebbb75ceb3dae1d06 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -36,6 +35,8 @@ #include #include #include +#include + #include #include #include @@ -93,32 +94,23 @@ datasrcConfigHandler(AuthSrv* server, bool* first_time, const isc::config::ConfigData&) { assert(server != NULL); - if (config->contains("classes")) { - AuthSrv::DataSrcClientListsPtr lists; - - if (*first_time) { - // HACK: The default is not passed to the handler in the first - // callback. This one will get the default (or, current value). - // Further updates will work the usual way. - assert(config_session != NULL); - *first_time = false; - lists = configureDataSource( - config_session->getRemoteConfigValue("data_sources", - "classes")); - } else { - lists = configureDataSource(config->get("classes")); - } - // Replace the server's lists. The returned lists will be stored - // in a local variable 'lists', and will be destroyed outside of - // the temporary block for the lock scope. That way we can minimize - // the range of the critical section. - { - isc::util::thread::Mutex::Locker locker( - server->getDataSrcClientListMutex()); - lists = server->swapDataSrcClientLists(lists); - } - // The previous lists are destroyed here. + // Note: remote config handler is requested to be exception free. + // While the code below is not 100% exception free, such an exception + // is really fatal and the server should actually stop. So we don't + // bother to catch them; the exception would be propagated to the + // top level of the server and terminate it. + + if (*first_time) { + // HACK: The default is not passed to the handler in the first + // callback. This one will get the default (or, current value). + // Further updates will work the usual way. + assert(config_session != NULL); + *first_time = false; + server->getDataSrcClientsMgr().reconfigure( + config_session->getRemoteConfigValue("data_sources", "classes")); + } else if (config->contains("classes")) { + server->getDataSrcClientsMgr().reconfigure(config->get("classes")); } } diff --git a/src/bin/auth/statistics.cc b/src/bin/auth/statistics.cc index 2d5f33619584ba418cda4d25d7fb768bc9e12573..b310b23bf67b2513dc14f1c25883d32e1acc6ecb 100644 --- a/src/bin/auth/statistics.cc +++ b/src/bin/auth/statistics.cc @@ -13,9 +13,11 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include #include #include +#include #include #include @@ -32,107 +34,206 @@ #include +#include +#include +#include +#include +#include + using namespace isc::dns; using namespace isc::auth; using namespace isc::statistics; -// TODO: We need a namespace ("auth_server"?) to hold -// AuthSrv and AuthCounters. +namespace isc { +namespace auth { +namespace statistics { // TODO: Make use of wrappers like isc::dns::Opcode // for counter item type. -class AuthCountersImpl : boost::noncopyable { +class CountersImpl : boost::noncopyable { public: - AuthCountersImpl(); - ~AuthCountersImpl(); - void inc(const AuthCounters::ServerCounterType type); - void inc(const Opcode opcode) { - opcode_counter_.inc(opcode.getCode()); - } - void inc(const Rcode rcode) { - rcode_counter_.inc(rcode.getCode()); - } - void inc(const std::string& zone, - const AuthCounters::PerZoneCounterType type); + CountersImpl(); + ~CountersImpl(); + void inc(const QRAttributes& qrattrs, const Message& response); isc::data::ConstElementPtr getStatistics() const; - void registerStatisticsValidator - (AuthCounters::validator_type validator); - // Currently for testing purpose only - uint64_t getCounter(const AuthCounters::ServerCounterType type) const; - uint64_t getCounter(const Opcode opcode) const { - return (opcode_counter_.get(opcode.getCode())); - } - uint64_t getCounter(const Rcode rcode) const { - return (rcode_counter_.get(rcode.getCode())); - } + void registerStatisticsValidator(Counters::validator_type validator); private: - Counter server_counter_; - Counter opcode_counter_; - static const size_t NUM_OPCODES = 16; - Counter rcode_counter_; - static const size_t NUM_RCODES = 17; - CounterDictionary per_zone_counter_; - AuthCounters::validator_type validator_; + // counter for query/response + Counter server_qr_counter_; + // set of counters for zones + CounterDictionary zone_qr_counters_; + // validator + Counters::validator_type validator_; }; -AuthCountersImpl::AuthCountersImpl() : - // initialize counter - // size of server_counter_: AuthCounters::SERVER_COUNTER_TYPES - // size of per_zone_counter_: AuthCounters::PER_ZONE_COUNTER_TYPES - server_counter_(AuthCounters::SERVER_COUNTER_TYPES), - opcode_counter_(NUM_OPCODES), rcode_counter_(NUM_RCODES), - per_zone_counter_(AuthCounters::PER_ZONE_COUNTER_TYPES) -{ - per_zone_counter_.addElement("_SERVER_"); -} +CountersImpl::CountersImpl() : + // size of server_qr_counter_, zone_qr_counters_: QR_COUNTER_TYPES + server_qr_counter_(QR_COUNTER_TYPES), + zone_qr_counters_(QR_COUNTER_TYPES), + validator_() +{} -AuthCountersImpl::~AuthCountersImpl() +CountersImpl::~CountersImpl() {} void -AuthCountersImpl::inc(const AuthCounters::ServerCounterType type) { - server_counter_.inc(type); -} +CountersImpl::inc(const QRAttributes& qrattrs, const Message& response) { + // protocols carrying request + if (qrattrs.req_ip_version_ == AF_INET) { + server_qr_counter_.inc(QR_REQUEST_IPV4); + } else if (qrattrs.req_ip_version_ == AF_INET6) { + server_qr_counter_.inc(QR_REQUEST_IPV6); + } + if (qrattrs.req_transport_protocol_ == IPPROTO_UDP) { + server_qr_counter_.inc(QR_REQUEST_UDP); + } else if (qrattrs.req_transport_protocol_ == IPPROTO_TCP) { + server_qr_counter_.inc(QR_REQUEST_TCP); + } -void -AuthCountersImpl::inc(const std::string& zone, - const AuthCounters::PerZoneCounterType type) -{ - per_zone_counter_[zone].inc(type); + // query TSIG + if (qrattrs.req_is_tsig_) { + server_qr_counter_.inc(QR_REQUEST_TSIG); + } + if (qrattrs.req_is_sig0_) { + server_qr_counter_.inc(QR_REQUEST_SIG0); + } + if (qrattrs.req_is_badsig_) { + server_qr_counter_.inc(QR_REQUEST_BADSIG); + // If signature validation is failed, no other attributes are reliable + return; + } + + // query EDNS + if (qrattrs.req_is_edns_0_) { + server_qr_counter_.inc(QR_REQUEST_EDNS0); + } + if (qrattrs.req_is_edns_badver_) { + server_qr_counter_.inc(QR_REQUEST_BADEDNSVER); + } + + // query DNSSEC + if (qrattrs.req_is_dnssec_ok_) { + server_qr_counter_.inc(QR_REQUEST_DNSSEC_OK); + } + + // QTYPE + unsigned int qtype_type = QR_QTYPE_OTHER; + const QuestionIterator qiter = response.beginQuestion(); + if (qiter != response.endQuestion()) { + // get the first and only question section and + // get the qtype code + const unsigned int qtype = (*qiter)->getType().getCode(); + if (qtype < 258) { + // qtype 0..257: lookup qtype-countertype table + qtype_type = QRQTypeToQRCounterType[qtype]; + } else if (qtype < 32768) { + // qtype 258..32767: (Unassigned) + qtype_type = QR_QTYPE_OTHER; + } else if (qtype < 32770) { + // qtype 32768..32769: TA and DLV + qtype_type = QR_QTYPE_TA + (qtype - 32768); + } else { + // qtype 32770..65535: (Unassigned, Private use, Reserved) + qtype_type = QR_QTYPE_OTHER; + } + } + server_qr_counter_.inc(qtype_type); + // OPCODE + server_qr_counter_.inc(QROpCodeToQRCounterType[qrattrs.req_opcode_]); + + // response + if (qrattrs.answer_sent_) { + // responded + server_qr_counter_.inc(QR_RESPONSE); + + // response truncated + if (qrattrs.res_is_truncated_) { + server_qr_counter_.inc(QR_RESPONSE_TRUNCATED); + } + + // response EDNS + ConstEDNSPtr response_edns = response.getEDNS(); + if (response_edns != NULL && response_edns->getVersion() == 0) { + server_qr_counter_.inc(QR_RESPONSE_EDNS0); + } + + // response TSIG + if (qrattrs.req_is_tsig_) { + // assume response is TSIG signed if request is TSIG signed + server_qr_counter_.inc(QR_RESPONSE_TSIG); + } + + // response SIG(0) is currently not implemented + + // RCODE + const unsigned int rcode = response.getRcode().getCode(); + unsigned int rcode_type = QR_RCODE_OTHER; + if (rcode < 23) { + // rcode 0..22: lookup rcode-countertype table + rcode_type = QRRCodeToQRCounterType[rcode]; + } else { + // opcode larger than 22 is reserved or unassigned + rcode_type = QR_RCODE_OTHER; + } + server_qr_counter_.inc(rcode_type); + + // compound attributes + const unsigned int answer_rrs = + response.getRRCount(Message::SECTION_ANSWER); + const bool is_aa_set = response.getHeaderFlag(Message::HEADERFLAG_AA); + + if (is_aa_set) { + // QryAuthAns + server_qr_counter_.inc(QR_QRYAUTHANS); + } else { + // QryNoAuthAns + server_qr_counter_.inc(QR_QRYNOAUTHANS); + } + + if (rcode == Rcode::NOERROR_CODE) { + if (answer_rrs > 0) { + // QrySuccess + server_qr_counter_.inc(QR_QRYSUCCESS); + } else { + if (is_aa_set) { + // QryNxrrset + server_qr_counter_.inc(QR_QRYNXRRSET); + } else { + // QryReferral + server_qr_counter_.inc(QR_QRYREFERRAL); + } + } + } else if (rcode == Rcode::REFUSED_CODE) { + // AuthRej + server_qr_counter_.inc(QR_QRYREJECT); + } + } } isc::data::ConstElementPtr -AuthCountersImpl::getStatistics() const { +CountersImpl::getStatistics() const { std::stringstream statistics_string; statistics_string << "{ \"queries.udp\": " - << server_counter_.get(AuthCounters::SERVER_UDP_QUERY) + << server_qr_counter_.get(QR_REQUEST_UDP) << ", \"queries.tcp\": " - << server_counter_.get(AuthCounters::SERVER_TCP_QUERY); + << server_qr_counter_.get(QR_REQUEST_TCP); // Insert non 0 Opcode counters. - for (int i = 0; i < NUM_OPCODES; ++i) { - const Counter::Type counter = opcode_counter_.get(i); + for (int i = QR_OPCODE_QUERY; i <= QR_OPCODE_OTHER; ++i) { + const Counter::Type counter = server_qr_counter_.get(i); if (counter != 0) { - // The counter item name should be derived lower-cased textual - // representation of the code. - std::string opcode_txt = Opcode(i).toText(); - std::transform(opcode_txt.begin(), opcode_txt.end(), - opcode_txt.begin(), ::tolower); - statistics_string << ", \"opcode." << opcode_txt << "\": " - << counter; + statistics_string << ", \"" << "opcode." << + QRCounterOpcode[i - QR_OPCODE_QUERY].name << + "\": " << counter; } } // Insert non 0 Rcode counters. - for (int i = 0; i < NUM_RCODES; ++i) { - const Counter::Type counter = rcode_counter_.get(i); + for (int i = QR_RCODE_NOERROR; i <= QR_RCODE_OTHER; ++i) { + const Counter::Type counter = server_qr_counter_.get(i); if (counter != 0) { - // The counter item name should be derived lower-cased textual - // representation of the code. - std::string rcode_txt = Rcode(i).toText(); - std::transform(rcode_txt.begin(), rcode_txt.end(), - rcode_txt.begin(), ::tolower); - statistics_string << ", \"rcode." << rcode_txt << "\": " - << counter; + statistics_string << ", \"" << "rcode." << + QRCounterRcode[i - QR_RCODE_NOERROR].name << + "\": " << counter; } } statistics_string << "}"; @@ -150,61 +251,34 @@ AuthCountersImpl::getStatistics() const { } void -AuthCountersImpl::registerStatisticsValidator - (AuthCounters::validator_type validator) +CountersImpl::registerStatisticsValidator + (Counters::validator_type validator) { validator_ = validator; } -// Currently for testing purpose only -uint64_t -AuthCountersImpl::getCounter(const AuthCounters::ServerCounterType type) const { - return (server_counter_.get(type)); -} - -AuthCounters::AuthCounters() : impl_(new AuthCountersImpl()) +Counters::Counters() : impl_(new CountersImpl()) {} -AuthCounters::~AuthCounters() {} +Counters::~Counters() {} void -AuthCounters::inc(const AuthCounters::ServerCounterType type) { - impl_->inc(type); -} - -void -AuthCounters::inc(const Opcode opcode) { - impl_->inc(opcode); -} - -void -AuthCounters::inc(const Rcode rcode) { - impl_->inc(rcode); +Counters::inc(const QRAttributes& qrattrs, const Message& response) { + impl_->inc(qrattrs, response); } isc::data::ConstElementPtr -AuthCounters::getStatistics() const { +Counters::getStatistics() const { return (impl_->getStatistics()); } -uint64_t -AuthCounters::getCounter(const AuthCounters::ServerCounterType type) const { - return (impl_->getCounter(type)); -} - -uint64_t -AuthCounters::getCounter(const Opcode opcode) const { - return (impl_->getCounter(opcode)); -} - -uint64_t -AuthCounters::getCounter(const Rcode rcode) const { - return (impl_->getCounter(rcode)); -} - void -AuthCounters::registerStatisticsValidator - (AuthCounters::validator_type validator) const +Counters::registerStatisticsValidator + (Counters::validator_type validator) const { return (impl_->registerStatisticsValidator(validator)); } + +} // namespace statistics +} // namespace auth +} // namespace isc diff --git a/src/bin/auth/statistics.h b/src/bin/auth/statistics.h index 0ca8da4d39c15eae06347615d9c30217d6316310..d60c6814a44da918fe6aafbdd598be442d54faaa 100644 --- a/src/bin/auth/statistics.h +++ b/src/bin/auth/statistics.h @@ -12,22 +12,136 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __STATISTICS_H -#define __STATISTICS_H 1 +#ifndef STATISTICS_H +#define STATISTICS_H 1 -#include -#include #include #include +#include + +#include + #include #include -class AuthCountersImpl; +namespace isc { +namespace auth { +namespace statistics { + +class CountersImpl; + +class QRAttributes { +/// \brief Query/Response attributes for statistics. +/// +/// This class holds some attributes related to a query/response +/// for statistics data collection. +/// +/// This class does not have getter methods since it exposes private members +/// to \c CountersImpl directly. +friend class CountersImpl; +private: + // request attributes + int req_ip_version_; // IP version + int req_transport_protocol_; // Transport layer protocol + int req_opcode_; // OpCode + bool req_is_edns_0_; // EDNS ver.0 + bool req_is_edns_badver_; // other EDNS version + bool req_is_dnssec_ok_; // DO bit + bool req_is_tsig_; // signed with valid TSIG + bool req_is_sig0_; // signed with valid SIG(0) + bool req_is_badsig_; // signed but bad signature + // zone origin + std::string zone_origin_; // zone origin + // response attributes + bool answer_sent_; // DNS message has sent + bool res_is_truncated_; // DNS message is truncated +public: + /// The constructor. + /// + /// This constructor is mostly exception free. But it may still throw + /// a standard exception if memory allocation fails inside the method. + /// + QRAttributes() { + reset(); + }; + + /// The destructor. + /// + /// This method never throws an exception. + /// + ~QRAttributes() {}; + /// \brief Set query opcode. + /// \throw None + void setQueryOpCode(const int opcode) { + req_opcode_ = opcode; + }; + /// \brief Set IP version carrying a query. + /// \throw None + void setQueryIPVersion(const int ip_version) { + req_ip_version_ = ip_version; + }; + /// \brief Set transport protocol carrying a query. + /// \throw None + void setQueryTransportProtocol(const int transport_protocol) { + req_transport_protocol_ = transport_protocol; + }; + /// \brief Set query EDNS attributes. + /// \throw None + void setQueryEDNS(const bool is_edns_0, const bool is_edns_badver) { + req_is_edns_0_ = is_edns_0; + req_is_edns_badver_ = is_edns_badver; + }; + /// \brief Set query DO bit. + /// \throw None + void setQueryDO(const bool is_dnssec_ok) { + req_is_dnssec_ok_ = is_dnssec_ok; + }; + /// \brief Set query TSIG attributes. + /// \throw None + void setQuerySig(const bool is_tsig, const bool is_sig0, + const bool is_badsig) + { + req_is_tsig_ = is_tsig; + req_is_sig0_ = is_sig0; + req_is_badsig_ = is_badsig; + }; + /// \brief Set zone origin. + /// \throw None + void setOrigin(const std::string& origin) { + zone_origin_ = origin; + }; + /// \brief Set if the answer was sent. + /// \throw None + void answerWasSent() { + answer_sent_ = true; + }; + /// \brief Set if the response is truncated. + /// \throw None + void setResponseTruncated(const bool is_truncated) { + res_is_truncated_ = is_truncated; + }; + /// \brief Reset attributes. + /// \throw None + void reset() { + req_ip_version_ = 0; + req_transport_protocol_ = 0; + req_opcode_ = 0; + req_is_edns_0_ = false; + req_is_edns_badver_ = false; + req_is_dnssec_ok_ = false; + req_is_tsig_ = false; + req_is_sig0_ = false; + req_is_badsig_ = false; + zone_origin_.clear(); + answer_sent_ = false; + res_is_truncated_ = false; + }; +}; /// \brief Set of query counters. /// -/// \c AuthCounters is set of query counters class. It holds query counters +/// \c Counters is set of query counters class. It holds query counters /// and provides an interface to increment the counter of specified type /// (e.g. UDP query, TCP query). /// @@ -35,9 +149,7 @@ class AuthCountersImpl; /// statistics module. /// /// This class is designed to be a part of \c AuthSrv. -/// Call \c inc() to increment a counter for specific type of query in -/// the query processing function. use \c enum \c CounterType to specify -/// the type of query. +/// Call \c inc() to increment a counter for the query. /// Call \c getStatistics() to answer statistics information to statistics /// module with statistics_session, when the command \c getstats is received. /// @@ -50,61 +162,31 @@ class AuthCountersImpl; /// construction overhead of this approach should be acceptable. /// /// \todo Hold counters for each query types (Notify, Axfr, Ixfr, Normal) -/// \todo Consider overhead of \c AuthCounters::inc() -class AuthCounters { +/// \todo Consider overhead of \c Counters::inc() +class Counters { private: - boost::scoped_ptr impl_; + boost::scoped_ptr impl_; public: - // Enum for the type of counter - enum ServerCounterType { - SERVER_UDP_QUERY, ///< SERVER_UDP_QUERY: counter for UDP queries - SERVER_TCP_QUERY, ///< SERVER_TCP_QUERY: counter for TCP queries - SERVER_COUNTER_TYPES ///< The number of defined counters - }; - enum PerZoneCounterType { - ZONE_UDP_QUERY, ///< ZONE_UDP_QUERY: counter for UDP queries - ZONE_TCP_QUERY, ///< ZONE_TCP_QUERY: counter for TCP queries - PER_ZONE_COUNTER_TYPES ///< The number of defined counters - }; /// The constructor. /// /// This constructor is mostly exception free. But it may still throw /// a standard exception if memory allocation fails inside the method. /// - AuthCounters(); + Counters(); /// The destructor. /// /// This method never throws an exception. /// - ~AuthCounters(); + ~Counters(); - /// \brief Increment the counter specified by the parameter. - /// - /// \param type Type of a counter to increment. + /// \brief Increment counters according to the parameters. /// - /// \throw std::out_of_range \a type is unknown. - /// - /// usage: counter.inc(AuthCounters::SERVER_UDP_QUERY); - /// - void inc(const ServerCounterType type); - - /// \brief Increment the counter of a per opcode counter. - /// - /// \note This is a tentative interface. See \c getCounter(). - /// - /// \param opcode The opcode of the counter to increment. + /// \param qrattrs Query/Response attributes. + /// \param response DNS response message. /// /// \throw None - void inc(const isc::dns::Opcode opcode); - - /// \brief Increment the counter of a per rcode counter. - /// - /// \note This is a tentative interface. See \c getCounter(). /// - /// \param rcode The rcode of the counter to increment. - /// - /// \throw None - void inc(const isc::dns::Rcode rcode); + void inc(const QRAttributes& qrattrs, const isc::dns::Message& response); /// \brief Answers statistics counters to statistics module. /// @@ -116,47 +198,6 @@ public: /// isc::data::ConstElementPtr getStatistics() const; - /// \brief Get the value of a counter in the AuthCounters. - /// - /// This function returns a value of the counter specified by \a type. - /// This method never throws an exception. - /// - /// Note: Currently this function is for testing purpose only. - /// - /// \param type Type of a counter to get the value of - /// - /// \return the value of the counter specified by \a type. - uint64_t getCounter(const AuthCounters::ServerCounterType type) const; - - /// \brief Get the value of a per opcode counter. - /// - /// This method returns the value of the per opcode counter for the - /// specified \c opcode. - /// - /// \note This is a tentative interface as an attempt of experimentally - /// supporting more statistics counters. This should eventually be more - /// generalized. In any case, this method is mainly for testing. - /// - /// \throw None - /// \param opcode The opcode of the counter to get the value of - /// \return the value of the counter. - uint64_t getCounter(const isc::dns::Opcode opcode) const; - - /// \brief Get the value of a per rcode counter. - /// - /// This method returns the value of the per rcode counter for the - /// specified \c rcode. - /// - /// \note As mentioned in getCounter(const isc::dns::Opcode opcode), - /// This is a tentative interface as an attempt of experimentally - /// supporting more statistics counters. This should eventually be more - /// generalized. In any case, this method is mainly for testing. - /// - /// \throw None - /// \param rcode The rcode of the counter to get the value of - /// \return the value of the counter. - uint64_t getCounter(const isc::dns::Rcode rcode) const; - /// \brief A type of validation function for the specification in /// isc::config::ModuleSpec. /// @@ -168,17 +209,21 @@ public: validator_type; /// \brief Register a function type of the statistics validation - /// function for AuthCounters. + /// function for Counters. /// /// This method never throws an exception. /// /// \param validator A function type of the validation of /// statistics specification. /// - void registerStatisticsValidator(AuthCounters::validator_type validator) const; + void registerStatisticsValidator(Counters::validator_type validator) const; }; -#endif // __STATISTICS_H +} // namespace statistics +} // namespace auth +} // namespace isc + +#endif // STATISTICS_H // Local Variables: // mode: c++ diff --git a/src/bin/auth/statistics_items.h b/src/bin/auth/statistics_items.h new file mode 100644 index 0000000000000000000000000000000000000000..583920648108c490b5f9d4d3b32e75e727062a54 --- /dev/null +++ b/src/bin/auth/statistics_items.h @@ -0,0 +1,609 @@ +// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef __STATISTICS_ITEMS_H +#define __STATISTICS_ITEMS_H 1 + +/// This file defines a set of statistics items in Auth module for internal +/// use. This file is intended to be included in statistics.cc. + +namespace { + +struct CounterTypeTree { + const char* const name; + const struct CounterTypeTree* const sub_tree; + const int counter_id; +}; + +// enum for query/response counters +enum QRCounterType { + // Request Attributes + QR_REQUEST_IPV4, ///< Number of IPv4 requests received + QR_REQUEST_IPV6, ///< Number of IPv6 requests received + QR_REQUEST_EDNS0, ///< Number of requests with EDNS(0) received + QR_REQUEST_BADEDNSVER, ///< Number of requests with unsupported EDNS version received + QR_REQUEST_TSIG, ///< Number of requests with TSIG received + QR_REQUEST_SIG0, ///< Number of requests with SIG(0) received; not implemented in BIND 10 + QR_REQUEST_BADSIG, ///< Number of requests with invalid TSIG or SIG(0) signature received + QR_REQUEST_UDP, ///< Number of UDP requests received + QR_REQUEST_TCP, ///< Number of TCP requests received + QR_REQUEST_DNSSEC_OK, ///< Number of requests with DO bit + // Request Opcodes + QR_OPCODE_QUERY, ///< Number of Opcode=QUERY requests received + QR_OPCODE_IQUERY, ///< Number of Opcode=IQUERY requests received + QR_OPCODE_STATUS, ///< Number of Opcode=STATUS requests received + QR_OPCODE_NOTIFY, ///< Number of Opcode=NOTIFY requests received + QR_OPCODE_UPDATE, ///< Number of Opcode=UPDATE requests received + QR_OPCODE_OTHER, ///< Number of requests in other OpCode received + // Query Types + QR_QTYPE_A, ///< Number of QTYPE = A queries received + QR_QTYPE_NS, ///< Number of QTYPE = NS queries received + QR_QTYPE_MD, ///< Number of QTYPE = MD queries received + QR_QTYPE_MF, ///< Number of QTYPE = MF queries received + QR_QTYPE_CNAME, ///< Number of QTYPE = CNAME queries received + QR_QTYPE_SOA, ///< Number of QTYPE = SOA queries received + QR_QTYPE_MB, ///< Number of QTYPE = MB queries received + QR_QTYPE_MG, ///< Number of QTYPE = MG queries received + QR_QTYPE_MR, ///< Number of QTYPE = MR queries received + QR_QTYPE_NULL, ///< Number of QTYPE = NULL queries received + QR_QTYPE_WKS, ///< Number of QTYPE = WKS queries received + QR_QTYPE_PTR, ///< Number of QTYPE = PTR queries received + QR_QTYPE_HINFO, ///< Number of QTYPE = HINFO queries received + QR_QTYPE_MINFO, ///< Number of QTYPE = MINFO queries received + QR_QTYPE_MX, ///< Number of QTYPE = MX queries received + QR_QTYPE_TXT, ///< Number of QTYPE = TXT queries received + QR_QTYPE_RP, ///< Number of QTYPE = RP queries received + QR_QTYPE_AFSDB, ///< Number of QTYPE = AFSDB queries received + QR_QTYPE_X25, ///< Number of QTYPE = X25 queries received + QR_QTYPE_ISDN, ///< Number of QTYPE = ISDN queries received + QR_QTYPE_RT, ///< Number of QTYPE = RT queries received + QR_QTYPE_NSAP, ///< Number of QTYPE = NSAP queries received + QR_QTYPE_NSAP_PTR, ///< Number of QTYPE = NSAP-PTR queries received + QR_QTYPE_SIG, ///< Number of QTYPE = SIG queries received + QR_QTYPE_KEY, ///< Number of QTYPE = KEY queries received + QR_QTYPE_PX, ///< Number of QTYPE = PX queries received + QR_QTYPE_GPOS, ///< Number of QTYPE = GPOS queries received + QR_QTYPE_AAAA, ///< Number of QTYPE = AAAA queries received + QR_QTYPE_LOC, ///< Number of QTYPE = LOC queries received + QR_QTYPE_NXT, ///< Number of QTYPE = NXT queries received + QR_QTYPE_EID, ///< Number of QTYPE = EID queries received + QR_QTYPE_NIMLOC, ///< Number of QTYPE = NIMLOC queries received + QR_QTYPE_SRV, ///< Number of QTYPE = SRV queries received + QR_QTYPE_ATMA, ///< Number of QTYPE = ATMA queries received + QR_QTYPE_NAPTR, ///< Number of QTYPE = NAPTR queries received + QR_QTYPE_KX, ///< Number of QTYPE = KX queries received + QR_QTYPE_CERT, ///< Number of QTYPE = CERT queries received + QR_QTYPE_A6, ///< Number of QTYPE = A6 queries received + QR_QTYPE_DNAME, ///< Number of QTYPE = DNAME queries received + QR_QTYPE_SINK, ///< Number of QTYPE = SINK queries received + QR_QTYPE_OPT, ///< Number of QTYPE = OPT queries received + QR_QTYPE_APL, ///< Number of QTYPE = APL queries received + QR_QTYPE_DS, ///< Number of QTYPE = DS queries received + QR_QTYPE_SSHFP, ///< Number of QTYPE = SSHFP queries received + QR_QTYPE_IPSECKEY, ///< Number of QTYPE = IPSECKEY queries received + QR_QTYPE_RRSIG, ///< Number of QTYPE = RRSIG queries received + QR_QTYPE_NSEC, ///< Number of QTYPE = NSEC queries received + QR_QTYPE_DNSKEY, ///< Number of QTYPE = DNSKEY queries received + QR_QTYPE_DHCID, ///< Number of QTYPE = DHCID queries received + QR_QTYPE_NSEC3, ///< Number of QTYPE = NSEC3 queries received + QR_QTYPE_NSEC3PARAM, ///< Number of QTYPE = NSEC3PARAM queries received + QR_QTYPE_HIP, ///< Number of QTYPE = HIP queries received + QR_QTYPE_NINFO, ///< Number of QTYPE = NINFO queries received + QR_QTYPE_RKEY, ///< Number of QTYPE = RKEY queries received + QR_QTYPE_TALINK, ///< Number of QTYPE = TALINK queries received + QR_QTYPE_SPF, ///< Number of QTYPE = SPF queries received + QR_QTYPE_UINFO, ///< Number of QTYPE = UINFO queries received + QR_QTYPE_UID, ///< Number of QTYPE = UID queries received + QR_QTYPE_GID, ///< Number of QTYPE = GID queries received + QR_QTYPE_UNSPEC, ///< Number of QTYPE = UNSPEC queries received + QR_QTYPE_TKEY, ///< Number of QTYPE = TKEY queries received + QR_QTYPE_TSIG, ///< Number of QTYPE = TSIG queries received + QR_QTYPE_IXFR, ///< Number of QTYPE = IXFR queries received + QR_QTYPE_AXFR, ///< Number of QTYPE = AXFR queries received + QR_QTYPE_MAILB, ///< Number of QTYPE = MAILB queries received + QR_QTYPE_MAILA, ///< Number of QTYPE = MAILA queries received + QR_QTYPE_URI, ///< Number of QTYPE = URI queries received + QR_QTYPE_CAA, ///< Number of QTYPE = CAA queries received + QR_QTYPE_TA, ///< Number of QTYPE = TA queries received + QR_QTYPE_DLV, ///< Number of QTYPE = DLV queries received + QR_QTYPE_OTHER, ///< Number of queries in other QTYPE received + // Respose Attributes + QR_RESPONSE, ///< Number of responses sent + QR_RESPONSE_TRUNCATED, ///< Number of truncated responses sent + QR_RESPONSE_EDNS0, ///< Number of responses with EDNS0; not implemented in BIND 10 + QR_RESPONSE_TSIG, ///< Number of responses with TSIG + QR_RESPONSE_SIG0, ///< Number of responses with SIG(0); not implemented in BIND 10 + QR_QRYSUCCESS, ///< Number of queries resulted in rcode = NOERROR and answer RR >= 1 + QR_QRYAUTHANS, ///< Number of queries resulted in authoritative answer + QR_QRYNOAUTHANS, ///< Number of queries resulted in non-authoritative answer + QR_QRYREFERRAL, ///< Number of queries resulted in referral answer + QR_QRYNXRRSET, ///< Number of queries resulted in NOERROR but answer RR == 0 + QR_QRYREJECT, ///< Number of queries rejected + // Response Rcodes + QR_RCODE_NOERROR, ///< Number of queries resulted in RCODE = 0 (NoError) + QR_RCODE_FORMERR, ///< Number of queries resulted in RCODE = 1 (FormErr) + QR_RCODE_SERVFAIL, ///< Number of queries resulted in RCODE = 2 (ServFail) + QR_RCODE_NXDOMAIN, ///< Number of queries resulted in RCODE = 3 (NXDomain) + QR_RCODE_NOTIMP, ///< Number of queries resulted in RCODE = 4 (NotImp) + QR_RCODE_REFUSED, ///< Number of queries resulted in RCODE = 5 (Refused) + QR_RCODE_YXDOMAIN, ///< Number of queries resulted in RCODE = 6 (YXDomain) + QR_RCODE_YXRRSET, ///< Number of queries resulted in RCODE = 7 (YXRRSet) + QR_RCODE_NXRRSET, ///< Number of queries resulted in RCODE = 8 (NXRRSet) + QR_RCODE_NOTAUTH, ///< Number of queries resulted in RCODE = 9 (NotAuth) + QR_RCODE_NOTZONE, ///< Number of queries resulted in RCODE = 10 (NotZone) + QR_RCODE_BADSIGVERS, ///< Number of queries resulted in RCODE = 16 (BADVERS, BADSIG) + QR_RCODE_BADKEY, ///< Number of queries resulted in RCODE = 17 (BADKEY) + QR_RCODE_BADTIME, ///< Number of queries resulted in RCODE = 18 (BADTIME) + QR_RCODE_BADMODE, ///< Number of queries resulted in RCODE = 19 (BADMODE) + QR_RCODE_BADNAME, ///< Number of queries resulted in RCODE = 20 (BADNAME) + QR_RCODE_BADALG, ///< Number of queries resulted in RCODE = 21 (BADALG) + QR_RCODE_BADTRUNC, ///< Number of queries resulted in RCODE = 22 (BADTRUNC) + QR_RCODE_OTHER, ///< Number of queries resulted in other RCODEs + // End of counter types + QR_COUNTER_TYPES ///< The number of defined counters +}; + +// item names for query/response counters +const struct CounterTypeTree QRCounterRequest[] = { + { "v4", NULL, QR_REQUEST_IPV4 }, + { "v6", NULL, QR_REQUEST_IPV6 }, + { "edns0", NULL, QR_REQUEST_EDNS0 }, + { "badednsver", NULL, QR_REQUEST_BADEDNSVER }, + { "tsig", NULL, QR_REQUEST_TSIG }, + { "sig0", NULL, QR_REQUEST_SIG0 }, + { "badsig", NULL, QR_REQUEST_BADSIG }, + { "udp", NULL, QR_REQUEST_UDP }, + { "tcp", NULL, QR_REQUEST_TCP }, + { "dnssec_ok", NULL, QR_REQUEST_DNSSEC_OK }, + { NULL, NULL, -1 } +}; +const struct CounterTypeTree QRCounterOpcode[] = { + { "query", NULL, QR_OPCODE_QUERY }, + { "iquery", NULL, QR_OPCODE_IQUERY }, + { "status", NULL, QR_OPCODE_STATUS }, + { "notify", NULL, QR_OPCODE_NOTIFY }, + { "update", NULL, QR_OPCODE_UPDATE }, + { "other", NULL, QR_OPCODE_OTHER }, + { NULL, NULL, -1 } +}; +const struct CounterTypeTree QRCounterQtype[] = { + { "a", NULL, QR_QTYPE_A, }, + { "ns", NULL, QR_QTYPE_NS }, + { "md", NULL, QR_QTYPE_MD }, + { "mf", NULL, QR_QTYPE_MF }, + { "cname", NULL, QR_QTYPE_CNAME }, + { "soa", NULL, QR_QTYPE_SOA }, + { "mb", NULL, QR_QTYPE_MB }, + { "mg", NULL, QR_QTYPE_MG }, + { "mr", NULL, QR_QTYPE_MR }, + { "null", NULL, QR_QTYPE_NULL }, + { "wks", NULL, QR_QTYPE_WKS }, + { "ptr", NULL, QR_QTYPE_PTR }, + { "hinfo", NULL, QR_QTYPE_HINFO }, + { "minfo", NULL, QR_QTYPE_MINFO }, + { "mx", NULL, QR_QTYPE_MX }, + { "txt", NULL, QR_QTYPE_TXT }, + { "rp", NULL, QR_QTYPE_RP }, + { "afsdb", NULL, QR_QTYPE_AFSDB }, + { "x25", NULL, QR_QTYPE_X25 }, + { "isdn", NULL, QR_QTYPE_ISDN }, + { "rt", NULL, QR_QTYPE_RT }, + { "nsap", NULL, QR_QTYPE_NSAP }, + { "nsap-ptr", NULL, QR_QTYPE_NSAP_PTR }, + { "sig", NULL, QR_QTYPE_SIG }, + { "key", NULL, QR_QTYPE_KEY }, + { "px", NULL, QR_QTYPE_PX }, + { "gpos", NULL, QR_QTYPE_GPOS }, + { "aaaa", NULL, QR_QTYPE_AAAA }, + { "loc", NULL, QR_QTYPE_LOC }, + { "nxt", NULL, QR_QTYPE_NXT }, + { "eid", NULL, QR_QTYPE_EID }, + { "nimloc", NULL, QR_QTYPE_NIMLOC }, + { "srv", NULL, QR_QTYPE_SRV }, + { "atma", NULL, QR_QTYPE_ATMA }, + { "naptr", NULL, QR_QTYPE_NAPTR }, + { "kx", NULL, QR_QTYPE_KX }, + { "cert", NULL, QR_QTYPE_CERT }, + { "a6", NULL, QR_QTYPE_A6 }, + { "dname", NULL, QR_QTYPE_DNAME }, + { "sink", NULL, QR_QTYPE_SINK }, + { "opt", NULL, QR_QTYPE_OPT }, + { "apl", NULL, QR_QTYPE_APL }, + { "ds", NULL, QR_QTYPE_DS }, + { "sshfp", NULL, QR_QTYPE_SSHFP }, + { "ipseckey", NULL, QR_QTYPE_IPSECKEY }, + { "rrsig", NULL, QR_QTYPE_RRSIG }, + { "nsec", NULL, QR_QTYPE_NSEC }, + { "dnskey", NULL, QR_QTYPE_DNSKEY }, + { "dhcid", NULL, QR_QTYPE_DHCID }, + { "nsec3", NULL, QR_QTYPE_NSEC3 }, + { "nsec3param", NULL, QR_QTYPE_NSEC3PARAM }, + { "hip", NULL, QR_QTYPE_HIP }, + { "ninfo", NULL, QR_QTYPE_NINFO }, + { "rkey", NULL, QR_QTYPE_RKEY }, + { "talink", NULL, QR_QTYPE_TALINK }, + { "spf", NULL, QR_QTYPE_SPF }, + { "uinfo", NULL, QR_QTYPE_UINFO }, + { "uid", NULL, QR_QTYPE_UID }, + { "gid", NULL, QR_QTYPE_GID }, + { "unspec", NULL, QR_QTYPE_UNSPEC }, + { "tkey", NULL, QR_QTYPE_TKEY }, + { "tsig", NULL, QR_QTYPE_TSIG }, + { "ixfr", NULL, QR_QTYPE_IXFR }, + { "axfr", NULL, QR_QTYPE_AXFR }, + { "mailb", NULL, QR_QTYPE_MAILB }, + { "maila", NULL, QR_QTYPE_MAILA }, + { "uri", NULL, QR_QTYPE_URI }, + { "caa", NULL, QR_QTYPE_CAA }, + { "ta", NULL, QR_QTYPE_TA }, + { "dlv", NULL, QR_QTYPE_DLV }, + { "other", NULL, QR_QTYPE_OTHER }, + { NULL, NULL, -1 } +}; +const struct CounterTypeTree QRCounterResponse[] = { + { "truncated", NULL, QR_RESPONSE_TRUNCATED }, + { "edns0", NULL, QR_RESPONSE_EDNS0 }, + { "tsig", NULL, QR_RESPONSE_TSIG }, + { "sig0", NULL, QR_RESPONSE_SIG0 }, + { NULL, NULL, -1 } +}; +const struct CounterTypeTree QRCounterRcode[] = { + { "noerror", NULL, QR_RCODE_NOERROR }, + { "formerr", NULL, QR_RCODE_FORMERR }, + { "servfail", NULL, QR_RCODE_SERVFAIL }, + { "nxdomain", NULL, QR_RCODE_NXDOMAIN }, + { "notimp", NULL, QR_RCODE_NOTIMP }, + { "refused", NULL, QR_RCODE_REFUSED }, + { "yxdomain", NULL, QR_RCODE_YXDOMAIN }, + { "yxrrset", NULL, QR_RCODE_YXRRSET }, + { "nxrrset", NULL, QR_RCODE_NXRRSET }, + { "notauth", NULL, QR_RCODE_NOTAUTH }, + { "notzone", NULL, QR_RCODE_NOTZONE }, + { "badsigvers", NULL, QR_RCODE_BADSIGVERS }, + { "badkey", NULL, QR_RCODE_BADKEY }, + { "badtime", NULL, QR_RCODE_BADTIME }, + { "badmode", NULL, QR_RCODE_BADMODE }, + { "badname", NULL, QR_RCODE_BADNAME }, + { "badalg", NULL, QR_RCODE_BADALG }, + { "badtrunc", NULL, QR_RCODE_BADTRUNC }, + { "other", NULL, QR_RCODE_OTHER }, + { NULL, NULL, -1 } +}; +const struct CounterTypeTree QRCounterTree[] = { + { "request", QRCounterRequest, -1 }, + { "opcode", QRCounterOpcode, -1 }, + { "qtype", QRCounterQtype, -1 }, + { "responses", NULL, QR_RESPONSE }, + { "response", QRCounterResponse, -1 }, + { "qrysuccess", NULL, QR_QRYSUCCESS }, + { "qryauthans", NULL, QR_QRYAUTHANS }, + { "qrynoauthans", NULL, QR_QRYNOAUTHANS }, + { "qryreferral", NULL, QR_QRYREFERRAL }, + { "qrynxrrset", NULL, QR_QRYNXRRSET }, + { "authqryrej", NULL, QR_QRYREJECT }, + { "rcode", QRCounterRcode, -1 }, + { NULL, NULL, -1 } +}; + +const int QROpCodeToQRCounterType[16] = { + QR_OPCODE_QUERY, // Opcode = 0: Query + QR_OPCODE_IQUERY, // Opcode = 1: Iquery + QR_OPCODE_STATUS, // Opcode = 2: STATUS + QR_OPCODE_OTHER, // Opcode = 3: (Unassigned) + QR_OPCODE_NOTIFY, // Opcode = 4: Notify + QR_OPCODE_UPDATE, // Opcode = 5: Update + QR_OPCODE_OTHER, // Opcode = 6: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 7: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 8: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 9: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 10: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 11: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 12: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 13: (Unassigned) + QR_OPCODE_OTHER, // Opcode = 14: (Unassigned) + QR_OPCODE_OTHER // Opcode = 15: (Unassigned) +}; +const int QRQTypeToQRCounterType[258] = { + QR_QTYPE_OTHER, // RRtype = 0: special use + QR_QTYPE_A, // RRtype = 1: A + QR_QTYPE_NS, // RRtype = 2: NS + QR_QTYPE_MD, // RRtype = 3: MD + QR_QTYPE_MF, // RRtype = 4: MF + QR_QTYPE_CNAME, // RRtype = 5: CNAME + QR_QTYPE_SOA, // RRtype = 6: SOA + QR_QTYPE_MB, // RRtype = 7: MB + QR_QTYPE_MG, // RRtype = 8: MG + QR_QTYPE_MR, // RRtype = 9: MR + QR_QTYPE_NULL, // RRtype = 10: NULL + QR_QTYPE_WKS, // RRtype = 11: WKS + QR_QTYPE_PTR, // RRtype = 12: PTR + QR_QTYPE_HINFO, // RRtype = 13: HINFO + QR_QTYPE_MINFO, // RRtype = 14: MINFO + QR_QTYPE_MX, // RRtype = 15: MX + QR_QTYPE_TXT, // RRtype = 16: TXT + QR_QTYPE_RP, // RRtype = 17: RP + QR_QTYPE_AFSDB, // RRtype = 18: AFSDB + QR_QTYPE_X25, // RRtype = 19: X25 + QR_QTYPE_ISDN, // RRtype = 20: ISDN + QR_QTYPE_RT, // RRtype = 21: RT + QR_QTYPE_NSAP, // RRtype = 22: NSAP + QR_QTYPE_NSAP_PTR, // RRtype = 23: NSAP-PTR + QR_QTYPE_SIG, // RRtype = 24: SIG + QR_QTYPE_KEY, // RRtype = 25: KEY + QR_QTYPE_PX, // RRtype = 26: PX + QR_QTYPE_GPOS, // RRtype = 27: GPOS + QR_QTYPE_AAAA, // RRtype = 28: AAAA + QR_QTYPE_LOC, // RRtype = 29: LOC + QR_QTYPE_NXT, // RRtype = 30: NXT + QR_QTYPE_EID, // RRtype = 31: EID + QR_QTYPE_NIMLOC, // RRtype = 32: NIMLOC + QR_QTYPE_SRV, // RRtype = 33: SRV + QR_QTYPE_ATMA, // RRtype = 34: ATMA + QR_QTYPE_NAPTR, // RRtype = 35: NAPTR + QR_QTYPE_KX, // RRtype = 36: KX + QR_QTYPE_CERT, // RRtype = 37: CERT + QR_QTYPE_A6, // RRtype = 38: A6 + QR_QTYPE_DNAME, // RRtype = 39: DNAME + QR_QTYPE_SINK, // RRtype = 40: SINK + QR_QTYPE_OPT, // RRtype = 41: OPT + QR_QTYPE_APL, // RRtype = 42: APL + QR_QTYPE_DS, // RRtype = 43: DS + QR_QTYPE_SSHFP, // RRtype = 44: SSHFP + QR_QTYPE_IPSECKEY, // RRtype = 45: IPSECKEY + QR_QTYPE_RRSIG, // RRtype = 46: RRSIG + QR_QTYPE_NSEC, // RRtype = 47: NSEC + QR_QTYPE_DNSKEY, // RRtype = 48: DNSKEY + QR_QTYPE_DHCID, // RRtype = 49: DHCID + QR_QTYPE_NSEC3, // RRtype = 50: NSEC3 + QR_QTYPE_NSEC3PARAM, // RRtype = 51: NSEC3PARAM + QR_QTYPE_OTHER, // RRtype = 52: TLSA + QR_QTYPE_OTHER, // RRtype = 53: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 54: (Unassigned) + QR_QTYPE_HIP, // RRtype = 55: HIP + QR_QTYPE_NINFO, // RRtype = 56: NINFO + QR_QTYPE_RKEY, // RRtype = 57: RKEY + QR_QTYPE_TALINK, // RRtype = 58: TALINK + QR_QTYPE_OTHER, // RRtype = 59: CDS + QR_QTYPE_OTHER, // RRtype = 60: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 61: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 62: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 63: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 64: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 65: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 66: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 67: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 68: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 69: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 70: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 71: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 72: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 73: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 74: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 75: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 76: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 77: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 78: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 79: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 80: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 81: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 82: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 83: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 84: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 85: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 86: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 87: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 88: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 89: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 90: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 91: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 92: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 93: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 94: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 95: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 96: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 97: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 98: (Unassigned) + QR_QTYPE_SPF, // RRtype = 99: SPF + QR_QTYPE_UINFO, // RRtype = 100: UINFO + QR_QTYPE_UID, // RRtype = 101: UID + QR_QTYPE_GID, // RRtype = 102: GID + QR_QTYPE_UNSPEC, // RRtype = 103: UNSPEC + QR_QTYPE_OTHER, // RRtype = 104: NID + QR_QTYPE_OTHER, // RRtype = 105: L32 + QR_QTYPE_OTHER, // RRtype = 106: L64 + QR_QTYPE_OTHER, // RRtype = 107: LP + QR_QTYPE_OTHER, // RRtype = 108: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 109: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 110: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 111: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 112: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 113: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 114: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 115: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 116: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 117: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 118: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 119: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 120: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 121: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 122: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 123: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 124: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 125: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 126: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 127: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 128: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 129: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 130: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 131: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 132: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 133: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 134: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 135: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 136: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 137: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 138: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 139: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 140: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 141: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 142: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 143: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 144: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 145: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 146: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 147: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 148: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 149: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 150: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 151: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 152: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 153: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 154: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 155: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 156: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 157: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 158: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 159: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 160: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 161: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 162: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 163: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 164: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 165: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 166: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 167: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 168: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 169: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 170: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 171: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 172: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 173: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 174: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 175: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 176: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 177: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 178: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 179: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 180: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 181: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 182: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 183: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 184: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 185: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 186: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 187: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 188: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 189: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 190: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 191: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 192: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 193: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 194: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 195: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 196: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 197: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 198: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 199: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 200: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 201: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 202: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 203: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 204: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 205: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 206: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 207: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 208: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 209: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 210: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 211: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 212: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 213: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 214: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 215: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 216: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 217: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 218: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 219: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 220: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 221: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 222: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 223: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 224: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 225: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 226: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 227: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 228: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 229: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 230: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 231: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 232: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 233: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 234: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 235: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 236: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 237: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 238: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 239: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 240: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 241: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 242: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 243: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 244: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 245: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 246: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 247: (Unassigned) + QR_QTYPE_OTHER, // RRtype = 248: (Unassigned) + QR_QTYPE_TKEY, // RRtype = 249: TKEY + QR_QTYPE_TSIG, // RRtype = 250: TSIG + QR_QTYPE_IXFR, // RRtype = 251: IXFR + QR_QTYPE_AXFR, // RRtype = 252: AXFR + QR_QTYPE_MAILB, // RRtype = 253: MAILB + QR_QTYPE_MAILA, // RRtype = 254: MAILA + QR_QTYPE_OTHER, // RRtype = 255: for All records + QR_QTYPE_URI, // RRtype = 256: URI + QR_QTYPE_CAA // RRtype = 257: CAA +}; +const int QRRCodeToQRCounterType[23] = { + QR_RCODE_NOERROR, // Rcode = 0: NoError + QR_RCODE_FORMERR, // Rcode = 1: FormErr + QR_RCODE_SERVFAIL, // Rcode = 2: ServFail + QR_RCODE_NXDOMAIN, // Rcode = 3: NXDomain + QR_RCODE_NOTIMP, // Rcode = 4: NotImp + QR_RCODE_REFUSED, // Rcode = 5: Refused + QR_RCODE_YXDOMAIN, // Rcode = 6: YXDomain + QR_RCODE_YXRRSET, // Rcode = 7: YXRRSet + QR_RCODE_NXRRSET, // Rcode = 8: NXRRSet + QR_RCODE_NOTAUTH, // Rcode = 9: NotAuth + QR_RCODE_NOTZONE, // Rcode = 10: NotZone + QR_RCODE_OTHER, // Rcode = 11: (Unassigned) + QR_RCODE_OTHER, // Rcode = 12: (Unassigned) + QR_RCODE_OTHER, // Rcode = 13: (Unassigned) + QR_RCODE_OTHER, // Rcode = 14: (Unassigned) + QR_RCODE_OTHER, // Rcode = 15: (Unassigned) + QR_RCODE_BADSIGVERS, // Rcode = 16: BADVERS, BADSIG + QR_RCODE_BADKEY, // Rcode = 17: BADKEY + QR_RCODE_BADTIME, // Rcode = 18: BADTIME + QR_RCODE_BADMODE, // Rcode = 19: BADMODE + QR_RCODE_BADNAME, // Rcode = 20: BADNAME + QR_RCODE_BADALG, // Rcode = 21: BADALG + QR_RCODE_BADTRUNC // Rcode = 22: BADTRUNC +}; + +} // anonymous namespace + +#endif // __STATISTICS_ITEMS_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am index 6b9d385298d34651a8d62c12875e7f3febf593e7..6a91309011840812973e1d97c2c7534d2708e93f 100644 --- a/src/bin/auth/tests/Makefile.am +++ b/src/bin/auth/tests/Makefile.am @@ -41,7 +41,7 @@ run_unittests_SOURCES += ../query.h ../query.cc run_unittests_SOURCES += ../auth_config.h ../auth_config.cc run_unittests_SOURCES += ../command.h ../command.cc run_unittests_SOURCES += ../common.h ../common.cc -run_unittests_SOURCES += ../statistics.h ../statistics.cc +run_unittests_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h run_unittests_SOURCES += ../datasrc_config.h ../datasrc_config.cc run_unittests_SOURCES += datasrc_util.h datasrc_util.cc run_unittests_SOURCES += auth_srv_unittest.cc @@ -51,6 +51,9 @@ run_unittests_SOURCES += command_unittest.cc run_unittests_SOURCES += common_unittest.cc run_unittests_SOURCES += query_unittest.cc run_unittests_SOURCES += statistics_unittest.cc +run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc +run_unittests_SOURCES += datasrc_clients_builder_unittest.cc +run_unittests_SOURCES += datasrc_clients_mgr_unittest.cc run_unittests_SOURCES += datasrc_config_unittest.cc run_unittests_SOURCES += run_unittests.cc @@ -72,7 +75,6 @@ run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libb10-nsas.la run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la -run_unittests_LDADD += $(top_builddir)/src/lib/statistics/libb10-statistics.la run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la run_unittests_LDADD += $(GTEST_LDADD) diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc index e248e81483e8cf3cd181fdb1c2a21c1f543e4760..8015043bf137e9f419c0a5178d83c00b64fdef8f 100644 --- a/src/bin/auth/tests/auth_srv_unittest.cc +++ b/src/bin/auth/tests/auth_srv_unittest.cc @@ -15,7 +15,6 @@ #include #include -#include #include #include @@ -36,10 +35,10 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -70,12 +69,15 @@ using namespace isc::util::unittests; using namespace isc::dns::rdata; using namespace isc::data; using namespace isc::xfr; +using namespace isc::auth; using namespace isc::asiodns; using namespace isc::asiolink; using namespace isc::testutils; using namespace isc::server_common::portconfig; +using isc::datasrc::memory::ZoneTableSegment; using isc::UnitTestUtil; using boost::scoped_ptr; +using isc::auth::statistics::Counters; namespace { const char* const CONFIG_TESTDB = @@ -123,29 +125,47 @@ protected: // Helper for checking Rcode statistic counters; // Checks for one specific Rcode statistics counter value - void checkRcodeCounter(const Rcode& rcode, int expected_value) const { - EXPECT_EQ(expected_value, server.getCounter(rcode)) << - "Expected Rcode count for " << rcode.toText() << - " " << expected_value << ", was: " << - server.getCounter(rcode); + void checkRcodeCounter(const std::string& rcode_name, const int rcode_value, + const int expected_value) const + { + EXPECT_EQ(expected_value, rcode_value) << + "Expected Rcode count for " << rcode_name << + " " << expected_value << ", was: " << + rcode_value; } // Checks whether all Rcode counters are set to zero void checkAllRcodeCountersZero() const { - for (int i = 0; i < 17; i++) { - checkRcodeCounter(Rcode(i), 0); - } + // with checking NOERROR == 0 and the others are 0 + checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 0); } // Checks whether all Rcode counters are set to zero except the given // rcode (it is checked to be set to 'value') void checkAllRcodeCountersZeroExcept(const Rcode& rcode, int value) const { - for (int i = 0; i < 17; i++) { - const Rcode rc(i); - if (rc == rcode) { - checkRcodeCounter(Rcode(i), value); - } else { - checkRcodeCounter(Rcode(i), 0); + std::string target_rcode_name = rcode.toText(); + std::transform(target_rcode_name.begin(), target_rcode_name.end(), + target_rcode_name.begin(), ::tolower); + // rcode 16 is registered as both BADVERS and BADSIG + if (target_rcode_name == "badvers") { + target_rcode_name = "badsigvers"; + } + + const std::map + stats_map(server.getStatistics()->mapValue()); + + const std::string rcode_prefix("rcode."); + for (std::map::const_iterator + i = stats_map.begin(), e = stats_map.end(); + i != e; + ++i) + { + if (i->first.compare(0, rcode_prefix.size(), rcode_prefix) == 0) { + if (i->first.compare(rcode_prefix + target_rcode_name) == 0) { + checkRcodeCounter(i->first, i->second->intValue(), value); + } else { + checkRcodeCounter(i->first, i->second->intValue(), 0); + } } } } @@ -222,6 +242,29 @@ createBuiltinVersionResponse(const qid_t qid, vector& data) { renderer.getLength()); } +// Check if the item has expected value. +// Before reading the item, check the item exists. +void +expectCounterItem(ConstElementPtr stats, + const std::string& item, const int expected) { + ConstElementPtr value(Element::create(0)); + if (item == "queries.udp" || item == "queries.tcp" || expected != 0) { + // if the value of the item is not zero, the item exists and has + // expected value + // item "queries.udp" and "queries.tcp" exists whether the value + // is zero or nonzero + ASSERT_TRUE(stats->find(item, value)) << " Item: " << item; + // Get the value of the item with another method because of API bug + // (ticket #2302) + value = stats->find(item); + EXPECT_EQ(expected, value->intValue()) << " Item: " << item; + } else { + // otherwise the item does not exist + ASSERT_FALSE(stats->find(item, value)) << " Item: " << item << + std::endl << " Value: " << value->intValue(); + } +} + // We did not configure any client lists. Therefore it should be REFUSED TEST_F(AuthSrvTest, noClientList) { UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(), @@ -405,7 +448,9 @@ TEST_F(AuthSrvTest, TSIGCheckFirst) { "It should be unsigned with this error"; // TSIG should have failed, and so the per opcode counter shouldn't be // incremented. - EXPECT_EQ(0, server.getCounter(Opcode::RESERVED14())); + ConstElementPtr stats = server.getStatistics(); + expectCounterItem(stats, "opcode.normal", 0); + expectCounterItem(stats, "opcode.other", 0); checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1); } @@ -726,11 +771,11 @@ TEST_F(AuthSrvTest, notifyWithSessionMessageError) { } void -installDataSrcClientLists(AuthSrv& server, - AuthSrv::DataSrcClientListsPtr lists) -{ - thread::Mutex::Locker locker(server.getDataSrcClientListMutex()); - server.swapDataSrcClientLists(lists); +installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) { + // For now, we use explicit swap than reconfigure() because the latter + // involves a separate thread and cannot guarantee the new config is + // available for the subsequent test. + server.getDataSrcClientsMgr().setDataSrcClientLists(lists); } void @@ -1041,8 +1086,12 @@ TEST_F(AuthSrvTest, // Submit UDP normal query and check query counter TEST_F(AuthSrvTest, queryCounterUDPNormal) { - // The counter should be initialized to 0. - EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_UDP_QUERY)); + // The counters should be initialized to 0. + ConstElementPtr stats_init = server.getStatistics(); + expectCounterItem(stats_init, "queries.udp", 0); + expectCounterItem(stats_init, "queries.tcp", 0); + expectCounterItem(stats_init, "opcode.query", 0); + expectCounterItem(stats_init, "rcode.refused", 0); // Create UDP message and process. UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(), default_qid, Name("example.com"), @@ -1050,18 +1099,25 @@ TEST_F(AuthSrvTest, queryCounterUDPNormal) { createRequestPacket(request_message, IPPROTO_UDP); server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv); - // After processing UDP query, the counter should be 1. - EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_UDP_QUERY)); - // The counter for opcode Query should also be one - EXPECT_EQ(1, server.getCounter(Opcode::QUERY())); - // The counter for REFUSED responses should also be one, the rest zero - checkAllRcodeCountersZeroExcept(Rcode::REFUSED(), 1); + // After processing the UDP query, these counters should be incremented: + // queries.udp, opcode.query, rcode.refused + // and these counters should not be incremented: + // queries.tcp + ConstElementPtr stats_after = server.getStatistics(); + expectCounterItem(stats_after, "queries.udp", 1); + expectCounterItem(stats_after, "queries.tcp", 0); + expectCounterItem(stats_after, "opcode.query", 1); + expectCounterItem(stats_after, "rcode.refused", 1); } // Submit TCP normal query and check query counter TEST_F(AuthSrvTest, queryCounterTCPNormal) { - // The counter should be initialized to 0. - EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_TCP_QUERY)); + // The counters should be initialized to 0. + ConstElementPtr stats_init = server.getStatistics(); + expectCounterItem(stats_init, "queries.udp", 0); + expectCounterItem(stats_init, "queries.tcp", 0); + expectCounterItem(stats_init, "opcode.query", 0); + expectCounterItem(stats_init, "rcode.refused", 0); // Create TCP message and process. UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(), default_qid, Name("example.com"), @@ -1069,18 +1125,24 @@ TEST_F(AuthSrvTest, queryCounterTCPNormal) { createRequestPacket(request_message, IPPROTO_TCP); server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv); - // After processing TCP query, the counter should be 1. - EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY)); - // The counter for SUCCESS responses should also be one - EXPECT_EQ(1, server.getCounter(Opcode::QUERY())); - // The counter for REFUSED responses should also be one, the rest zero - checkAllRcodeCountersZeroExcept(Rcode::REFUSED(), 1); + // After processing the TCP query, these counters should be incremented: + // queries.tcp, opcode.query, rcode.refused + // and these counters should not be incremented: + // queries.udp + ConstElementPtr stats_after = server.getStatistics(); + expectCounterItem(stats_after, "queries.udp", 0); + expectCounterItem(stats_after, "queries.tcp", 1); + expectCounterItem(stats_after, "opcode.query", 1); + expectCounterItem(stats_after, "rcode.refused", 1); } // Submit TCP AXFR query and check query counter TEST_F(AuthSrvTest, queryCounterTCPAXFR) { - // The counter should be initialized to 0. - EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_TCP_QUERY)); + // The counters should be initialized to 0. + ConstElementPtr stats_init = server.getStatistics(); + expectCounterItem(stats_init, "queries.udp", 0); + expectCounterItem(stats_init, "queries.tcp", 0); + expectCounterItem(stats_init, "opcode.query", 0); UnitTestUtil::createRequestMessage(request_message, opcode, default_qid, Name("example.com"), RRClass::IN(), RRType::AXFR()); createRequestPacket(request_message, IPPROTO_TCP); @@ -1089,16 +1151,24 @@ TEST_F(AuthSrvTest, queryCounterTCPAXFR) { server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv); EXPECT_FALSE(dnsserv.hasAnswer()); - // After processing TCP AXFR query, the counter should be 1. - EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY)); - // No rcodes should be incremented - checkAllRcodeCountersZero(); + // After processing the TCP AXFR query, these counters should be + // incremented: + // queries.tcp, opcode.query + // and these counters should not be incremented: + // queries.udp + ConstElementPtr stats_after = server.getStatistics(); + expectCounterItem(stats_after, "queries.udp", 0); + expectCounterItem(stats_after, "queries.tcp", 1); + expectCounterItem(stats_after, "opcode.query", 1); } // Submit TCP IXFR query and check query counter TEST_F(AuthSrvTest, queryCounterTCPIXFR) { - // The counter should be initialized to 0. - EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_TCP_QUERY)); + // The counters should be initialized to 0. + ConstElementPtr stats_init = server.getStatistics(); + expectCounterItem(stats_init, "queries.udp", 0); + expectCounterItem(stats_init, "queries.tcp", 0); + expectCounterItem(stats_init, "opcode.query", 0); UnitTestUtil::createRequestMessage(request_message, opcode, default_qid, Name("example.com"), RRClass::IN(), RRType::IXFR()); createRequestPacket(request_message, IPPROTO_TCP); @@ -1107,14 +1177,27 @@ TEST_F(AuthSrvTest, queryCounterTCPIXFR) { server.processMessage(*io_message, *parse_message, *response_obuffer, &dnsserv); EXPECT_FALSE(dnsserv.hasAnswer()); - // After processing TCP IXFR query, the counter should be 1. - EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY)); + // After processing the TCP IXFR query, these counters should be + // incremented: + // queries.tcp, opcode.query + // and these counters should not be incremented: + // queries.udp + ConstElementPtr stats_after = server.getStatistics(); + expectCounterItem(stats_after, "queries.udp", 0); + expectCounterItem(stats_after, "queries.tcp", 1); + expectCounterItem(stats_after, "opcode.query", 1); } TEST_F(AuthSrvTest, queryCounterOpcodes) { - for (int i = 0; i < 16; ++i) { + // Check for 0..2, 3(=other), 4..5 + // The counter should be initialized to 0. + for (int i = 0; i < 6; ++i) { // The counter should be initialized to 0. - EXPECT_EQ(0, server.getCounter(Opcode(i))); + expectCounterItem(server.getStatistics(), + std::string("opcode.") + + QRCounterOpcode[QROpCodeToQRCounterType[i] - + QR_OPCODE_QUERY].name, + 0); // For each possible opcode, create a request message and send it UnitTestUtil::createRequestMessage(request_message, Opcode(i), @@ -1132,7 +1215,45 @@ TEST_F(AuthSrvTest, queryCounterOpcodes) { } // Confirm the counter. - EXPECT_EQ(i + 1, server.getCounter(Opcode(i))); + expectCounterItem(server.getStatistics(), + std::string("opcode.") + + QRCounterOpcode[QROpCodeToQRCounterType[i] - + QR_OPCODE_QUERY].name, + i + 1); + } + // Check for 6..15 + // they are treated as the 'other' opcode + // the 'other' opcode counter is 4 at this point + int expected = 4; + for (int i = 6; i < 16; ++i) { + // The counter should be initialized to 0. + expectCounterItem(server.getStatistics(), + std::string("opcode.") + + QRCounterOpcode[QROpCodeToQRCounterType[i] - + QR_OPCODE_QUERY].name, + expected); + + // For each possible opcode, create a request message and send it + UnitTestUtil::createRequestMessage(request_message, Opcode(i), + default_qid, Name("example.com"), + RRClass::IN(), RRType::NS()); + createRequestPacket(request_message, IPPROTO_UDP); + + // "send" the request once + parse_message->clear(Message::PARSE); + server.processMessage(*io_message, *parse_message, + *response_obuffer, + &dnsserv); + + // the 'other' opcode counter should be incremented + ++expected; + + // Confirm the counter. + expectCounterItem(server.getStatistics(), + std::string("opcode.") + + QRCounterOpcode[QROpCodeToQRCounterType[i] - + QR_OPCODE_QUERY].name, + expected); } } @@ -1401,7 +1522,9 @@ public: real_list, ThrowWhen throw_when, bool isc_exception, ConstRRsetPtr fake_rrset = ConstRRsetPtr()) : ConfigurableClientList(RRClass::IN()), - real_(real_list) + real_(real_list), + config_(Element::fromJSON("{}")), + ztable_segment_(ZoneTableSegment::create(*config_, RRClass::IN())) { BOOST_FOREACH(const DataSourceInfo& info, real_->getDataSources()) { const isc::datasrc::DataSourceClientPtr @@ -1413,13 +1536,14 @@ public: data_sources_.push_back( DataSourceInfo(client.get(), isc::datasrc::DataSourceClientContainerPtr(), - false, RRClass::IN(), mem_sgmt_)); + false, RRClass::IN(), ztable_segment_)); } } private: const boost::shared_ptr real_; + const ConstElementPtr config_; + boost::shared_ptr ztable_segment_; vector clients_; - MemorySegmentLocal mem_sgmt_; }; } // end anonymous namespace for throwing proxy classes @@ -1438,16 +1562,16 @@ TEST_F(AuthSrvTest, { // Set real inmem client to proxy updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE); + boost::shared_ptr list; + DataSrcClientsMgr& mgr = server.getDataSrcClientsMgr(); { - isc::util::thread::Mutex::Locker locker( - server.getDataSrcClientListMutex()); - boost::shared_ptr - list(new FakeList(server.getDataSrcClientList(RRClass::IN()), - THROW_NEVER, false)); - AuthSrv::DataSrcClientListsPtr lists(new std::map); - lists->insert(pair(RRClass::IN(), list)); - server.swapDataSrcClientLists(lists); + DataSrcClientsMgr::Holder holder(mgr); + list.reset(new FakeList(holder.findClientList(RRClass::IN()), + THROW_NEVER, false)); } + ClientListMapPtr lists(new std::map); + lists->insert(pair(RRClass::IN(), list)); + server.getDataSrcClientsMgr().setDataSrcClientLists(lists); createDataFromFile("nsec3query_nodnssec_fromWire.wire"); server.processMessage(*io_message, *parse_message, *response_obuffer, @@ -1470,14 +1594,16 @@ setupThrow(AuthSrv& server, ThrowWhen throw_when, bool isc_exception, { updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE); - isc::util::thread::Mutex::Locker locker( - server.getDataSrcClientListMutex()); - boost::shared_ptr - list(new FakeList(server.getDataSrcClientList(RRClass::IN()), - throw_when, isc_exception, rrset)); - AuthSrv::DataSrcClientListsPtr lists(new std::map); + boost::shared_ptr list; + DataSrcClientsMgr& mgr = server.getDataSrcClientsMgr(); + { // we need to limit the scope so swap is outside of it + DataSrcClientsMgr::Holder holder(mgr); + list.reset(new FakeList(holder.findClientList(RRClass::IN()), + throw_when, isc_exception, rrset)); + } + ClientListMapPtr lists(new std::map); lists->insert(pair(RRClass::IN(), list)); - server.swapDataSrcClientLists(lists); + mgr.setDataSrcClientLists(lists); } TEST_F(AuthSrvTest, @@ -1713,6 +1839,15 @@ namespace { isc::config::parseAnswer(command_result, response); EXPECT_EQ(0, command_result); } + + void sendCommand(AuthSrv& server, const std::string& command, + ConstElementPtr args, int expected_result) { + ConstElementPtr response = execAuthServerCommand(server, command, + args); + int command_result = -1; + isc::config::parseAnswer(command_result, response); + EXPECT_EQ(expected_result, command_result); + } } // end anonymous namespace TEST_F(AuthSrvTest, DDNSForwardCreateDestroy) { @@ -1784,57 +1919,18 @@ TEST_F(AuthSrvTest, DDNSForwardCreateDestroy) { Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0); } -// Check the client list accessors -TEST_F(AuthSrvTest, clientList) { - // We need to lock the mutex to make the (get|set)ClientList happy. - // There's a debug-build only check in them to make sure everything - // locks them and we call them directly here. - isc::util::thread::Mutex::Locker locker( - server.getDataSrcClientListMutex()); - - AuthSrv::DataSrcClientListsPtr lists; // initially empty - - // The lists don't exist. Therefore, the list of RRClasses is empty. - EXPECT_TRUE(server.swapDataSrcClientLists(lists)->empty()); +TEST_F(AuthSrvTest, loadZoneCommand) { + // Just some very basic tests, to check the command is accepted, and that + // it raises on bad arguments, but not on correct ones (full testing + // is handled in the unit tests for the corresponding classes) - // Put something in. - const ListPtr list(new ConfigurableClientList(RRClass::IN())); - const ListPtr list2(new ConfigurableClientList(RRClass::CH())); - - lists.reset(new std::map); - lists->insert(pair(RRClass::IN(), list)); - lists->insert(pair(RRClass::CH(), list2)); - server.swapDataSrcClientLists(lists); - - // And the lists can be retrieved. - EXPECT_EQ(list, server.getDataSrcClientList(RRClass::IN())); - EXPECT_EQ(list2, server.getDataSrcClientList(RRClass::CH())); - - // Replace the lists with new lists containing only one list. - lists.reset(new std::map); - lists->insert(pair(RRClass::IN(), list)); - lists = server.swapDataSrcClientLists(lists); - - // Old one had two lists. That confirms our swap for IN and CH classes - // (i.e., no other entries were there). - EXPECT_EQ(2, lists->size()); - - // The CH list really got deleted. - EXPECT_EQ(list, server.getDataSrcClientList(RRClass::IN())); - EXPECT_FALSE(server.getDataSrcClientList(RRClass::CH())); -} - -// We just test the mutex can be locked (exactly once). -TEST_F(AuthSrvTest, mutex) { - isc::util::thread::Mutex::Locker l1(server.getDataSrcClientListMutex()); - // TODO: Once we have non-debug build, this one will not work, since - // we currently use the fact that we can't lock twice from the same - // thread. In the non-debug mode, this would deadlock. - // Skip then. - EXPECT_THROW({ - isc::util::thread::Mutex::Locker l2( - server.getDataSrcClientListMutex()); - }, isc::InvalidOperation); + // Empty map should fail + ElementPtr args(Element::createMap()); + sendCommand(server, "loadzone", args, 1); + // Setting an origin should be enough (even if it isn't actually loaded, + // it should be initially accepted) + args->set("origin", Element::create("example.com")); + sendCommand(server, "loadzone", args, 0); } } diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc index 8aa2322151a145b59d172f01c45644f7caa2808e..be90d736f84c04fd4b23e23210365cf0f12b2320 100644 --- a/src/bin/auth/tests/command_unittest.cc +++ b/src/bin/auth/tests/command_unittest.cc @@ -16,10 +16,7 @@ #include "datasrc_util.h" -#include - #include -#include #include #include @@ -58,6 +55,7 @@ using namespace isc::datasrc; using namespace isc::config; using namespace isc::util::unittests; using namespace isc::testutils; +using namespace isc::auth; using namespace isc::auth::unittest; namespace { @@ -171,285 +169,6 @@ TEST_F(AuthCommandTest, shutdownIncorrectPID) { EXPECT_EQ(0, rcode_); } -// A helper function commonly used for the "loadzone" command tests. -// It configures the server with a memory data source containing two -// zones, and checks the zones are correctly loaded. -void -zoneChecks(AuthSrv& server) { - isc::util::thread::Mutex::Locker locker( - server.getDataSrcClientListMutex()); - EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test1.example")).finder_-> - find(Name("ns.test1.example"), RRType::A())->code); - EXPECT_EQ(ZoneFinder::NXRRSET, server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test1.example")).finder_-> - find(Name("ns.test1.example"), RRType::AAAA())->code); - EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test2.example")).finder_-> - find(Name("ns.test2.example"), RRType::A())->code); - EXPECT_EQ(ZoneFinder::NXRRSET, server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test2.example")).finder_-> - find(Name("ns.test2.example"), RRType::AAAA())->code); -} - -void -installDataSrcClientLists(AuthSrv& server, - AuthSrv::DataSrcClientListsPtr lists) -{ - isc::util::thread::Mutex::Locker locker( - server.getDataSrcClientListMutex()); - server.swapDataSrcClientLists(lists); -} - -void -configureZones(AuthSrv& server) { - ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in " - TEST_DATA_BUILDDIR "/test1.zone.copied")); - ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in " - TEST_DATA_BUILDDIR "/test2.zone.copied")); - - const ConstElementPtr config(Element::fromJSON("{" - "\"IN\": [{" - " \"type\": \"MasterFiles\"," - " \"params\": {" - " \"test1.example\": \"" + - string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\"," - " \"test2.example\": \"" + - string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\"" - " }," - " \"cache-enable\": true" - "}]}")); - - installDataSrcClientLists(server, configureDataSource(config)); - - zoneChecks(server); -} - -void -newZoneChecks(AuthSrv& server) { - isc::util::thread::Mutex::Locker locker( - server.getDataSrcClientListMutex()); - EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test1.example")).finder_-> - find(Name("ns.test1.example"), RRType::A())->code); - // now test1.example should have ns/AAAA - EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test1.example")).finder_-> - find(Name("ns.test1.example"), RRType::AAAA())->code); - - // test2.example shouldn't change - EXPECT_EQ(ZoneFinder::SUCCESS, server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test2.example")).finder_-> - find(Name("ns.test2.example"), RRType::A())->code); - EXPECT_EQ(ZoneFinder::NXRRSET, - server.getDataSrcClientList(RRClass::IN())-> - find(Name("ns.test2.example")).finder_-> - find(Name("ns.test2.example"), RRType::AAAA())->code); -} - -TEST_F(AuthCommandTest, loadZone) { - configureZones(server_); - - ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR - "/test1-new.zone.in " - TEST_DATA_BUILDDIR "/test1.zone.copied")); - ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR - "/test2-new.zone.in " - TEST_DATA_BUILDDIR "/test2.zone.copied")); - - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"test1.example\"}")); - checkAnswer(0); - newZoneChecks(server_); -} - -TEST_F(AuthCommandTest, -#ifdef USE_STATIC_LINK - DISABLED_loadZoneSQLite3 -#else - loadZoneSQLite3 -#endif - ) -{ - // Prepare the database first - const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied"; - const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3"; - stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n"); - createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss); - // This describes the data source in the configuration - const ConstElementPtr config(Element::fromJSON("{" - "\"IN\": [{" - " \"type\": \"sqlite3\"," - " \"params\": {\"database_file\": \"" + test_db + "\"}," - " \"cache-enable\": true," - " \"cache-zones\": [\"example.org\"]" - "}]}")); - installDataSrcClientLists(server_, configureDataSource(config)); - - { - isc::util::thread::Mutex::Locker locker( - server_.getDataSrcClientListMutex()); - // Check that the A record at www.example.org does not exist - EXPECT_EQ(ZoneFinder::NXDOMAIN, - server_.getDataSrcClientList(RRClass::IN())-> - find(Name("example.org")).finder_-> - find(Name("www.example.org"), RRType::A())->code); - - // Add the record to the underlying sqlite database, by loading - // it as a separate datasource, and updating it - ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\"," - "\"database_file\": \"" - + test_db + "\"}"); - DataSourceClientContainer sql_ds("sqlite3", sql_cfg); - ZoneUpdaterPtr sql_updater = - sql_ds.getInstance().getUpdater(Name("example.org"), false); - RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(), - RRType::A(), RRTTL(60))); - rrset->addRdata(rdata::createRdata(rrset->getType(), - rrset->getClass(), - "192.0.2.1")); - sql_updater->addRRset(*rrset); - sql_updater->commit(); - - EXPECT_EQ(ZoneFinder::NXDOMAIN, - server_.getDataSrcClientList(RRClass::IN())-> - find(Name("example.org")).finder_-> - find(Name("www.example.org"), RRType::A())->code); - } - - // Now send the command to reload it - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"example.org\"}")); - checkAnswer(0, "Successful load"); - - { - isc::util::thread::Mutex::Locker locker( - server_.getDataSrcClientListMutex()); - // And now it should be present too. - EXPECT_EQ(ZoneFinder::SUCCESS, - server_.getDataSrcClientList(RRClass::IN())-> - find(Name("example.org")).finder_-> - find(Name("www.example.org"), RRType::A())->code); - } - - // Some error cases. First, the zone has no configuration. (note .com here) - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON("{\"origin\": \"example.com\"}")); - checkAnswer(1, "example.com"); - - { - isc::util::thread::Mutex::Locker locker( - server_.getDataSrcClientListMutex()); - // The previous zone is not hurt in any way - EXPECT_EQ(ZoneFinder::SUCCESS, - server_.getDataSrcClientList(RRClass::IN())-> - find(Name("example.org")).finder_-> - find(Name("example.org"), RRType::SOA())->code); - } - - const ConstElementPtr config2(Element::fromJSON("{" - "\"IN\": [{" - " \"type\": \"sqlite3\"," - " \"params\": {\"database_file\": \"" + bad_db + "\"}," - " \"cache-enable\": true," - " \"cache-zones\": [\"example.com\"]" - "}]}")); - EXPECT_THROW(configureDataSource(config2), - ConfigurableClientList::ConfigurationError); - - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON("{\"origin\": \"example.com\"}")); - checkAnswer(1, "Unreadable"); - - isc::util::thread::Mutex::Locker locker( - server_.getDataSrcClientListMutex()); - // The previous zone is not hurt in any way - EXPECT_EQ(ZoneFinder::SUCCESS, - server_.getDataSrcClientList(RRClass::IN())-> - find(Name("example.org")).finder_-> - find(Name("example.org"), RRType::SOA())->code); -} - -TEST_F(AuthCommandTest, loadBrokenZone) { - configureZones(server_); - - ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR - "/test1-broken.zone.in " - TEST_DATA_BUILDDIR "/test1.zone.copied")); - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"test1.example\"}")); - checkAnswer(1); - zoneChecks(server_); // zone shouldn't be replaced -} - -TEST_F(AuthCommandTest, loadUnreadableZone) { - configureZones(server_); - - // install the zone file as unreadable - ASSERT_EQ(0, system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR - "/test1.zone.in " - TEST_DATA_BUILDDIR "/test1.zone.copied")); - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"test1.example\"}")); - checkAnswer(1); - zoneChecks(server_); // zone shouldn't be replaced -} - -TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) { - // try to execute load command without configuring the zone beforehand. - // it should fail. - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"test1.example\"}")); - checkAnswer(1); -} - -TEST_F(AuthCommandTest, loadZoneInvalidParams) { - configureZones(server_); - - // null arg - result_ = execAuthServerCommand(server_, "loadzone", ElementPtr()); - checkAnswer(1, "Null arg"); - - // zone class is bogus - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"test1.example\"," - " \"class\": \"no_such_class\"}")); - checkAnswer(1, "No such class"); - - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"test1.example\"," - " \"class\": 1}")); - checkAnswer(1, "Integral class"); - - - // origin is missing - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON("{}")); - checkAnswer(1, "Missing origin"); - - // zone doesn't exist in the data source - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON("{\"origin\": \"xx\"}")); - checkAnswer(1, "No such zone"); - - // origin is bogus - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON( - "{\"origin\": \"...\"}")); - checkAnswer(1, "Wrong name"); - - result_ = execAuthServerCommand(server_, "loadzone", - Element::fromJSON("{\"origin\": 10}")); - checkAnswer(1, "Integral name"); -} - TEST_F(AuthCommandTest, getStats) { result_ = execAuthServerCommand(server_, "getstats", ConstElementPtr()); parseAnswer(rcode_, result_); diff --git a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..585e7c30a49498172c18d0b28cec4a88595b3a0f --- /dev/null +++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc @@ -0,0 +1,517 @@ +// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +#include + +#include "test_datasrc_clients_mgr.h" +#include "datasrc_util.h" + +#include + +#include + +#include +#include +#include + +using isc::data::ConstElementPtr; +using namespace isc::dns; +using namespace isc::data; +using namespace isc::datasrc; +using namespace isc::auth::datasrc_clientmgr_internal; +using namespace isc::auth::unittest; +using namespace isc::testutils; + +namespace { +class DataSrcClientsBuilderTest : public ::testing::Test { +protected: + DataSrcClientsBuilderTest() : + clients_map(new std::map >), + builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex), + cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()), + shutdown_cmd(SHUTDOWN, ConstElementPtr()), + noop_cmd(NOOP, ConstElementPtr()) + {} + + void configureZones(); // used for loadzone related tests + + ClientListMapPtr clients_map; // configured clients + std::list command_queue; // test command queue + std::list delayed_command_queue; // commands available after wait + TestDataSrcClientsBuilder builder; + TestCondVar cond; + TestMutex queue_mutex; + TestMutex map_mutex; + const RRClass rrclass; + const Command shutdown_cmd; + const Command noop_cmd; +}; + +TEST_F(DataSrcClientsBuilderTest, runSingleCommand) { + // A simplest case, just to check the basic behavior. + command_queue.push_back(shutdown_cmd); + builder.run(); + EXPECT_TRUE(command_queue.empty()); + EXPECT_EQ(0, cond.wait_count); // no wait because command queue is not empty + EXPECT_EQ(1, queue_mutex.lock_count); + EXPECT_EQ(1, queue_mutex.unlock_count); +} + +TEST_F(DataSrcClientsBuilderTest, runMultiCommands) { + // Two NOOP commands followed by SHUTDOWN. We should see two doNoop() + // calls. + command_queue.push_back(noop_cmd); + command_queue.push_back(noop_cmd); + command_queue.push_back(shutdown_cmd); + builder.run(); + EXPECT_TRUE(command_queue.empty()); + EXPECT_EQ(1, queue_mutex.lock_count); + EXPECT_EQ(1, queue_mutex.unlock_count); + EXPECT_EQ(2, queue_mutex.noop_count); +} + +TEST_F(DataSrcClientsBuilderTest, exception) { + // Let the noop command handler throw exceptions and see if we can see + // them. Right now, we simply abort to prevent the system from running + // with half-broken state. Eventually we should introduce a better + // error handling. + if (!isc::util::unittests::runningOnValgrind()) { + command_queue.push_back(noop_cmd); + queue_mutex.throw_from_noop = TestMutex::EXCLASS; + EXPECT_DEATH_IF_SUPPORTED({builder.run();}, ""); + + command_queue.push_back(noop_cmd); + queue_mutex.throw_from_noop = TestMutex::INTEGER; + EXPECT_DEATH_IF_SUPPORTED({builder.run();}, ""); + } + + command_queue.push_back(noop_cmd); + command_queue.push_back(shutdown_cmd); // we need to stop the loop + queue_mutex.throw_from_noop = TestMutex::INTERNAL; + builder.run(); +} + +TEST_F(DataSrcClientsBuilderTest, condWait) { + // command_queue is originally empty, so it will require waiting on + // condvar. specialized wait() will make the delayed command available. + delayed_command_queue.push_back(shutdown_cmd); + builder.run(); + + // There should be one call to wait() + EXPECT_EQ(1, cond.wait_count); + // wait() effectively involves one more set of lock/unlock, so we have + // two in total + EXPECT_EQ(2, queue_mutex.lock_count); + EXPECT_EQ(2, queue_mutex.unlock_count); +} + +TEST_F(DataSrcClientsBuilderTest, reconfigure) { + // Full testing of different configurations is not here, but we + // do check a few cases of correct and erroneous input, to verify + // the error handling + + // A command structure we'll modify to send different commands + Command reconfig_cmd(RECONFIGURE, ConstElementPtr()); + + // Initially, no clients should be there + EXPECT_TRUE(clients_map->empty()); + + // A config that doesn't do much except be accepted + ConstElementPtr good_config = Element::fromJSON( + "{" + "\"IN\": [{" + " \"type\": \"MasterFiles\"," + " \"params\": {}," + " \"cache-enable\": true" + "}]" + "}" + ); + + // A configuration that is 'correct' in the top-level, but contains + // bad data for the type it specifies + ConstElementPtr bad_config = Element::fromJSON( + "{" + "\"IN\": [{" + " \"type\": \"MasterFiles\"," + " \"params\": { \"foo\": [ 1, 2, 3, 4 ]}," + " \"cache-enable\": true" + "}]" + "}" + ); + + reconfig_cmd.second = good_config; + EXPECT_TRUE(builder.handleCommand(reconfig_cmd)); + EXPECT_EQ(1, clients_map->size()); + EXPECT_EQ(1, map_mutex.lock_count); + + // Store the nonempty clients map we now have + ClientListMapPtr working_config_clients(clients_map); + + // If a 'bad' command argument got here, the config validation should + // have failed already, but still, the handler should return true, + // and the clients_map should not be updated. + reconfig_cmd.second = Element::create("{ \"foo\": \"bar\" }"); + EXPECT_TRUE(builder.handleCommand(reconfig_cmd)); + EXPECT_EQ(working_config_clients, clients_map); + // Building failed, so map mutex should not have been locked again + EXPECT_EQ(1, map_mutex.lock_count); + + // The same for a configuration that has bad data for the type it + // specifies + reconfig_cmd.second = bad_config; + builder.handleCommand(reconfig_cmd); + EXPECT_TRUE(builder.handleCommand(reconfig_cmd)); + EXPECT_EQ(working_config_clients, clients_map); + // Building failed, so map mutex should not have been locked again + EXPECT_EQ(1, map_mutex.lock_count); + + // The same goes for an empty parameter (it should at least be + // an empty map) + reconfig_cmd.second = ConstElementPtr(); + EXPECT_TRUE(builder.handleCommand(reconfig_cmd)); + EXPECT_EQ(working_config_clients, clients_map); + EXPECT_EQ(1, map_mutex.lock_count); + + // Reconfigure again with the same good clients, the result should + // be a different map than the original, but not an empty one. + reconfig_cmd.second = good_config; + EXPECT_TRUE(builder.handleCommand(reconfig_cmd)); + EXPECT_NE(working_config_clients, clients_map); + EXPECT_EQ(1, clients_map->size()); + EXPECT_EQ(2, map_mutex.lock_count); + + // And finally, try an empty config to disable all datasource clients + reconfig_cmd.second = Element::createMap(); + EXPECT_TRUE(builder.handleCommand(reconfig_cmd)); + EXPECT_EQ(0, clients_map->size()); + EXPECT_EQ(3, map_mutex.lock_count); + + // Also check if it has been cleanly unlocked every time + EXPECT_EQ(3, map_mutex.unlock_count); +} + +TEST_F(DataSrcClientsBuilderTest, shutdown) { + EXPECT_FALSE(builder.handleCommand(shutdown_cmd)); +} + +TEST_F(DataSrcClientsBuilderTest, badCommand) { + // out-of-range command ID + EXPECT_THROW(builder.handleCommand(Command(NUM_COMMANDS, + ConstElementPtr())), + isc::Unexpected); +} + +// A helper function commonly used for the "loadzone" command tests. +// It configures the given data source client lists with a memory data source +// containing two zones, and checks the zones are correctly loaded. +void +zoneChecks(ClientListMapPtr clients_map, RRClass rrclass) { + EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second-> + find(Name("ns.test1.example")).finder_-> + find(Name("ns.test1.example"), RRType::A())->code); + EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second-> + find(Name("ns.test1.example")).finder_-> + find(Name("ns.test1.example"), RRType::AAAA())->code); + EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second-> + find(Name("ns.test2.example")).finder_-> + find(Name("ns.test2.example"), RRType::A())->code); + EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second-> + find(Name("ns.test2.example")).finder_-> + find(Name("ns.test2.example"), RRType::AAAA())->code); +} + +// Another helper that checks after completing loadzone command. +void +newZoneChecks(ClientListMapPtr clients_map, RRClass rrclass) { + EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second-> + find(Name("ns.test1.example")).finder_-> + find(Name("ns.test1.example"), RRType::A())->code); + // now test1.example should have ns/AAAA + EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second-> + find(Name("ns.test1.example")).finder_-> + find(Name("ns.test1.example"), RRType::AAAA())->code); + + // test2.example shouldn't change + EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second-> + find(Name("ns.test2.example")).finder_-> + find(Name("ns.test2.example"), RRType::A())->code); + EXPECT_EQ(ZoneFinder::NXRRSET, + clients_map->find(rrclass)->second-> + find(Name("ns.test2.example")).finder_-> + find(Name("ns.test2.example"), RRType::AAAA())->code); +} + +void +DataSrcClientsBuilderTest::configureZones() { + ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in " + TEST_DATA_BUILDDIR "/test1.zone.copied")); + ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in " + TEST_DATA_BUILDDIR "/test2.zone.copied")); + + const ConstElementPtr config( + Element::fromJSON( + "{" + "\"IN\": [{" + " \"type\": \"MasterFiles\"," + " \"params\": {" + " \"test1.example\": \"" + + std::string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\"," + " \"test2.example\": \"" + + std::string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\"" + " }," + " \"cache-enable\": true" + "}]}")); + clients_map = configureDataSource(config); + zoneChecks(clients_map, rrclass); +} + +TEST_F(DataSrcClientsBuilderTest, loadZone) { + // pre test condition checks + EXPECT_EQ(0, map_mutex.lock_count); + EXPECT_EQ(0, map_mutex.unlock_count); + + configureZones(); + + EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR + "/test1-new.zone.in " + TEST_DATA_BUILDDIR "/test1.zone.copied")); + EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR + "/test2-new.zone.in " + TEST_DATA_BUILDDIR "/test2.zone.copied")); + + const Command loadzone_cmd(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"test1.example\"}")); + EXPECT_TRUE(builder.handleCommand(loadzone_cmd)); + EXPECT_EQ(1, map_mutex.lock_count); // we should have acquired the lock + EXPECT_EQ(1, map_mutex.unlock_count); // and released it. + + newZoneChecks(clients_map, rrclass); +} + +TEST_F(DataSrcClientsBuilderTest, +#ifdef USE_STATIC_LINK + DISABLED_loadZoneSQLite3 +#else + loadZoneSQLite3 +#endif + ) +{ + // Prepare the database first + const std::string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied"; + std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n"); + createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss); + // This describes the data source in the configuration + const ConstElementPtr config(Element::fromJSON("{" + "\"IN\": [{" + " \"type\": \"sqlite3\"," + " \"params\": {\"database_file\": \"" + test_db + "\"}," + " \"cache-enable\": true," + " \"cache-zones\": [\"example.org\"]" + "}]}")); + clients_map = configureDataSource(config); + + // Check that the A record at www.example.org does not exist + EXPECT_EQ(ZoneFinder::NXDOMAIN, + clients_map->find(rrclass)->second-> + find(Name("example.org")).finder_-> + find(Name("www.example.org"), RRType::A())->code); + + // Add the record to the underlying sqlite database, by loading + // it as a separate datasource, and updating it + ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\"," + "\"database_file\": \"" + + test_db + "\"}"); + DataSourceClientContainer sql_ds("sqlite3", sql_cfg); + ZoneUpdaterPtr sql_updater = + sql_ds.getInstance().getUpdater(Name("example.org"), false); + sql_updater->addRRset( + *textToRRset("www.example.org. 60 IN A 192.0.2.1")); + sql_updater->commit(); + + EXPECT_EQ(ZoneFinder::NXDOMAIN, + clients_map->find(rrclass)->second-> + find(Name("example.org")).finder_-> + find(Name("www.example.org"), RRType::A())->code); + + // Now send the command to reload it + const Command loadzone_cmd(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"example.org\"}")); + EXPECT_TRUE(builder.handleCommand(loadzone_cmd)); + // And now it should be present too. + EXPECT_EQ(ZoneFinder::SUCCESS, + clients_map->find(rrclass)->second-> + find(Name("example.org")).finder_-> + find(Name("www.example.org"), RRType::A())->code); + + // An error case: the zone has no configuration. (note .com here) + const Command nozone_cmd(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"example.com\"}")); + EXPECT_THROW(builder.handleCommand(nozone_cmd), + TestDataSrcClientsBuilder::InternalCommandError); + // The previous zone is not hurt in any way + EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second-> + find(Name("example.org")).finder_-> + find(Name("example.org"), RRType::SOA())->code); + + // attempt of reloading a zone but in-memory cache is disabled. + const ConstElementPtr config2(Element::fromJSON("{" + "\"IN\": [{" + " \"type\": \"sqlite3\"," + " \"params\": {\"database_file\": \"" + test_db + "\"}," + " \"cache-enable\": false," + " \"cache-zones\": [\"example.org\"]" + "}]}")); + clients_map = configureDataSource(config2); + EXPECT_THROW(builder.handleCommand( + Command(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"example.org\"}"))), + TestDataSrcClientsBuilder::InternalCommandError); + + // basically impossible case: in-memory cache is completely disabled. + // In this implementation of manager-builder, this should never happen, + // but it catches it like other configuration error and keeps going. + clients_map->clear(); + boost::shared_ptr nocache_list( + new ConfigurableClientList(rrclass)); + nocache_list->configure( + Element::fromJSON( + "[{\"type\": \"sqlite3\"," + " \"params\": {\"database_file\": \"" + test_db + "\"}," + " \"cache-enable\": true," + " \"cache-zones\": [\"example.org\"]" + "}]"), false); // false = disable cache + (*clients_map)[rrclass] = nocache_list; + EXPECT_THROW(builder.handleCommand( + Command(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"example.org\"}"))), + TestDataSrcClientsBuilder::InternalCommandError); +} + +TEST_F(DataSrcClientsBuilderTest, loadBrokenZone) { + configureZones(); + + ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR + "/test1-broken.zone.in " + TEST_DATA_BUILDDIR "/test1.zone.copied")); + // there's an error in the new zone file. reload will be rejected. + const Command loadzone_cmd(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"test1.example\"}")); + EXPECT_THROW(builder.handleCommand(loadzone_cmd), + TestDataSrcClientsBuilder::InternalCommandError); + zoneChecks(clients_map, rrclass); // zone shouldn't be replaced +} + +TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) { + configureZones(); + + // install the zone file as unreadable + ASSERT_EQ(0, std::system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR + "/test1.zone.in " + TEST_DATA_BUILDDIR "/test1.zone.copied")); + const Command loadzone_cmd(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"test1.example\"}")); + EXPECT_THROW(builder.handleCommand(loadzone_cmd), + TestDataSrcClientsBuilder::InternalCommandError); + zoneChecks(clients_map, rrclass); // zone shouldn't be replaced +} + +TEST_F(DataSrcClientsBuilderTest, loadZoneWithoutDataSrc) { + // try to execute load command without configuring the zone beforehand. + // it should fail. + EXPECT_THROW(builder.handleCommand( + Command(LOADZONE, + Element::fromJSON( + "{\"class\": \"IN\", " + " \"origin\": \"test1.example\"}"))), + TestDataSrcClientsBuilder::InternalCommandError); +} + +TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) { + configureZones(); + + if (!isc::util::unittests::runningOnValgrind()) { + // null arg: this causes assertion failure + EXPECT_DEATH_IF_SUPPORTED({ + builder.handleCommand(Command(LOADZONE, ElementPtr())); + }, ""); + } + + // zone class is bogus (note that this shouldn't happen except in tests) + EXPECT_THROW(builder.handleCommand( + Command(LOADZONE, + Element::fromJSON( + "{\"origin\": \"test1.example\"," + " \"class\": \"no_such_class\"}"))), + InvalidRRClass); + + // not a string + EXPECT_THROW(builder.handleCommand( + Command(LOADZONE, + Element::fromJSON( + "{\"origin\": \"test1.example\"," + " \"class\": 1}"))), + isc::data::TypeError); + + // class or origin is missing: result in assertion failure + if (!isc::util::unittests::runningOnValgrind()) { + EXPECT_DEATH_IF_SUPPORTED({ + builder.handleCommand(Command(LOADZONE, + Element::fromJSON( + "{\"class\": \"IN\"}"))); + }, ""); + } + + // zone doesn't exist in the data source + EXPECT_THROW( + builder.handleCommand( + Command(LOADZONE, + Element::fromJSON( + "{\"class\": \"IN\", \"origin\": \"xx\"}"))), + TestDataSrcClientsBuilder::InternalCommandError); + + // origin is bogus + EXPECT_THROW(builder.handleCommand( + Command(LOADZONE, + Element::fromJSON( + "{\"class\": \"IN\", \"origin\": \"...\"}"))), + EmptyLabel); + EXPECT_THROW(builder.handleCommand( + Command(LOADZONE, + Element::fromJSON( + "{\"origin\": 10, \"class\": 1}"))), + isc::data::TypeError); +} + +} // unnamed namespace diff --git a/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..c37ef115a92f1a3e6e27a2d00d41051feb8f7b36 --- /dev/null +++ b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc @@ -0,0 +1,254 @@ +// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include + +#include + +#include + +#include + +#include +#include "test_datasrc_clients_mgr.h" + +#include + +#include + +using namespace isc::dns; +using namespace isc::data; +using namespace isc::datasrc; +using namespace isc::auth; +using namespace isc::auth::datasrc_clientmgr_internal; + +namespace { +void +shutdownCheck() { + // Check for common points on shutdown. The manager should have acquired + // the lock, put a SHUTDOWN command to the queue, and should have signaled + // the builder. + EXPECT_EQ(1, FakeDataSrcClientsBuilder::queue_mutex->lock_count); + EXPECT_EQ(1, FakeDataSrcClientsBuilder::cond->signal_count); + EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size()); + const Command& cmd = FakeDataSrcClientsBuilder::command_queue->front(); + EXPECT_EQ(SHUTDOWN, cmd.first); + EXPECT_FALSE(cmd.second); // no argument + + // Finally, the manager should wait for the thread to terminate. + EXPECT_TRUE(FakeDataSrcClientsBuilder::thread_waited); +} + +// Commonly used pattern of checking member variables shared between the +// manager and builder. +void +checkSharedMembers(size_t expected_queue_lock_count, + size_t expected_queue_unlock_count, + size_t expected_map_lock_count, + size_t expected_map_unlock_count, + size_t expected_cond_signal_count, + size_t expected_command_queue_size) +{ + EXPECT_EQ(expected_queue_lock_count, + FakeDataSrcClientsBuilder::queue_mutex->lock_count); + EXPECT_EQ(expected_queue_unlock_count, + FakeDataSrcClientsBuilder::queue_mutex->unlock_count); + EXPECT_EQ(expected_map_lock_count, + FakeDataSrcClientsBuilder::map_mutex->lock_count); + EXPECT_EQ(expected_map_unlock_count, + FakeDataSrcClientsBuilder::map_mutex->unlock_count); + EXPECT_EQ(expected_cond_signal_count, + FakeDataSrcClientsBuilder::cond->signal_count); + EXPECT_EQ(expected_command_queue_size, + FakeDataSrcClientsBuilder::command_queue->size()); +} + +TEST(DataSrcClientsMgrTest, start) { + // When we create a manager, builder's run() method should be called. + FakeDataSrcClientsBuilder::started = false; + { + TestDataSrcClientsMgr mgr; + EXPECT_TRUE(FakeDataSrcClientsBuilder::started); + EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty()); + + // Check pre-destroy conditions + EXPECT_EQ(0, FakeDataSrcClientsBuilder::cond->signal_count); + EXPECT_FALSE(FakeDataSrcClientsBuilder::thread_waited); + } // mgr and builder have been destroyed by this point. + + // We stopped the manager implicitly (without shutdown()). The manager + // will internally notify it + shutdownCheck(); +} + +TEST(DataSrcClientsMgrTest, shutdownWithUncaughtException) { + // Emulating the case when the builder exists on exception. shutdown() + // will encounter UncaughtException exception and catch it. + EXPECT_NO_THROW({ + TestDataSrcClientsMgr mgr; + FakeDataSrcClientsBuilder::thread_throw_on_wait = + FakeDataSrcClientsBuilder::THROW_UNCAUGHT_EX; + }); +} + +TEST(DataSrcClientsMgrTest, shutdownWithException) { + EXPECT_NO_THROW({ + TestDataSrcClientsMgr mgr; + FakeDataSrcClientsBuilder::thread_throw_on_wait = + FakeDataSrcClientsBuilder::THROW_OTHER; + }); +} + +TEST(DataSrcClientsMgrTest, reconfigure) { + TestDataSrcClientsMgr mgr; + + // Check pre-command condition + checkSharedMembers(0, 0, 0, 0, 0, 0); + + // A valid reconfigure argument + ConstElementPtr reconfigure_arg = Element::fromJSON( + "{""\"IN\": [{\"type\": \"MasterFiles\", \"params\": {}," + " \"cache-enable\": true}]}"); + + // On reconfigure(), it just send the RECONFIGURE command to the builder + // thread with the given argument intact. + mgr.reconfigure(reconfigure_arg); + + // The manager should have acquired the queue lock, send RECONFIGURE + // command with the arg, wake up the builder thread by signal. It doesn't + // touch or refer to the map, so it shouldn't acquire the map lock. + checkSharedMembers(1, 1, 0, 0, 1, 1); + const Command& cmd1 = FakeDataSrcClientsBuilder::command_queue->front(); + EXPECT_EQ(RECONFIGURE, cmd1.first); + EXPECT_EQ(reconfigure_arg, cmd1.second); + + // Non-null, but semantically invalid argument. The manager doesn't do + // this check, so it should result in the same effect. + FakeDataSrcClientsBuilder::command_queue->clear(); + reconfigure_arg = isc::data::Element::create("{ \"foo\": \"bar\" }"); + mgr.reconfigure(reconfigure_arg); + checkSharedMembers(2, 2, 0, 0, 2, 1); + const Command& cmd2 = FakeDataSrcClientsBuilder::command_queue->front(); + EXPECT_EQ(RECONFIGURE, cmd2.first); + EXPECT_EQ(reconfigure_arg, cmd2.second); + + // Passing NULL argument is immediately rejected + EXPECT_THROW(mgr.reconfigure(ConstElementPtr()), isc::InvalidParameter); + checkSharedMembers(2, 2, 0, 0, 2, 1); // no state change +} + +TEST(DataSrcClientsMgrTest, holder) { + TestDataSrcClientsMgr mgr; + + { + // Initially it's empty, so findClientList() will always return NULL + TestDataSrcClientsMgr::Holder holder(mgr); + EXPECT_FALSE(holder.findClientList(RRClass::IN())); + EXPECT_FALSE(holder.findClientList(RRClass::CH())); + // map should be protected here + EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->lock_count); + EXPECT_EQ(0, FakeDataSrcClientsBuilder::map_mutex->unlock_count); + } + // map lock has been released + EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->unlock_count); + + // Put something in, that should become visible. + ConstElementPtr reconfigure_arg = Element::fromJSON( + "{\"IN\": [{\"type\": \"MasterFiles\", \"params\": {}," + " \"cache-enable\": true}]," + " \"CH\": [{\"type\": \"MasterFiles\", \"params\": {}," + " \"cache-enable\": true}]}"); + mgr.reconfigure(reconfigure_arg); + { + TestDataSrcClientsMgr::Holder holder(mgr); + EXPECT_TRUE(holder.findClientList(RRClass::IN())); + EXPECT_TRUE(holder.findClientList(RRClass::CH())); + } + // We need to clear command queue by hand + FakeDataSrcClientsBuilder::command_queue->clear(); + + // Replace the lists with new lists containing only one list. + // The CH will disappear again. + reconfigure_arg = Element::fromJSON( + "{\"IN\": [{\"type\": \"MasterFiles\", \"params\": {}," + " \"cache-enable\": true}]}"); + mgr.reconfigure(reconfigure_arg); + { + TestDataSrcClientsMgr::Holder holder(mgr); + EXPECT_TRUE(holder.findClientList(RRClass::IN())); + EXPECT_FALSE(holder.findClientList(RRClass::CH())); + } + + // Duplicate lock acquisition is prohibited (only test mgr can detect + // this reliably, so this test may not be that useful) + TestDataSrcClientsMgr::Holder holder1(mgr); + EXPECT_THROW(TestDataSrcClientsMgr::Holder holder2(mgr), isc::Unexpected); +} + +TEST(DataSrcClientsMgrTest, reload) { + TestDataSrcClientsMgr mgr; + EXPECT_TRUE(FakeDataSrcClientsBuilder::started); + EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty()); + + isc::data::ElementPtr args = + isc::data::Element::fromJSON("{ \"class\": \"IN\"," + " \"origin\": \"example.com\" }"); + mgr.loadZone(args); + EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size()); + mgr.loadZone(args); + EXPECT_EQ(2, FakeDataSrcClientsBuilder::command_queue->size()); + + // Should fail with non-string 'class' value + args->set("class", Element::create(1)); + EXPECT_THROW(mgr.loadZone(args), CommandError); + EXPECT_EQ(2, FakeDataSrcClientsBuilder::command_queue->size()); + + // And with badclass + args->set("class", Element::create("BADCLASS")); + EXPECT_THROW(mgr.loadZone(args), CommandError); + EXPECT_EQ(2, FakeDataSrcClientsBuilder::command_queue->size()); + + // Should succeed without 'class' + args->remove("class"); + mgr.loadZone(args); + EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size()); + + // but fail without origin, without sending new commands + args->remove("origin"); + EXPECT_THROW(mgr.loadZone(args), CommandError); + EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size()); + + // And for 'origin' that is not a string + args->set("origin", Element::create(1)); + EXPECT_THROW(mgr.loadZone(args), CommandError); + EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size()); + + // And origin that is not a correct name + args->set("origin", Element::create("..")); + EXPECT_THROW(mgr.loadZone(args), CommandError); + EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size()); + + // same for empty data and data that is not a map + EXPECT_THROW(mgr.loadZone(isc::data::ConstElementPtr()), CommandError); + EXPECT_THROW(mgr.loadZone(isc::data::Element::createList()), CommandError); + EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size()); +} + +TEST(DataSrcClientsMgrTest, realThread) { + // Using the non-test definition with a real thread. Just checking + // no disruption happens. + DataSrcClientsMgr mgr; +} + +} // unnamed namespace diff --git a/src/bin/auth/tests/datasrc_config_unittest.cc b/src/bin/auth/tests/datasrc_config_unittest.cc index 877f92166f67adc385d369d342e665ac26cfbb8e..b555aa69ad970df8bc31ad339a5f0f09859e35ad 100644 --- a/src/bin/auth/tests/datasrc_config_unittest.cc +++ b/src/bin/auth/tests/datasrc_config_unittest.cc @@ -16,7 +16,6 @@ #include #include -#include #include @@ -78,12 +77,8 @@ datasrcConfigHandler(DatasrcConfigTest* fake_server, const std::string&, class DatasrcConfigTest : public ::testing::Test { public: - // These pretend to be the server - isc::util::thread::Mutex& getDataSrcClientListMutex() const { - return (mutex_); - } - void swapDataSrcClientLists(shared_ptr > - new_lists) + void setDataSrcClientLists(shared_ptr > + new_lists) { lists_.clear(); // first empty it @@ -156,7 +151,6 @@ protected: const string specfile; std::map lists_; string log_; - mutable isc::util::thread::Mutex mutex_; }; void @@ -167,7 +161,7 @@ testConfigureDataSource(DatasrcConfigTest& test, // possible to easily look that they were called. shared_ptr > lists = configureDataSourceGeneric(config); - test.swapDataSrcClientLists(lists); + test.setDataSrcClientLists(lists); } // Push there a configuration with a single list. diff --git a/src/bin/auth/tests/datasrc_util.h b/src/bin/auth/tests/datasrc_util.h index 07ebc0c25e6da0fabdceca687162124aee957bdc..974833266b65678144946e050aeccab849e9df3b 100644 --- a/src/bin/auth/tests/datasrc_util.h +++ b/src/bin/auth/tests/datasrc_util.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __AUTH_DATA_SOURCE_UTIL_H -#define __AUTH_DATA_SOURCE_UTIL_H 1 +#ifndef AUTH_DATA_SOURCE_UTIL_H +#define AUTH_DATA_SOURCE_UTIL_H 1 #include #include @@ -51,7 +51,7 @@ createSQLite3DB(dns::RRClass zclass, const dns::Name& zname, } // end of auth } // end of isc -#endif // __AUTH_DATA_SOURCE_UTIL_H +#endif // AUTH_DATA_SOURCE_UTIL_H // Local Variables: // mode: c++ diff --git a/src/bin/auth/tests/statistics_unittest.cc b/src/bin/auth/tests/statistics_unittest.cc index d2f5a5ab95381cb0db0b448e784a215ac55c6a20..052a70e83eaed111f36602877f5cddb05886c740 100644 --- a/src/bin/auth/tests/statistics_unittest.cc +++ b/src/bin/auth/tests/statistics_unittest.cc @@ -27,345 +27,97 @@ #include #include +#include #include +#include +#include +#include +#include +#include + using namespace std; using namespace isc::cc; using namespace isc::dns; using namespace isc::data; +using isc::auth::statistics::Counters; +using isc::auth::statistics::QRAttributes; namespace { -class AuthCountersTest : public ::testing::Test { -private: - class MockSession : public AbstractSession { - public: - MockSession() : - // by default we return a simple "success" message. - msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")), - throw_session_error_(false), throw_session_timeout_(false) - {} - virtual void establish(const char* socket_file); - virtual void disconnect(); - virtual int group_sendmsg(ConstElementPtr msg, string group, - string instance, string to); - virtual bool group_recvmsg(ConstElementPtr& envelope, - ConstElementPtr& msg, - bool nonblock, int seq); - virtual void subscribe(string group, string instance); - virtual void unsubscribe(string group, string instance); - virtual void startRead(boost::function read_callback); - virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg); - virtual bool hasQueuedMsgs() const; - virtual void setTimeout(size_t) {} - virtual size_t getTimeout() const { return (0); }; - - void setThrowSessionError(bool flag); - void setThrowSessionTimeout(bool flag); - - void setMessage(ConstElementPtr msg) { msg_ = msg; } - - ConstElementPtr sent_msg; - string msg_destination; - private: - ConstElementPtr msg_; - bool throw_session_error_; - bool throw_session_timeout_; - }; - +class CountersTest : public ::testing::Test { protected: - AuthCountersTest() : counters() { - } - ~AuthCountersTest() { - } - AuthCounters counters; - // no need to be inherited from the original class here. - class MockModuleSpec { - public: - bool validateStatistics(ConstElementPtr, const bool valid) const - { return (valid); } - }; - MockModuleSpec module_spec_; + CountersTest() : counters() {} + ~CountersTest() {} + Counters counters; }; +// Test if the values of the counters are all zero except for the items +// specified in except_for. void -AuthCountersTest::MockSession::establish(const char*) {} - -void -AuthCountersTest::MockSession::disconnect() {} - -void -AuthCountersTest::MockSession::subscribe(string, string) -{} - -void -AuthCountersTest::MockSession::unsubscribe(string, string) -{} - -void -AuthCountersTest::MockSession::startRead(boost::function) -{} - -int -AuthCountersTest::MockSession::reply(ConstElementPtr, ConstElementPtr) { - return (-1); -} - -bool -AuthCountersTest::MockSession::hasQueuedMsgs() const { - return (false); -} - -int -AuthCountersTest::MockSession::group_sendmsg(ConstElementPtr msg, - string group, string, string) -{ - if (throw_session_error_) { - isc_throw(SessionError, "Session Error"); - } - sent_msg = msg; - msg_destination = group; - return (0); -} - -bool -AuthCountersTest::MockSession::group_recvmsg(ConstElementPtr&, - ConstElementPtr& msg, bool, int) -{ - if (throw_session_timeout_) { - isc_throw(SessionTimeout, "Session Timeout"); - } - msg = msg_; - return (true); -} - -void -AuthCountersTest::MockSession::setThrowSessionError(bool flag) { - throw_session_error_ = flag; -} - -void -AuthCountersTest::MockSession::setThrowSessionTimeout(bool flag) { - throw_session_timeout_ = flag; -} - -TEST_F(AuthCountersTest, incrementUDPCounter) { - // The counter should be initialized to 0. - EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_UDP_QUERY)); - EXPECT_NO_THROW(counters.inc(AuthCounters::SERVER_UDP_QUERY)); - // After increment, the counter should be 1. - EXPECT_EQ(1, counters.getCounter(AuthCounters::SERVER_UDP_QUERY)); -} - -TEST_F(AuthCountersTest, incrementTCPCounter) { - // The counter should be initialized to 0. - EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_TCP_QUERY)); - EXPECT_NO_THROW(counters.inc(AuthCounters::SERVER_TCP_QUERY)); - // After increment, the counter should be 1. - EXPECT_EQ(1, counters.getCounter(AuthCounters::SERVER_TCP_QUERY)); -} - -TEST_F(AuthCountersTest, incrementInvalidCounter) { - // Expect to throw an isc::OutOfRange - EXPECT_THROW(counters.inc(AuthCounters::SERVER_COUNTER_TYPES), - isc::OutOfRange); -} - -TEST_F(AuthCountersTest, incrementOpcodeCounter) { - // The counter should be initialized to 0. If we increment it by 1 - // the counter should be 1. - for (int i = 0; i < 16; ++i) { - EXPECT_EQ(0, counters.getCounter(Opcode(i))); - counters.inc(Opcode(i)); - EXPECT_EQ(1, counters.getCounter(Opcode(i))); - } -} - -TEST_F(AuthCountersTest, incrementRcodeCounter) { - // The counter should be initialized to 0. If we increment it by 1 - // the counter should be 1. - for (int i = 0; i < 17; ++i) { - EXPECT_EQ(0, counters.getCounter(Rcode(i))); - counters.inc(Rcode(i)); - EXPECT_EQ(1, counters.getCounter(Rcode(i))); - } -} - -void -opcodeDataCheck(ConstElementPtr data, const int expected[16]) { - const char* item_names[] = { - "query", "iquery", "status", "reserved3", "notify", "update", - "reserved6", "reserved7", "reserved8", "reserved9", "reserved10", - "reserved11", "reserved12", "reserved13", "reserved14", "reserved15", - NULL - }; - int i; - for (i = 0; i < 16; ++i) { - ASSERT_NE(static_cast(NULL), item_names[i]); - const string item_name = "opcode." + string(item_names[i]); - if (expected[i] == 0) { - EXPECT_FALSE(data->get(item_name)); - } else { - EXPECT_EQ(expected[i], data->get(item_name)->intValue()); - } - } - // We should have examined all names - ASSERT_EQ(static_cast(NULL), item_names[i]); -} - -void -rcodeDataCheck(ConstElementPtr data, const int expected[17]) { - const char* item_names[] = { - "noerror", "formerr", "servfail", "nxdomain", "notimp", "refused", - "yxdomain", "yxrrset", "nxrrset", "notauth", "notzone", "reserved11", - "reserved12", "reserved13", "reserved14", "reserved15", "badvers", - NULL - }; - int i; - for (i = 0; i < 17; ++i) { - ASSERT_NE(static_cast(NULL), item_names[i]); - const string item_name = "rcode." + string(item_names[i]); - if (expected[i] == 0) { - EXPECT_FALSE(data->get(item_name)); - } else { - EXPECT_EQ(expected[i], data->get(item_name)->intValue()); +checkCountersAllZeroExcept(const isc::data::ConstElementPtr counters, + const std::set& except_for) { + std::map stats_map = counters->mapValue(); + + for (std::map::const_iterator + i = stats_map.begin(), e = stats_map.end(); + i != e; + ++i) + { + int expect = 0; + if (except_for.count(i->first) != 0) { + expect = 1; } + EXPECT_EQ(expect, i->second->intValue()) << "Expected counter " + << i->first << " = " << expect << ", actual: " + << i->second->intValue(); } - // We should have examined all names - ASSERT_EQ(static_cast(NULL), item_names[i]); } -TEST_F(AuthCountersTest, getStatisticsWithoutValidator) { - // Get statistics data. - // Validate if it answers correct data. +TEST_F(CountersTest, incrementNormalQuery) { + Message response(Message::RENDER); + QRAttributes qrattrs; + std::set expect_nonzero; - // Counters should be initialized to 0. - EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_UDP_QUERY)); - EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_TCP_QUERY)); + expect_nonzero.clear(); + checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero); - // UDP query counter is set to 2. - counters.inc(AuthCounters::SERVER_UDP_QUERY); - counters.inc(AuthCounters::SERVER_UDP_QUERY); - // TCP query counter is set to 1. - counters.inc(AuthCounters::SERVER_TCP_QUERY); - ConstElementPtr statistics_data = counters.getStatistics(); + qrattrs.setQueryIPVersion(AF_INET6); + qrattrs.setQueryTransportProtocol(IPPROTO_UDP); + qrattrs.setQueryOpCode(Opcode::QUERY_CODE); + qrattrs.setQueryEDNS(true, false); + qrattrs.setQueryDO(true); + qrattrs.answerWasSent(); - // UDP query counter is 2 and TCP query counter is 1. - EXPECT_EQ(2, statistics_data->get("queries.udp")->intValue()); - EXPECT_EQ(1, statistics_data->get("queries.tcp")->intValue()); + response.setRcode(Rcode::REFUSED()); + response.addQuestion(Question(Name("example.com"), + RRClass::IN(), RRType::AAAA())); - // By default opcode counters are all 0 and omitted - const int opcode_results[16] = { 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 }; - opcodeDataCheck(statistics_data, opcode_results); - // By default rcode counters are all 0 and omitted - const int rcode_results[17] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 }; - rcodeDataCheck(statistics_data, rcode_results); -} + counters.inc(qrattrs, response); -void -updateOpcodeCounters(AuthCounters &counters, const int expected[16]) { - for (int i = 0; i < 16; ++i) { - for (int j = 0; j < expected[i]; ++j) { - counters.inc(Opcode(i)); - } - } + expect_nonzero.clear(); + expect_nonzero.insert("opcode.query"); + expect_nonzero.insert("queries.udp"); + expect_nonzero.insert("rcode.refused"); + checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero); } -void -updateRcodeCounters(AuthCounters &counters, const int expected[17]) { - for (int i = 0; i < 17; ++i) { - for (int j = 0; j < expected[i]; ++j) { - counters.inc(Rcode(i)); +int +countTreeElements(const struct CounterTypeTree* tree) { + int count = 0; + for (int i = 0; tree[i].name != NULL; ++i) { + if (tree[i].sub_tree == NULL) { + ++count; + } else { + count += countTreeElements(tree[i].sub_tree); } } + return count; } -TEST_F(AuthCountersTest, getStatisticsWithOpcodeCounters) { - // Increment some of the opcode counters. Then they should appear in the - // submitted data; others shouldn't - const int opcode_results[16] = { 1, 2, 3, 0, 4, 5, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 }; - updateOpcodeCounters(counters, opcode_results); - ConstElementPtr statistics_data = counters.getStatistics(); - opcodeDataCheck(statistics_data, opcode_results); -} - -TEST_F(AuthCountersTest, getStatisticsWithAllOpcodeCounters) { - // Increment all opcode counters. Then they should appear in the - // submitted data. - const int opcode_results[16] = { 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1 }; - updateOpcodeCounters(counters, opcode_results); - ConstElementPtr statistics_data = counters.getStatistics(); - opcodeDataCheck(statistics_data, opcode_results); -} - -TEST_F(AuthCountersTest, getStatisticsWithRcodeCounters) { - // Increment some of the rcode counters. Then they should appear in the - // submitted data; others shouldn't - const int rcode_results[17] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 0, 0, 0, 0, 0, 0, 11 }; - updateRcodeCounters(counters, rcode_results); - ConstElementPtr statistics_data = counters.getStatistics(); - rcodeDataCheck(statistics_data, rcode_results); +TEST(StatisticsItemsTest, QRItemNamesCheck) { + EXPECT_EQ(QR_COUNTER_TYPES, countTreeElements(QRCounterTree)); } -TEST_F(AuthCountersTest, getStatisticsWithAllRcodeCounters) { - // Increment all rcode counters. Then they should appear in the - // submitted data. - const int rcode_results[17] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1 }; - updateOpcodeCounters(counters, rcode_results); - ConstElementPtr statistics_data = counters.getStatistics(); - opcodeDataCheck(statistics_data, rcode_results); -} - -TEST_F(AuthCountersTest, getStatisticsWithValidator) { - - //a validator for the unittest - AuthCounters::validator_type validator; - ConstElementPtr el; - - // Get statistics data with correct statistics validator. - validator = boost::bind( - &AuthCountersTest::MockModuleSpec::validateStatistics, - &module_spec_, _1, true); - - EXPECT_TRUE(validator(el)); - - // register validator to AuthCounters - counters.registerStatisticsValidator(validator); - - // Counters should be initialized to 0. - EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_UDP_QUERY)); - EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_TCP_QUERY)); - - // UDP query counter is set to 2. - counters.inc(AuthCounters::SERVER_UDP_QUERY); - counters.inc(AuthCounters::SERVER_UDP_QUERY); - // TCP query counter is set to 1. - counters.inc(AuthCounters::SERVER_TCP_QUERY); - - // checks the value returned by getStatistics - ConstElementPtr statistics_data = counters.getStatistics(); - - // UDP query counter is 2 and TCP query counter is 1. - EXPECT_EQ(2, statistics_data->get("queries.udp")->intValue()); - EXPECT_EQ(1, statistics_data->get("queries.tcp")->intValue()); - - // Get statistics data with incorrect statistics validator. - validator = boost::bind( - &AuthCountersTest::MockModuleSpec::validateStatistics, - &module_spec_, _1, false); - - EXPECT_FALSE(validator(el)); - - counters.registerStatisticsValidator(validator); - - // checks the value returned by getStatistics - EXPECT_FALSE(counters.getStatistics()); -} } diff --git a/src/bin/auth/tests/test_datasrc_clients_mgr.cc b/src/bin/auth/tests/test_datasrc_clients_mgr.cc new file mode 100644 index 0000000000000000000000000000000000000000..82937c0c5e8f23594a86677de2f03b51083997b5 --- /dev/null +++ b/src/bin/auth/tests/test_datasrc_clients_mgr.cc @@ -0,0 +1,95 @@ +// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include + +#include "test_datasrc_clients_mgr.h" + +#include + +namespace isc { +namespace auth { +namespace datasrc_clientmgr_internal { + +// Define static DataSrcClientsBuilder member variables. +bool FakeDataSrcClientsBuilder::started = false; +std::list* FakeDataSrcClientsBuilder::command_queue = NULL; +std::list FakeDataSrcClientsBuilder::command_queue_copy; +TestCondVar* FakeDataSrcClientsBuilder::cond = NULL; +TestCondVar FakeDataSrcClientsBuilder::cond_copy; +TestMutex* FakeDataSrcClientsBuilder::queue_mutex = NULL; +isc::datasrc::ClientListMapPtr* + FakeDataSrcClientsBuilder::clients_map = NULL; +TestMutex* FakeDataSrcClientsBuilder::map_mutex = NULL; +TestMutex FakeDataSrcClientsBuilder::queue_mutex_copy; +bool FakeDataSrcClientsBuilder::thread_waited = false; +FakeDataSrcClientsBuilder::ExceptionFromWait +FakeDataSrcClientsBuilder::thread_throw_on_wait = + FakeDataSrcClientsBuilder::NOTHROW; + +template<> +void +TestDataSrcClientsBuilder::doNoop() { + ++queue_mutex_->noop_count; + switch (queue_mutex_->throw_from_noop) { + case TestMutex::NONE: + break; // no throw + case TestMutex::EXCLASS: + isc_throw(Exception, "test exception"); + case TestMutex::INTEGER: + throw 42; + case TestMutex::INTERNAL: + isc_throw(InternalCommandError, "internal error, should be ignored"); + } +} +} // namespace datasrc_clientmgr_internal + +template<> +void +TestDataSrcClientsMgr::cleanup() { + using namespace datasrc_clientmgr_internal; + // Make copy of some of the manager's member variables and reset the + // corresponding pointers. The currently pointed objects are in the + // manager object, which are going to be invalidated. + + FakeDataSrcClientsBuilder::command_queue_copy = command_queue_; + FakeDataSrcClientsBuilder::command_queue = + &FakeDataSrcClientsBuilder::command_queue_copy; + FakeDataSrcClientsBuilder::queue_mutex_copy = queue_mutex_; + FakeDataSrcClientsBuilder::queue_mutex = + &FakeDataSrcClientsBuilder::queue_mutex_copy; + FakeDataSrcClientsBuilder::cond_copy = cond_; + FakeDataSrcClientsBuilder::cond = + &FakeDataSrcClientsBuilder::cond_copy; +} + +template<> +void +TestDataSrcClientsMgr::reconfigureHook() { + using namespace datasrc_clientmgr_internal; + + // Simply replace the local map, ignoring bogus config value. + assert(command_queue_.front().first == RECONFIGURE); + try { + clients_map_ = configureDataSource(command_queue_.front().second); + } catch (...) {} +} + +} // namespace auth +} // namespace isc + +// Local Variables: +// mode: c++ +// End: diff --git a/src/bin/auth/tests/test_datasrc_clients_mgr.h b/src/bin/auth/tests/test_datasrc_clients_mgr.h new file mode 100644 index 0000000000000000000000000000000000000000..9b1a3672cdc424b902420c957583a599944e6500 --- /dev/null +++ b/src/bin/auth/tests/test_datasrc_clients_mgr.h @@ -0,0 +1,223 @@ +// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef TEST_DATASRC_CLIENTS_MGR_H +#define TEST_DATASRC_CLIENTS_MGR_H 1 + +#include + +#include +#include + +#include + +#include + +// In this file we provide specialization of thread, mutex, condition variable, +// and DataSrcClientsBuilder for convenience of tests. They don't use +// actual threads or mutex, and allow tests to inspect some internal states +// of the corresponding objects. +// +// In many cases, tests can use TestDataSrcClientsMgr (defined below) where +// DataSrcClientsMgr is needed. + +// Below we extend the isc::auth::datasrc_clientmgr_internal namespace to +// specialize the doNoop() method. +namespace isc { +namespace auth { +namespace datasrc_clientmgr_internal { +class TestMutex { +public: + // for throw_from_noop. + // None: no throw from specialized doNoop() + // EXCLASS: throw some exception class object + // INTEGER: throw an integer + // INTERNAL: internal error (shouldn't terminate the thread) + enum ExceptionFromNoop { NONE, EXCLASS, INTEGER, INTERNAL }; + + TestMutex() : lock_count(0), unlock_count(0), noop_count(0), + throw_from_noop(NONE) + {} + class Locker { + public: + Locker(TestMutex& mutex) : mutex_(mutex) { + if (mutex.lock_count != mutex.unlock_count) { + isc_throw(Unexpected, + "attempt of duplicate lock acquisition"); + } + + ++mutex.lock_count; + if (mutex.lock_count > 100) { // 100 is an arbitrary choice + isc_throw(Unexpected, + "too many test mutex count, likely a bug in test"); + } + } + ~Locker() { + ++mutex_.unlock_count; + } + private: + TestMutex& mutex_; + }; + size_t lock_count; // number of lock acquisitions; tests can check this + size_t unlock_count; // number of lock releases; tests can check this + size_t noop_count; // allow doNoop() to modify this + ExceptionFromNoop throw_from_noop; // tests can set this to control doNoop +}; + +class TestCondVar { +public: + TestCondVar() : wait_count(0), signal_count(0), command_queue_(NULL), + delayed_command_queue_(NULL) + {} + TestCondVar(std::list& command_queue, + std::list& delayed_command_queue) : + wait_count(0), + signal_count(0), + command_queue_(&command_queue), + delayed_command_queue_(&delayed_command_queue) + { + } + void wait(TestMutex& mutex) { + // bookkeeping + ++mutex.unlock_count; + ++wait_count; + ++mutex.lock_count; + + if (wait_count > 100) { // 100 is an arbitrary choice + isc_throw(Unexpected, + "too many cond wait count, likely a bug in test"); + } + + // make the delayed commands available + command_queue_->splice(command_queue_->end(), *delayed_command_queue_); + } + void signal() { + ++signal_count; + } + size_t wait_count; // number of calls to wait(); tests can check this + size_t signal_count; // number of calls to signal(); tests can check this +private: + std::list* command_queue_; + std::list* delayed_command_queue_; +}; + +// Convenient shortcut +typedef DataSrcClientsBuilderBase +TestDataSrcClientsBuilder; + +// We specialize this command handler for the convenience of tests. +// It abuses our specialized Mutex to count the number of calls of this method. +template<> +void +TestDataSrcClientsBuilder::doNoop(); + +// A specialization of DataSrcClientsBuilder that allows tests to inspect +// its internal states via static class variables. Using static is suboptimal, +// but DataSrcClientsMgr is highly encapsulated, this seems to be the best +// possible compromise. +class FakeDataSrcClientsBuilder { +public: + // true iff a builder has started. + static bool started; + + // These three correspond to the resource shared with the manager. + // xxx_copy will be set in the manager's destructor to record the + // final state of the manager. + static std::list* command_queue; + static TestCondVar* cond; + static TestMutex* queue_mutex; + static isc::datasrc::ClientListMapPtr* clients_map; + static TestMutex* map_mutex; + static std::list command_queue_copy; + static TestCondVar cond_copy; + static TestMutex queue_mutex_copy; + + // true iff the manager waited on the thread running the builder. + static bool thread_waited; + + // If set to true by a test, TestThread::wait() throws an exception + // exception. + enum ExceptionFromWait { NOTHROW, THROW_UNCAUGHT_EX, THROW_OTHER }; + static ExceptionFromWait thread_throw_on_wait; + + FakeDataSrcClientsBuilder( + std::list* command_queue, + TestCondVar* cond, + TestMutex* queue_mutex, + isc::datasrc::ClientListMapPtr* clients_map, + TestMutex* map_mutex) + { + FakeDataSrcClientsBuilder::started = false; + FakeDataSrcClientsBuilder::command_queue = command_queue; + FakeDataSrcClientsBuilder::cond = cond; + FakeDataSrcClientsBuilder::queue_mutex = queue_mutex; + FakeDataSrcClientsBuilder::clients_map = clients_map; + FakeDataSrcClientsBuilder::map_mutex = map_mutex; + FakeDataSrcClientsBuilder::thread_waited = false; + FakeDataSrcClientsBuilder::thread_throw_on_wait = NOTHROW; + } + void run() { + FakeDataSrcClientsBuilder::started = true; + } +}; + +// A fake thread class that doesn't really invoke thread but simply calls +// the given main function (synchronously). Tests can tweak the wait() +// behavior via some static variables so it will throw some exceptions. +class TestThread { +public: + TestThread(const boost::function& main) { + main(); + } + void wait() { + FakeDataSrcClientsBuilder::thread_waited = true; + switch (FakeDataSrcClientsBuilder::thread_throw_on_wait) { + case FakeDataSrcClientsBuilder::NOTHROW: + break; + case FakeDataSrcClientsBuilder::THROW_UNCAUGHT_EX: + isc_throw(util::thread::Thread::UncaughtException, + "TestThread wait() saw an exception"); + case FakeDataSrcClientsBuilder::THROW_OTHER: + isc_throw(Unexpected, + "General emulated failure in TestThread wait()"); + } + } +}; +} // namespace datasrc_clientmgr_internal + +// Convenient shortcut +typedef DataSrcClientsMgrBase< + datasrc_clientmgr_internal::TestThread, + datasrc_clientmgr_internal::FakeDataSrcClientsBuilder, + datasrc_clientmgr_internal::TestMutex, + datasrc_clientmgr_internal::TestCondVar> TestDataSrcClientsMgr; + +// A specialization of manager's "cleanup" called at the end of the +// destructor. We use this to record the final values of some of the class +// member variables. +template<> +void +TestDataSrcClientsMgr::cleanup(); + +template<> +void +TestDataSrcClientsMgr::reconfigureHook(); +} // namespace auth +} // namespace isc + +#endif // TEST_DATASRC_CLIENTS_MGR_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes index 2f48325498089182ac1208afdc111404362bd290..aba8da5ce0d5319549e9a997d133c933551bd073 100644 --- a/src/bin/bind10/bind10_messages.mes +++ b/src/bin/bind10/bind10_messages.mes @@ -82,6 +82,21 @@ the boss process will try to force them). A debug message. The configurator is about to perform one task of the plan it is currently executing on the named component. +% BIND10_CONNECTING_TO_CC_FAIL failed to connect to configuration/command channel; try -v to see output from msgq +The boss process tried to connect to the communication channel for +commands and configuration updates during initialization, but it +failed. This is a fatal startup error, and process will soon +terminate after some cleanup. There can be several reasons for the +failure, but the most likely cause is that the msgq daemon failed to +start, and the most likely cause of the msgq failure is that it +doesn't have a permission to create a socket file for the +communication. To confirm that, you can see debug messages from msgq +by starting BIND 10 with the -v command line option. If it indicates +permission problem for msgq, make sure the directory where the socket +file is to be created is writable for the msgq process. Note that if +you specify the -u option to change process users, the directory must +be writable for that user. + % BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified An error was encountered when the boss module specified statistics data which is invalid for the boss specification file. @@ -94,11 +109,6 @@ and continue running as the specified user, but the user is unknown. The boss module was not able to start every process it needed to start during startup, and will now kill the processes that did get started. -% BIND10_KILL_PROCESS killing process %1 -The boss module is sending a kill signal to process with the given name, -as part of the process of killing all started processes during a failed -startup, as described for BIND10_KILLING_ALL_PROCESSES - % BIND10_LOST_SOCKET_CONSUMER consumer %1 of sockets disconnected, considering all its sockets closed A connection from one of the applications which requested a socket was closed. This means the application has terminated, so all the sockets it was @@ -167,6 +177,30 @@ so BIND 10 will now shut down. The specific error is printed. % BIND10_SEND_SIGKILL sending SIGKILL to %1 (PID %2) The boss module is sending a SIGKILL signal to the given process. +% BIND10_SEND_SIGNAL_FAIL sending %1 to %2 (PID %3) failed: %4 +The boss module sent a single (either SIGTERM or SIGKILL) to a process, +but it failed due to some system level error. There are two major cases: +the target process has already terminated but the boss module had sent +the signal before it noticed the termination. In this case an error +message should indicate something like "no such process". This can be +safely ignored. The other case is that the boss module doesn't have +the privilege to send a signal to the process. It can typically +happen when the boss module started as a privileged process, spawned a +subprocess, and then dropped the privilege. It includes the case for +the socket creator when the boss process runs with the -u command line +option. In this case, the boss module simply gives up to terminate +the process explicitly because it's unlikely to succeed by keeping +sending the signal. Although the socket creator is implemented so +that it will terminate automatically when the boss process exits +(and that should be the case for any other future process running with +a higher privilege), but it's recommended to check if there's any +remaining BIND 10 process if this message is logged. For all other +cases, the boss module will keep sending the signal until it confirms +all child processes terminate. Although unlikely, this could prevent +the boss module from exiting, just keeping sending the signals. So, +again, it's advisable to check if it really terminates when this +message is logged. + % BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2) The boss module is sending a SIGTERM signal to the given process. @@ -274,13 +308,6 @@ During the startup process, a number of messages are exchanged between the Boss process and the processes it starts. This error is output when a message received by the Boss process is not recognised. -% BIND10_START_AS_NON_ROOT_AUTH starting b10-auth as a user, not root. This might fail. -The authoritative server is being started or restarted without root privileges. -If the module needs these privileges, it may have problems starting. -Note that this issue should be resolved by the pending 'socket-creator' -process; once that has been implemented, modules should not need root -privileges anymore. See tickets #800 and #801 for more information. - % BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail. The resolver is being started or restarted without root privileges. If the module needs these privileges, it may have problems starting. diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in index 32c3152099c4a560e4628bb270195a471ea9965c..36ad760e019133c1b125f0878de7f9883a8d0aa3 100755 --- a/src/bin/bind10/bind10_src.py.in +++ b/src/bin/bind10/bind10_src.py.in @@ -331,11 +331,7 @@ class BoB: each one. It then clears that list. """ logger.info(BIND10_KILLING_ALL_PROCESSES) - - for pid in self.components: - logger.info(BIND10_KILL_PROCESS, self.components[pid].name()) - self.components[pid].kill(True) - self.components = {} + self.__kill_children(True) def _read_bind10_config(self): """ @@ -427,6 +423,7 @@ class BoB: while self.cc_session is None: # if we have been trying for "a while" give up if (time.time() - cc_connect_start) > 5: + logger.error(BIND10_CONNECTING_TO_CC_FAIL) raise CChannelConnectError("Unable to connect to c-channel after 5 seconds") # try to connect, and if we can't wait a short while @@ -546,8 +543,6 @@ class BoB: """ Start the Authoritative server """ - if self.uid is not None and self.__started: - logger.warn(BIND10_START_AS_NON_ROOT_AUTH) authargs = ['b10-auth'] if self.verbose: authargs += ['-v'] @@ -693,32 +688,42 @@ class BoB: # from doing so if not self.nokill: # next try sending a SIGTERM - components_to_stop = list(self.components.values()) - for component in components_to_stop: - logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid()) - try: - component.kill() - except OSError: - # ignore these (usually ESRCH because the child - # finally exited) - pass - # finally, send SIGKILL (unmaskable termination) until everybody dies + self.__kill_children(False) + # finally, send SIGKILL (unmaskable termination) until everybody + # dies while self.components: # XXX: some delay probably useful... how much is uncertain time.sleep(0.1) self.reap_children() - components_to_stop = list(self.components.values()) - for component in components_to_stop: - logger.info(BIND10_SEND_SIGKILL, component.name(), - component.pid()) - try: - component.kill(True) - except OSError: - # ignore these (usually ESRCH because the child - # finally exited) - pass + self.__kill_children(True) logger.info(BIND10_SHUTDOWN_COMPLETE) + def __kill_children(self, forceful): + '''Terminate remaining subprocesses by sending a signal. + + The forceful paramter will be passed Component.kill(). + This is a dedicated subroutine of shutdown(), just to unify two + similar cases. + + ''' + logmsg = BIND10_SEND_SIGKILL if forceful else BIND10_SEND_SIGTERM + # We need to make a copy of values as the components may be modified + # in the loop. + for component in list(self.components.values()): + logger.info(logmsg, component.name(), component.pid()) + try: + component.kill(forceful) + except OSError as ex: + # If kill() failed due to EPERM, it doesn't make sense to + # keep trying, so we just log the fact and forget that + # component. Ignore other OSErrors (usually ESRCH because + # the child finally exited) + signame = "SIGKILL" if forceful else "SIGTERM" + logger.info(BIND10_SEND_SIGNAL_FAIL, signame, + component.name(), component.pid(), ex) + if ex.errno == errno.EPERM: + del self.components[component.pid()] + def _get_process_exit_status(self): return os.waitpid(-1, os.WNOHANG) @@ -1137,6 +1142,21 @@ def main(): options = parse_args() + # Announce startup. Making this is the first log message. + try: + logger.info(BIND10_STARTING, VERSION) + except RuntimeError as e: + sys.stderr.write('ERROR: failed to write the initial log: %s\n' % + str(e)) + sys.stderr.write("""\ +TIP: if this is about permission error for a lock file, check if the directory +of the file is writable for the user of the bind10 process; often you need +to start bind10 as a super user. Also, if you specify the -u option to +change the user and group, the directory must be writable for the group, +and the created lock file must be writable for that user. +""") + sys.exit(1) + # Check user ID. setuid = None setgid = None @@ -1169,9 +1189,6 @@ def main(): logger.fatal(BIND10_INVALID_USER, options.user) sys.exit(1) - # Announce startup. - logger.info(BIND10_STARTING, VERSION) - # Create wakeup pipe for signal handlers wakeup_pipe = os.pipe() signal.set_wakeup_fd(wakeup_pipe[1]) diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in index 0b119606e02ec035fdbd974ba84dfbff6829f8b5..ece6370c3a92cf35f08e0fd1e3d0251d1e07c941 100644 --- a/src/bin/bind10/tests/bind10_test.py.in +++ b/src/bin/bind10/tests/bind10_test.py.in @@ -1181,7 +1181,7 @@ class TestBossComponents(unittest.TestCase): # We check somewhere else that the shutdown is actually called # from there (the test_kills). - def __real_test_kill(self, nokill = False): + def __real_test_kill(self, nokill=False, ex_on_kill=None): """ Helper function that does the actual kill functionality testing. """ @@ -1195,8 +1195,23 @@ class TestBossComponents(unittest.TestCase): (anyway it is not told so). It does not die if it is killed the first time. It dies only when killed forcefully. """ + def __init__(self): + # number of kill() calls, preventing infinite loop. + self.__call_count = 0 + def kill(self, forceful=False): + self.__call_count += 1 + if self.__call_count > 2: + raise Exception('Too many calls to ImmortalComponent.kill') + killed.append(forceful) + if ex_on_kill is not None: + # If exception is given by the test, raise it here. + # In the case of ESRCH, the process should have gone + # somehow, so we clear the components. + if ex_on_kill.errno == errno.ESRCH: + bob.components = {} + raise ex_on_kill if forceful: bob.components = {} def pid(self): @@ -1224,7 +1239,10 @@ class TestBossComponents(unittest.TestCase): if nokill: self.assertEqual([], killed) else: - self.assertEqual([False, True], killed) + if ex_on_kill is not None: + self.assertEqual([False], killed) + else: + self.assertEqual([False, True], killed) self.assertTrue(self.__called) @@ -1236,6 +1254,20 @@ class TestBossComponents(unittest.TestCase): """ self.__real_test_kill() + def test_kill_fail(self): + """Test cases where kill() results in an exception due to OS error. + + The behavior should be different for EPERM, so we test two cases. + + """ + + ex = OSError() + ex.errno, ex.strerror = errno.ESRCH, 'No such process' + self.__real_test_kill(ex_on_kill=ex) + + ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted' + self.__real_test_kill(ex_on_kill=ex) + def test_nokill(self): """ Test that the boss *doesn't* kill components which don't want to diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index b4e71bf1827667da2c153cc6823fa50c46b3a5c7..37b74e7fd77f472a242f0a229191dc9252dc6d1e 100644 --- a/src/bin/bindctl/bindcmd.py +++ b/src/bin/bindctl/bindcmd.py @@ -133,7 +133,18 @@ class BindCmdInterpreter(Cmd): return digest def run(self): - '''Parse commands from user and send them to cmdctl. ''' + '''Parse commands from user and send them to cmdctl.''' + + # Show helper warning about a well known issue. We only do this + # when stdin is attached to a terminal, because otherwise it doesn't + # matter and is just noisy, and could even be harmful if the output + # is processed by a script that expects a specific format. + if my_readline == sys.stdin.readline and sys.stdin.isatty(): + sys.stdout.write("""\ +WARNING: Python readline module isn't available, so the command line editor + (including command history management) does not work. See BIND 10 + guide for more details.\n\n""") + try: if not self.login_to_cmdctl(): return 1 diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py index f598472ce0ae9b25b6d1f34d9dc2a84b5c622b05..b9af5c232917149d2e4b17a2f477ab859e653665 100644 --- a/src/bin/bindctl/tests/bindctl_test.py +++ b/src/bin/bindctl/tests/bindctl_test.py @@ -511,10 +511,7 @@ class TestBindCmdInterpreter(unittest.TestCase): def test_csv_file_dir(self): # Checking default value - if "HOME" in os.environ: - home_dir = os.environ["HOME"] - else: - home_dir = pwd.getpwnam(getpass.getuser()).pw_dir + home_dir = pwd.getpwnam(getpass.getuser()).pw_dir self.assertEqual(home_dir + os.sep + '.bind10' + os.sep, bindcmd.BindCmdInterpreter().csv_file_dir) diff --git a/src/bin/cfgmgr/Makefile.am b/src/bin/cfgmgr/Makefile.am index e9e0ccaf856d9e176f478b8f6aa99e77690f246b..9c73f79ee666943ecefbeca86b62f6f0e2260774 100644 --- a/src/bin/cfgmgr/Makefile.am +++ b/src/bin/cfgmgr/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = . plugins tests +SUBDIRS = . plugins local_plugins tests pkglibexecdir = $(libexecdir)/@PACKAGE@ diff --git a/src/bin/cfgmgr/local_plugins/Makefile.am b/src/bin/cfgmgr/local_plugins/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..2f4dd3976714139cc429baa814ad7655a919c0d6 --- /dev/null +++ b/src/bin/cfgmgr/local_plugins/Makefile.am @@ -0,0 +1,11 @@ +# Nothing is installed from this directory. This local_plugins +# directory overrides the plugins directory when lettuce is run, and the +# spec file here is used to serve the static zone from the source tree +# for testing (instead of installation prefix). + +noinst_DATA = datasrc.spec + +datasrc.spec: ../plugins/datasrc.spec.pre + $(SED) -e "s|@@STATIC_ZONE_FILE@@|$(abs_top_builddir)/src/lib/datasrc/static.zone|;s|@@SQLITE3_DATABASE_FILE@@|$(abs_top_builddir)/local.zone.sqlite3|" ../plugins/datasrc.spec.pre >$@ + +CLEANFILES = datasrc.spec diff --git a/src/bin/cfgmgr/plugins/Makefile.am b/src/bin/cfgmgr/plugins/Makefile.am index e6ed1275c814f8298fc7c9c0d676307aade9fa52..5967abd4ace5e1e4e7faca9ff167a19b53bf9dd5 100644 --- a/src/bin/cfgmgr/plugins/Makefile.am +++ b/src/bin/cfgmgr/plugins/Makefile.am @@ -3,7 +3,7 @@ SUBDIRS = tests EXTRA_DIST = README logging.spec tsig_keys.spec datasrc.spec: datasrc.spec.pre - $(SED) -e "s|@@PKGDATADIR@@|$(pkgdatadir)|;s|@@LOCALSTATEDIR@@|$(localstatedir)|" datasrc.spec.pre >$@ + $(SED) -e "s|@@STATIC_ZONE_FILE@@|$(pkgdatadir)/static.zone|;s|@@SQLITE3_DATABASE_FILE@@|$(localstatedir)/$(PACKAGE)/zone.sqlite3|" datasrc.spec.pre >$@ config_plugindir = @prefix@/share/@PACKAGE@/config_plugins config_plugin_DATA = logging.spec tsig_keys.spec datasrc.spec diff --git a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in index f2c6a974a2d52e0241ab9178239bdaf294b1b0af..6d5bd77e7510608c988dd823e91bfe0049f6cbd5 100644 --- a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in +++ b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in @@ -12,7 +12,7 @@ { "type": "sqlite3", "params": { - "database_file": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3" + "database_file": "@@SQLITE3_DATABASE_FILE@@" } } ], @@ -20,7 +20,7 @@ { "type": "static", "cache-enable": false, - "params": "@@PKGDATADIR@@/static.zone" + "params": "@@STATIC_ZONE_FILE@@" } ] }, diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index 35314e818ef0c865b77d72b58d5b848be2d0f378..d60f18678ceb1f3c05a0060e0038bd1ca8c00518 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -161,7 +161,7 @@ get_schema() { # @param $2 Expected backup file upgrade_ok_test() { copy_file $1 $tempfile - ../run_dbutil.sh --upgrade --noconfirm $tempfile + ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile if [ $? -eq 0 ] then # Compare schema with the reference @@ -199,7 +199,7 @@ upgrade_ok_test() { # @param $2 Expected backup file upgrade_fail_test() { copy_file $1 $tempfile - ../run_dbutil.sh --upgrade --noconfirm $tempfile + ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? check_backup $1 $backupfile } @@ -222,7 +222,7 @@ record_count_test() { records_count=`sqlite3 $tempfile 'select count(*) from records'` zones_count=`sqlite3 $tempfile 'select count(*) from zones'` - ../run_dbutil.sh --upgrade --noconfirm $tempfile + ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile if [ $? -ne 0 ] then # Reason for failure should already have been output @@ -268,12 +268,12 @@ record_count_test() { # @param $2 Expected version string check_version() { copy_file $1 $verfile - ../run_dbutil.sh --check $verfile + ${SHELL} ../run_dbutil.sh --check $verfile if [ $? -gt 2 ] then fail "version check failed on database $1; return code $?" else - ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null + ${SHELL} ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null if [ $? -ne 0 ] then fail "database $1 not at expected version $2 (output: $?)" @@ -293,7 +293,7 @@ check_version() { # @param $2 Backup file check_version_fail() { copy_file $1 $verfile - ../run_dbutil.sh --check $verfile + ${SHELL} ../run_dbutil.sh --check $verfile failzero $? check_no_backup $tempfile $backupfile } @@ -305,12 +305,12 @@ rm -f $tempfile $backupfile # Test 1 - check that the utility fails if the database does not exist echo "1.1. Non-existent database - check" -../run_dbutil.sh --check $tempfile +${SHELL} ../run_dbutil.sh --check $tempfile failzero $? check_no_backup $tempfile $backupfile echo "1.2. Non-existent database - upgrade" -../run_dbutil.sh --upgrade --noconfirm $tempfile +${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? check_no_backup $tempfile $backupfile rm -f $tempfile $backupfile @@ -324,7 +324,7 @@ rm -f $tempfile $backupfile echo "2.2. Database is an empty file - upgrade" touch $tempfile -../run_dbutil.sh --upgrade --noconfirm $tempfile +${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? # A backup is performed before anything else, so the backup should exist. check_backup $tempfile $backupfile @@ -338,7 +338,7 @@ rm -f $tempfile $backupfile echo "3.2. Database is not an SQLite file - upgrade" echo "This is not an sqlite3 database" > $tempfile -../run_dbutil.sh --upgrade --noconfirm $tempfile +${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? # ...and as before, a backup should have been created check_backup $tempfile $backupfile @@ -421,31 +421,31 @@ rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2 echo "13.1 Command-line errors" copy_file $testdata/old_v1.sqlite3 $tempfile -../run_dbutil.sh $tempfile +${SHELL} ../run_dbutil.sh $tempfile failzero $? -../run_dbutil.sh --upgrade --check $tempfile +${SHELL} ../run_dbutil.sh --upgrade --check $tempfile failzero $? -../run_dbutil.sh --noconfirm --check $tempfile +${SHELL} ../run_dbutil.sh --noconfirm --check $tempfile failzero $? -../run_dbutil.sh --check +${SHELL} ../run_dbutil.sh --check failzero $? -../run_dbutil.sh --upgrade --noconfirm +${SHELL} ../run_dbutil.sh --upgrade --noconfirm failzero $? -../run_dbutil.sh --check $tempfile $backupfile +${SHELL} ../run_dbutil.sh --check $tempfile $backupfile failzero $? -../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile +${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile failzero $? rm -f $tempfile $backupfile echo "13.2 verbose flag" copy_file $testdata/old_v1.sqlite3 $tempfile -../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile +${SHELL} ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile passzero $? rm -f $tempfile $backupfile echo "13.3 Interactive prompt - yes" copy_file $testdata/old_v1.sqlite3 $tempfile -../run_dbutil.sh --upgrade $tempfile << . +${SHELL} ../run_dbutil.sh --upgrade $tempfile << . Yes . passzero $? @@ -454,7 +454,7 @@ rm -f $tempfile $backupfile echo "13.4 Interactive prompt - no" copy_file $testdata/old_v1.sqlite3 $tempfile -../run_dbutil.sh --upgrade $tempfile << . +${SHELL} ../run_dbutil.sh --upgrade $tempfile << . no . passzero $? @@ -464,7 +464,7 @@ rm -f $tempfile $backupfile echo "13.5 quiet flag" copy_file $testdata/old_v1.sqlite3 $tempfile -../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep . +${SHELL} ../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep . failzero $? rm -f $tempfile $backupfile diff --git a/src/bin/dhcp4/dhcp4_log.h b/src/bin/dhcp4/dhcp4_log.h index 3717b62d9292c66a545628799cc1b670ab9649ac..b042ea4045f05a672a9e82a28c1dd78cce69206e 100644 --- a/src/bin/dhcp4/dhcp4_log.h +++ b/src/bin/dhcp4/dhcp4_log.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DHCP4_LOG__H -#define __DHCP4_LOG__H +#ifndef DHCP4_LOG_H +#define DHCP4_LOG_H #include #include @@ -56,4 +56,4 @@ extern isc::log::Logger dhcp4_logger; } // namespace dhcp4 } // namespace isc -#endif // __DHCP4_LOG__H +#endif // DHCP4_LOG_H diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am index 68aadea5adecf63a4b2246fb70ed4f98fd6849f5..55fe619da0a969b38aa1ccec4a10aa51cc567f05 100644 --- a/src/bin/dhcp6/Makefile.am +++ b/src/bin/dhcp6/Makefile.am @@ -60,6 +60,7 @@ b10_dhcp6_CXXFLAGS = -Wno-unused-parameter endif b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la +b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc index dbffc408636a22b560845b2ac99710c59b42e011..e330e1966c45b347601fb44f20d96b0a39858a79 100644 --- a/src/bin/dhcp6/config_parser.cc +++ b/src/bin/dhcp6/config_parser.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -21,10 +21,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -60,12 +62,18 @@ typedef std::map StringStorage; /// no subnet object created yet to store them. typedef std::vector PoolStorage; +/// @brief Collection of options. +typedef std::vector OptionStorage; + /// @brief Global uint32 parameters that will be used as defaults. Uint32Storage uint32_defaults; /// @brief global string parameters that will be used as defaults. StringStorage string_defaults; +/// @brief Global storage for options that will be used as defaults. +OptionStorage option_defaults; + /// @brief a dummy configuration parser /// /// It is a debugging parser. It does not configure anything, @@ -135,6 +143,9 @@ protected: /// /// For overview of usability of this generic purpose parser, see /// \ref dhcpv6-config-inherit page. +/// +/// @todo this class should be turned into the template class which +/// will handle all uintX_types of data (see ticket #2415). class Uint32Parser : public DhcpConfigParser { public: @@ -151,12 +162,37 @@ public: /// /// @param value pointer to the content of parsed values virtual void build(ConstElementPtr value) { + bool parse_error = false; + // Cast the provided value to int64 value to check. + int64_t int64value = 0; try { - value_ = boost::lexical_cast(value->str()); - } catch (const boost::bad_lexical_cast &) { + // Parsing the value as a int64 value allows to + // check if the provided value is within the range + // of uint32_t (is not negative or greater than + // maximal uint32_t value. + int64value = boost::lexical_cast(value->str()); + } catch (const boost::bad_lexical_cast&) { + parse_error = true; + } + if (!parse_error) { + if ((int64value < 0) || + (int64value > std::numeric_limits::max())) { + parse_error = true; + } else { + try { + value_ = boost::lexical_cast(value->str()); + } catch (const boost::bad_lexical_cast &) { + parse_error = true; + } + } + + } + + if (parse_error) { isc_throw(BadValue, "Failed to parse value " << value->str() << " as unsigned 32-bit integer."); } + storage_->insert(pair(param_name_, value_)); } @@ -445,6 +481,296 @@ protected: PoolStorage* pools_; }; +/// @brief Parser for option data value. +/// +/// This parser parses configuration entries that specify value of +/// a single option. These entries include option name, option code +/// and data carried by the option. If parsing is successful than +/// instance of an option is created and added to the storage provided +/// by the calling class. +/// +/// @todo This class parses and validates option name. However it is +/// not used anywhere util support for option spaces is implemented +/// (see tickets #2319, #2314). When option spaces are implemented +/// there will be a way to reference the particular option using +/// its type (code) or option name. +class OptionDataParser : public DhcpConfigParser { +public: + + /// @brief Constructor. + /// + /// Class constructor. + OptionDataParser(const std::string&) + : options_(NULL) { } + + /// @brief Parses the single option data. + /// + /// This method parses the data of a single option from the configuration. + /// The option data includes option name, option code and data being + /// carried by this option. Eventually it creates the instance of the + /// option. + /// + /// @warning setStorage must be called with valid storage pointer prior + /// to calling this method. + /// + /// @param option_data_entries collection of entries that define value + /// for a particular option. + /// @throw Dhcp6ConfigError if invalid parameter specified in + /// the configuration. + /// @throw isc::InvalidOperation if failed to set storage prior to + /// calling build. + /// @throw isc::BadValue if option data storage is invalid. + virtual void build(ConstElementPtr option_data_entries) { + if (options_ == NULL) { + isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before " + "parsing option data."); + } + BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) { + ParserPtr parser; + if (param.first == "name") { + boost::shared_ptr + name_parser(dynamic_cast(StringParser::Factory(param.first))); + if (name_parser) { + name_parser->setStorage(&string_values_); + parser = name_parser; + } + } else if (param.first == "code") { + boost::shared_ptr + code_parser(dynamic_cast(Uint32Parser::Factory(param.first))); + if (code_parser) { + code_parser->setStorage(&uint32_values_); + parser = code_parser; + } + } else if (param.first == "data") { + boost::shared_ptr + value_parser(dynamic_cast(StringParser::Factory(param.first))); + if (value_parser) { + value_parser->setStorage(&string_values_); + parser = value_parser; + } + } else { + isc_throw(Dhcp6ConfigError, + "Parser error: option-data parameter not supported: " + << param.first); + } + parser->build(param.second); + } + // Try to create the option instance. + createOption(); + } + + /// @brief Does nothing. + /// + /// This function does noting because option data is committed + /// by a higher level parser. + virtual void commit() { } + + /// @brief Set storage for the parser. + /// + /// Sets storage for the parser. This storage points to the + /// vector of options and is used by multiple instances of + /// OptionDataParser. Each instance creates exactly one object + /// of dhcp::Option or derived type and appends it to this + /// storage. + /// + /// @param storage pointer to the options storage + void setStorage(OptionStorage* storage) { + options_ = storage; + } + +private: + + /// @brief Create option instance. + /// + /// Creates an instance of an option and adds it to the provided + /// options storage. If the option data parsed by \ref build function + /// are invalid or insufficient it emits exception. + /// + /// @warning this function does not check if options_ storage pointer + /// is intitialized but this is not needed here because it is checked in + /// \ref build function. + /// + /// @throw Dhcp6ConfigError if parameters provided in the configuration + /// are invalid. + void createOption() { + // Option code is held in the uint32_t storage but is supposed to + // be uint16_t value. We need to check that value in the configuration + // does not exceed range of uint16_t and is not zero. + uint32_t option_code = getUint32Param("code"); + if (option_code == 0) { + isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not" + << " be equal to zero. Option code '0' is reserved in" + << " DHCPv6."); + } else if (option_code > std::numeric_limits::max()) { + isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not" + << " exceed " << std::numeric_limits::max()); + } + // Check the option name has been specified, is non-empty and does not + // contain spaces. + // @todo possibly some more restrictions apply here? + std::string option_name = getStringParam("name"); + if (option_name.empty()) { + isc_throw(Dhcp6ConfigError, "Parser error: option name must not be" + << " empty"); + } else if (option_name.find(" ") != std::string::npos) { + isc_throw(Dhcp6ConfigError, "Parser error: option name must not contain" + << " spaces"); + } + + // Get option data from the configuration database ('data' field). + // Option data is specified by the user as case insensitive string + // of hexadecimal digits for each option. + std::string option_data = getStringParam("data"); + // Transform string of hexadecimal digits into binary format. + std::vector binary; + try { + util::encode::decodeHex(option_data, binary); + } catch (...) { + isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid" + << " string of hexadecimal digits: " << option_data); + } + // Get all existing DHCPv6 option definitions. The one that matches + // our option will be picked and used to create it. + OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6); + // Get search index #1. It allows searching for options definitions + // using option type value. + const OptionDefContainerTypeIndex& idx = option_defs.get<1>(); + // Get all option definitions matching option code we want to create. + const OptionDefContainerTypeRange& range = idx.equal_range(option_code); + size_t num_defs = std::distance(range.first, range.second); + OptionPtr option; + // Currently we do not allow duplicated definitions and if there are + // any duplicates we issue internal server error. + if (num_defs > 1) { + isc_throw(Dhcp6ConfigError, "Internal error: currently it is not" + << " supported to initialize multiple option definitions" + << " for the same option code. This will be supported once" + << " there option spaces are implemented."); + } else if (num_defs == 0) { + // @todo We have a limited set of option definitions intiialized at the moment. + // In the future we want to initialize option definitions for all options. + // Consequently error will be issued if option definition does not exist + // for a particular option code. For now it is ok to create generic option + // if definition does not exist. + OptionPtr option(new Option(Option::V6, static_cast(option_code), + binary)); + // If option is created succesfully, add it to the storage. + options_->push_back(option); + } else { + // We have exactly one option definition for the particular option code. + // use it to create option instance. + const OptionDefinitionPtr& def = *(range.first); + // getFactory should never return NULL pointer. + Option::Factory* factory = def->getFactory(); + assert(factory != NULL); + try { + OptionPtr option = factory(Option::V6, option_code, binary); + options_->push_back(option); + } catch (const isc::Exception& ex) { + isc_throw(Dhcp6ConfigError, "Parser error: option data does not match" + << " option definition (code " << option_code << "): " + << ex.what()); + } + } + } + + /// @brief Get a parameter from the strings storage. + /// + /// @param param_id parameter identifier. + /// @throw Dhcp6ConfigError if parameter has not been found. + std::string getStringParam(const std::string& param_id) const { + StringStorage::const_iterator param = string_values_.find(param_id); + if (param == string_values_.end()) { + isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter" + << " '" << param_id << "' not specified"); + } + return (param->second); + } + + /// @brief Get a parameter from the uint32 values storage. + /// + /// @param param_id parameter identifier. + /// @throw Dhcp6ConfigError if parameter has not been found. + uint32_t getUint32Param(const std::string& param_id) const { + Uint32Storage::const_iterator param = uint32_values_.find(param_id); + if (param == uint32_values_.end()) { + isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter" + << " '" << param_id << "' not specified"); + } + return (param->second); + } + + /// Storage for uint32 values (e.g. option code). + Uint32Storage uint32_values_; + /// Storage for string values (e.g. option name or data). + StringStorage string_values_; + /// Pointer to options storage. This storage is provided by + /// the calling class and is shared by all OptionDataParser objects. + OptionStorage* options_; +}; + +/// @brief Parser for option data values with ina subnet. +/// +/// This parser iterates over all entries that define options +/// data for a particular subnet and creates a collection of options. +/// If parsing is successful, all these options are added to the Subnet +/// object. +class OptionDataListParser : public DhcpConfigParser { +public: + + /// @brief Constructor. + /// + /// Unless otherwise specified, parsed options will be stored in + /// a global option containers (option_default). That storage location + /// is overriden on a subnet basis. + OptionDataListParser(const std::string&) + : options_(&option_defaults) { } + + /// @brief Parses entries that define options' data for a subnet. + /// + /// This method iterates over all entries that define option data + /// for options within a single subnet and creates options' instances. + /// + /// @param option_data_list pointer to a list of options' data sets. + /// @throw Dhcp6ConfigError if option parsing failed. + void build(ConstElementPtr option_data_list) { + BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) { + boost::shared_ptr parser(new OptionDataParser("option-data")); + // options_ member will hold instances of all options thus + // each OptionDataParser takes it as a storage. + parser->setStorage(options_); + // Build the instance of a singkle option. + parser->build(option_value); + } + } + + /// @brief Set storage for option instances. + /// + /// @param storage pointer to options storage. + void setStorage(OptionStorage* storage) { + options_ = storage; + } + + + /// @brief Does nothing. + /// + /// @todo Currently this function does nothing but in the future + /// we may need to extend it to commit at this level. + void commit() { } + + /// @brief Create DhcpDataListParser object + /// + /// @param param_name param name. + /// + /// @return DhcpConfigParser object. + static DhcpConfigParser* Factory(const std::string& param_name) { + return (new OptionDataListParser(param_name)); + } + + /// Pointer to options instances storage. + OptionStorage* options_; +}; + /// @brief this class parses a single subnet /// /// This class parses the whole subnet definition. It creates parsers @@ -464,35 +790,36 @@ public: void build(ConstElementPtr subnet) { BOOST_FOREACH(ConfigPair param, subnet->mapValue()) { - ParserPtr parser(createSubnet6ConfigParser(param.first)); - - // if this is an Uint32 parser, tell it to store the values - // in values_, rather than in global storage - boost::shared_ptr uintParser = - boost::dynamic_pointer_cast(parser); - if (uintParser) { - uintParser->setStorage(&uint32_values_); - } else { - - boost::shared_ptr stringParser = - boost::dynamic_pointer_cast(parser); - if (stringParser) { - stringParser->setStorage(&string_values_); - } else { - - boost::shared_ptr poolParser = - boost::dynamic_pointer_cast(parser); - if (poolParser) { - poolParser->setStorage(&pools_); - } - } + // The actual type of the parser is unknown here. We have to discover + // parser type here to invoke corresponding setStorage function on it. + // We discover parser type by trying to cast the parser to various + // parser types and checking which one was successful. For this one + // a setStorage and build methods are invoked. + + // Try uint32 type parser. + if (buildParser(parser, uint32_values_, + param.second)) { + // Storage set, build invoked on the parser, proceed with + // next configuration element. + continue; + } + // Try string type parser. + if (buildParser(parser, string_values_, + param.second)) { + continue; + } + // Try pools parser. + if (buildParser(parser, pools_, + param.second)) { + continue; + } + // Try option data parser. + if (buildParser(parser, options_, + param.second)) { + continue; } - - parser->build(param.second); - parsers_.push_back(parser); } - // Ok, we now have subnet parsed } @@ -540,10 +867,78 @@ public: subnet->addPool6(*it); } + const Subnet::OptionContainer& options = subnet->getOptions(); + const Subnet::OptionContainerTypeIndex& idx = options.get<1>(); + + // Add subnet specific options. + BOOST_FOREACH(OptionPtr option, options_) { + Subnet::OptionContainerTypeRange range = idx.equal_range(option->getType()); + if (std::distance(range.first, range.second) > 0) { + LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE) + .arg(option->getType()).arg(addr.toText()); + } + subnet->addOption(option); + } + + // Check all global options and add them to the subnet object if + // they have been configured in the global scope. If they have been + // configured in the subnet scope we don't add global option because + // the one configured in the subnet scope always takes precedense. + BOOST_FOREACH(OptionPtr option, option_defaults) { + // Get all options specified locally in the subnet and having + // code equal to global option's code. + Subnet::OptionContainerTypeRange range = idx.equal_range(option->getType()); + // @todo: In the future we will be searching for options using either + // option code or namespace. Currently we have only the option + // code available so if there is at least one option found with the + // specific code we don't add globally configured option. + // @todo with this code the first globally configured option + // with the given code will be added to a subnet. We may + // want to issue warning about dropping configuration of + // global option if one already exsist. + if (std::distance(range.first, range.second) == 0) { + subnet->addOption(option); + } + } + CfgMgr::instance().addSubnet6(subnet); } -protected: +private: + + /// @brief Set storage for a parser and invoke build. + /// + /// This helper method casts the provided parser pointer to specified + /// type. If cast is successful it sets the corresponding storage for + /// this parser, invokes build on it and save the parser. + /// + /// @tparam T parser type to which parser argument should be cast. + /// @tparam Y storage type for the specified parser type. + /// @param parser parser on which build must be invoked. + /// @param storage reference to a storage that will be set for a parser. + /// @param subnet subnet element read from the configuration and being parsed. + /// @return true if parser pointer was successfully cast to specialized + /// parser type provided as Y. + template + bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) { + // We need to cast to T in order to set storage for the parser. + boost::shared_ptr cast_parser = boost::dynamic_pointer_cast(parser); + // It is common that this cast is not successful because we try to cast to all + // supported parser types as we don't know the type of a parser in advance. + if (cast_parser) { + // Cast, successful so we go ahead with setting storage and actual parse. + cast_parser->setStorage(&storage); + parser->build(subnet); + parsers_.push_back(parser); + // We indicate that cast was successful so as the calling function + // may skip attempts to cast to other parser types and proceed to + // next element. + return (true); + } + // It was not successful. Indicate that another parser type + // should be tried. + return (false); + } /// @brief creates parsers for entries in subnet definition /// @@ -569,6 +964,10 @@ protected: factories.insert(pair( "pool", PoolParser::Factory)); + factories.insert(pair( + "option-data", OptionDataListParser::Factory)); + + FactoryMap::iterator f = factories.find(config_id); if (f == factories.end()) { // Used for debugging only. @@ -622,6 +1021,9 @@ protected: /// storage for pools belonging to this subnet PoolStorage pools_; + /// storage for options belonging to this subnet + OptionStorage options_; + /// parsers are stored here ParserCollection parsers_; }; @@ -698,7 +1100,6 @@ public: DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) { FactoryMap factories; - // factories.insert(pair( "preferred-lifetime", Uint32Parser::Factory)); factories.insert(pair( @@ -713,6 +1114,9 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) { factories.insert(pair( "subnet6", Subnets6ListConfigParser::Factory)); + factories.insert(pair( + "option-data", OptionDataListParser::Factory)); + factories.insert(pair( "version", StringParser::Factory)); @@ -749,6 +1153,12 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) { "Null pointer is passed to configuration parser"); } + /// Reset global storage. Containers being reset below may contain + /// data from the previous configuration attempts. + option_defaults.clear(); + uint32_defaults.clear(); + string_defaults.clear(); + /// @todo: append most essential info here (like "2 new subnets configured") string config_details; diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox index fe842de44b571a762ce42d6f028ad4552706a6e8..c234f40ed319aec073093b6c08236f368565cbb5 100644 --- a/src/bin/dhcp6/dhcp6.dox +++ b/src/bin/dhcp6/dhcp6.dox @@ -35,7 +35,7 @@ This method iterates over list of received configuration elements and creates a list of parsers for each received entry. Parser is an object that is derived - from a \ref isc::dhcp::Dhcp6ConfigParser class. Once a parser is created + from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created (constructor), its value is set (using build() method). Once all parsers are build, the configuration is then applied ("commited") and commit() method is called. @@ -51,7 +51,7 @@ @section dhcpv6-config-inherit DHCPv6 Configuration Inheritance - One notable useful features of DHCP configuration is its parameter inheritance. + One notable useful feature of DHCP configuration is its parameter inheritance. For example, renew-timer value may be specified at a global scope and it then applies to all subnets. However, some subnets may have it overwritten with more specific values that takes precedence over global values that are considered @@ -64,7 +64,7 @@ phase (commit() method), appropriate parsers can use apply parameter inheritance. Debugging configuration parser may be confusing. Therefore there is a special - class called \ref isc::dhcp::DummyParser. It does not configure anything, but just + class called \ref isc::dhcp::DebugParser. It does not configure anything, but just accepts any parameter of any type. If requested to commit configuration, it will print out received parameter name and its value. This class is not currently used, but it is convenient to have it every time a new parameter is added to DHCP @@ -76,4 +76,6 @@ simple as possible. In fact, currently the code has to call Subnet6->getT1() and do not implement any fancy inheritance logic. + @todo Add section about setting up options and their definitions with bindctl. + */ diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec index f35f606213a3f1cbc193c3abff9a73b5f0792758..c5e9565f5fcb48e7402f2866f604166ab0a57600 100644 --- a/src/bin/dhcp6/dhcp6.spec +++ b/src/bin/dhcp6/dhcp6.spec @@ -40,6 +40,37 @@ "item_default": 4000 }, + { "item_name": "option-data", + "item_type": "list", + "item_optional": false, + "item_default": [], + "list_item_spec": + { + "item_name": "single-option-data", + "item_type": "map", + "item_optional": false, + "item_default": {}, + "map_item_spec": [ + { + "item_name": "name", + "item_type": "string", + "item_optional": false, + "item_default": "" + }, + + { "item_name": "code", + "item_type": "integer", + "item_optional": false, + "item_default": 0 + }, + { "item_name": "data", + "item_type": "string", + "item_optional": false, + "item_default": "" + } ] + } + }, + { "item_name": "subnet6", "item_type": "list", "item_optional": false, @@ -92,10 +123,40 @@ "item_optional": false, "item_default": "" } - } - ] - } - } + }, + { "item_name": "option-data", + "item_type": "list", + "item_optional": false, + "item_default": [], + "list_item_spec": + { + "item_name": "single-option-data", + "item_type": "map", + "item_optional": false, + "item_default": {}, + "map_item_spec": [ + { + "item_name": "name", + "item_type": "string", + "item_optional": false, + "item_default": "" + }, + { + "item_name": "code", + "item_type": "integer", + "item_optional": false, + "item_default": 0 + }, + { + "item_name": "data", + "item_type": "string", + "item_optional": false, + "item_default": "" + } ] + } + } ] + } + } ], "commands": [ { diff --git a/src/bin/dhcp6/dhcp6_log.h b/src/bin/dhcp6/dhcp6_log.h index 6d7f4e33b09f9dee539f3f701b8837e2d55d4f39..fb3c3f90bb1eb7a551a71646f82eeee6b19ce449 100644 --- a/src/bin/dhcp6/dhcp6_log.h +++ b/src/bin/dhcp6/dhcp6_log.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DHCP6_LOG__H -#define __DHCP6_LOG__H +#ifndef DHCP6_LOG_H +#define DHCP6_LOG_H #include #include @@ -56,4 +56,4 @@ extern isc::log::Logger dhcp6_logger; } // namespace dhcp6 } // namespace isc -#endif // __DHCP6_LOG__H +#endif // DHCP6_LOG_H diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index 2399c190369956f502a206301e4cbdcc6b386286..5f9cd02b68cdf7070181be56e15b5e24ab8d1a90 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -30,6 +30,11 @@ from the BIND 10 control system by the IPv6 DHCP server. A debug message indicating that the IPv6 DHCP server has received an updated configuration from the BIND 10 configuration system. +% DHCP6_DB_BACKEND_STARTED Lease database started (backend type: %1) +This informational message is printed every time DHCPv6 is started. +It indicates what database backend type is being to store lease and +other information. + % DHCP6_NOT_RUNNING IPv6 DHCP server is not running A warning message is issued when an attempt is made to shut down the IPv6 DHCP server but it is not running. @@ -42,6 +47,27 @@ interfaces and is therefore shutting down. A debug message issued during startup, this indicates that the IPv6 DHCP server is about to open sockets on the specified port. +% DHCP6_LEASE_ADVERT Lease %1 advertised (client duid=%2, iaid=%3) +This debug message indicates that the server successfully advertised +a lease. It is up to the client to choose one server out of othe advertised +and continue allocation with that server. This is a normal behavior and +indicates successful operation. + +% DHCP6_LEASE_ALLOC lease %1 has been allocated (client duid=%2, iaid=%3) +This debug message indicates that the server successfully granted (in +response to client's REQUEST message) a lease. This is a normal behavior +and incicates successful operation. + +% DHCP6_LEASE_ADVERT_FAIL failed to advertise a lease for client duid=%1, iaid=%2 +This message indicates that the server failed to advertise (in response to +received SOLICIT) a lease for a given client. There may be many reasons for +such failure. Each specific failure is logged in a separate log entry. + +% DHCP6_LEASE_ALLOC_FAIL failed to grant a lease for client duid=%1, iaid=%2 +This message indicates that the server failed to grant (in response to +received REQUEST) a lease for a given client. There may be many reasons for +such failure. Each specific failure is logged in a separate log entry. + % DHCP6_PACKET_PARSE_FAIL failed to parse incoming packet The IPv6 DHCP server has received a packet that it is unable to interpret. @@ -50,7 +76,7 @@ The IPv6 DHCP server tried to receive a packet but an error occured during this attempt. The reason for the error is included in the message. -% DHCP6_PACKET_RECEIVED %1 (type %2) packet received +% DHCP6_PACKET_RECEIVED %1 packet received A debug message noting that the server has received the specified type of packet. Note that a packet marked as UNKNOWN may well be a valid DHCP packet, just a type not expected by the server (e.g. it will report @@ -66,10 +92,15 @@ This error is output if the server failed to assemble the data to be returned to the client into a valid packet. The reason is most likely to be to a programming error: please raise a bug report. -% DHCP6_QUERY_DATA received packet length %1, data length %2, data is <%3> +% DHCP6_PROCESS_IA_NA_REQUEST server is processing IA_NA option (duid=%1, iaid=%2, hint=%3) +This is a debug message that indicates a processing of received IA_NA +option. It may optionally contain an address that may be used by the server +as a hint for possible requested address. + +% DHCP6_QUERY_DATA received packet length %1, data length %2, data is %3 A debug message listing the data received from the client or relay. -% DHCP6_RESPONSE_DATA responding with packet type %1 data is <%2> +% DHCP6_RESPONSE_DATA responding with packet type %1 data is %2 A debug message listing the data returned to the client. % DHCP6_SERVER_FAILED server failed: %1 @@ -110,6 +141,28 @@ This is a debug message issued during the IPv6 DHCP server startup. It lists some information about the parameters with which the server is running. +% DHCP6_SUBNET_SELECTED the %1 subnet was selected for client assignment +This is a debug message informing that a given subnet was selected. It will +be used for address and option assignment. This is one of the early steps +in the processing of incoming client message. + +% DHCP6_SUBNET_SELECTION_FAILED failed to select a subnet for incoming packet, src=%1 type=%2 +This warning message is output when a packet was received from a subnet for +which the DHCPv6 server has not been configured. The cause is most likely due +to a misconfiguration of the server. The packet processing will continue, but +the response will only contain generic configuration parameters and no +addresses or prefixes. + +% DHCP6_NO_SUBNET_DEF_OPT failed to find subnet for address %1 when adding default options +This warning message indicates that when attempting to add default options to a response, +the server found that it was not configured to support the subnet from which the DHCPv6 +request was received. The packet has been ignored. + +% DHCP6_NO_SUBNET_REQ_OPT failed to find subnet for address %1 when adding requested options +This warning message indicates that when attempting to add requested options to a response, +the server found that it was not configured to support the subnet from which the DHCPv6 +request was received. The packet has been ignored. + % DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1 This critical error message indicates that the initial DHCPv6 configuration has failed. The server will start, but nothing will be @@ -120,7 +173,7 @@ This is a debug message that is issued every time the server receives a configuration. That happens start up and also when a server configuration change is committed by the administrator. -% DHCP6_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1 +% DHCP6_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1 This is an informational message reporting that the configuration has been extended to include the specified subnet. @@ -129,3 +182,8 @@ This is an informational message announcing the successful processing of a new configuration. it is output during server startup, and when an updated configuration is committed by the administrator. Additional information may be provided. + +% DHCP6_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2 +This warning message is issued on attempt to configure multiple options with the +same option code for the particular subnet. Adding multiple options is uncommon +for DHCPv6, yet it is not prohibited. diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 7c57a6139cfaeaa591f4018a62a109011560f713..5f844a3d94786b47f3d36b131c06a8e57599a5c6 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -12,6 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +#include + #include #include @@ -20,13 +22,28 @@ #include #include #include +#include #include #include #include +#include #include +#include +#include #include #include #include +#include +#include +#include +#include +#include + +// @todo: Replace this with MySQL_LeaseMgr (or a LeaseMgr factory) +// once it is merged +#include + +#include using namespace isc; using namespace isc::asiolink; @@ -34,50 +51,62 @@ using namespace isc::dhcp; using namespace isc::util; using namespace std; -const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd"; -const uint32_t HARDCODED_T1 = 1500; // in seconds -const uint32_t HARDCODED_T2 = 2600; // in seconds -const uint32_t HARDCODED_PREFERRED_LIFETIME = 3600; // in seconds -const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds -const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1"; +namespace isc { +namespace dhcp { -Dhcpv6Srv::Dhcpv6Srv(uint16_t port) { - if (port == 0) { - // used for testing purposes. Some tests, e.g. configuration parser, - // require Dhcpv6Srv object, but they don't really need it to do - // anything. This speed up and simplifies the tests. - return; - } +Dhcpv6Srv::Dhcpv6Srv(uint16_t port) : alloc_engine_(), serverid_(), + shutdown_(false) { LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port); - // First call to instance() will create IfaceMgr (it's a singleton) - // it may throw something if things go wrong + // Initialize objects required for DHCP server operation. try { - - if (IfaceMgr::instance().countIfaces() == 0) { - LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES); - shutdown_ = true; - return; + // Initialize standard DHCPv6 option definitions. This function + // may throw bad_alloc if system goes out of memory during the + // creation if option definitions. It may also throw isc::Unexpected + // if definitions are wrong. This would mean error in implementation. + initStdOptionDefs(); + + // Port 0 is used for testing purposes. It means that the server should + // not open any sockets at all. Some tests, e.g. configuration parser, + // require Dhcpv6Srv object, but they don't really need it to do + // anything. This speed up and simplifies the tests. + if (port > 0) { + if (IfaceMgr::instance().countIfaces() == 0) { + LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES); + shutdown_ = true; + return; + } + IfaceMgr::instance().openSockets6(port); } - IfaceMgr::instance().openSockets6(port); - setServerID(); - /// @todo: instantiate LeaseMgr here once it is imlpemented. - } catch (const std::exception &e) { LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what()); shutdown_ = true; return; } - shutdown_ = false; + // Instantiate LeaseMgr + // @todo: Replace this with MySQL_LeaseMgr (or a LeaseMgr factory) + // once it is merged +#ifdef HAVE_MYSQL + LeaseMgrFactory::create("type=mysql user=kea password=kea name=kea host=localhost"); +#else + LeaseMgrFactory::create("type=memfile"); +#endif + LOG_INFO(dhcp6_logger, DHCP6_DB_BACKEND_STARTED) + .arg(LeaseMgrFactory::instance().getName()); + + // Instantiate allocation engine + alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)); } Dhcpv6Srv::~Dhcpv6Srv() { IfaceMgr::instance().closeSockets(); + + LeaseMgrFactory::destroy(); } void Dhcpv6Srv::shutdown() { @@ -87,7 +116,12 @@ void Dhcpv6Srv::shutdown() { bool Dhcpv6Srv::run() { while (!shutdown_) { - /// @todo: calculate actual timeout once we have lease database + /// @todo: calculate actual timeout to the next event (e.g. lease + /// expiration) once we have lease database. The idea here is that + /// it is possible to do everything in a single process/thread. + /// For now, we are just calling select for 1000 seconds. There + /// were some issues reported on some systems when calling select() + /// with too large values. Unfortunately, I don't recall the details. int timeout = 1000; // client's message and server's response @@ -107,10 +141,9 @@ bool Dhcpv6Srv::run() { continue; } LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED) - .arg(serverReceivedPacketName(query->getType())) - .arg(query->getType()); + .arg(serverReceivedPacketName(query->getType())); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA) - .arg(query->getType()) + .arg(static_cast(query->getType())) .arg(query->getBuffer().getLength()) .arg(query->toText()); @@ -196,7 +229,7 @@ void Dhcpv6Srv::setServerID() { const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces(); - // let's find suitable interface + // Let's find suitable interface. for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin(); iface != ifaces.end(); ++iface) { // All the following checks could be merged into one multi-condition @@ -217,17 +250,17 @@ void Dhcpv6Srv::setServerID() { continue; } - // let's don't use loopback + // Let's don't use loopback. if (iface->flag_loopback_) { continue; } - // let's skip downed interfaces. It is better to use working ones. + // Let's skip downed interfaces. It is better to use working ones. if (!iface->flag_up_) { continue; } - // some interfaces (like lo on Linux) report 6-bytes long + // Some interfaces (like lo on Linux) report 6-bytes long // MAC adress 00:00:00:00:00:00. Let's not use such weird interfaces // to generate DUID. if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) { @@ -243,37 +276,37 @@ void Dhcpv6Srv::setServerID() { seconds -= DUID_TIME_EPOCH; OptionBuffer srvid(8 + iface->getMacLen()); - writeUint16(DUID_LLT, &srvid[0]); + writeUint16(DUID::DUID_LLT, &srvid[0]); writeUint16(HWTYPE_ETHERNET, &srvid[2]); writeUint32(static_cast(seconds), &srvid[4]); - memcpy(&srvid[0]+8, iface->getMac(), iface->getMacLen()); + memcpy(&srvid[0] + 8, iface->getMac(), iface->getMacLen()); serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID, srvid.begin(), srvid.end())); return; } - // if we reached here, there are no suitable interfaces found. + // If we reached here, there are no suitable interfaces found. // Either interface detection is not supported on this platform or // this is really weird box. Let's use DUID-EN instead. // See Section 9.3 of RFC3315 for details. OptionBuffer srvid(12); - writeUint16(DUID_EN, &srvid[0]); + writeUint16(DUID::DUID_EN, &srvid[0]); writeUint32(ENTERPRISE_ID_ISC, &srvid[2]); // Length of the identifier is company specific. I hereby declare // ISC "standard" of 6 bytes long pseudo-random numbers. srandom(time(NULL)); - fillRandom(&srvid[6],&srvid[12]); + fillRandom(&srvid[6], &srvid[12]); serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID, srvid.begin(), srvid.end())); } void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) { - // add client-id - boost::shared_ptr