diff --git a/ChangeLog b/ChangeLog index f7f5ab14a5..91645876f1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,255 @@ -4XX. [func] tomek +525. [func] tomek b10-dhcp4: DHCPv4 server is now able to parse configuration. It is possible to specify IPv4 subnets with dynamic pools within them. Although configuration is accepted, it is not used yet. This will be implemented shortly. + (Trac #2270, git de29c07129d41c96ee0d5eebdd30a1ea7fb9ac8a) + +524. [func] tomek + b10-dhcp6 is now able to handle RENEW messages. Leases are + renewed and REPLY responses are sent back to clients. + (Trac #2325, git 7f6c9d057cc0a7a10f41ce7da9c8565b9ee85246) + +523. [bug] muks + Fixed a problem in inmem NSEC3 lookup (for, instance when using a + zone with no non-apex names) which caused exceptions when the zone + origin was not added as an explicit NSEC3 record. + (Trac #2503, git 6fe86386be0e7598633fe35999112c1a6e3b0370) + +522. [func]* jelte + Configuration of TSIG keys for b10-xfrin has changed; instead of + specifying the full TSIG key (::) it now expects + just the name, and uses the global TSIG Key Ring like all the other + components (configuration list /tsig_keys/keys). + Note: this is not automatically updated, so if you use TSIG in + xfrin, you need to update your configuration. + (Trac #1351, git e65b7b36f60f14b7abe083da411e6934cdfbae7a) + +521. [func] marcin + Implemented definitions for DHCPv6 standard options identified + by codes up to 48. These definitions are now used by the DHCPv6 + server to create instances of options being sent to a client. + (Trac #2491, git 0a4faa07777189ed9c25211987a1a9b574015a95) + +520. [func] jelte + The system no longer prints initial log messages to stdout + regardless of what logging configuration is present, but it + temporarily stores any log messages until the configuration is + processed. If there is no specific configuration, or if the + configuration cannot be accessed, it will still fall back to stdout. + Note that there are still a few instances where output is printed, + these shall be addressed separately. + Note also that, currently, in case it falls back to stdout (such as + when it cannot connect to b10-cfgmgr), all log messages are always + printed (including debug messages), regardless of whether -v was + used. This shall also be addressed in a future change. + (Trac #2445, git 74a0abe5a6d10b28e4a3e360e87b129c232dea68) + +519. [bug] muks + Fixed a problem in inmem NSEC lookup which caused returning an + incorrect NSEC record or (in rare cases) assert failures + when a non-existent domain was queried, which was a sub-domain of + a domain that existed. + (Trac #2504, git 835553eb309d100b062051f7ef18422d2e8e3ae4) + +518. [func] stephen + Extend DHCP MySQL backend to handle IPv4 addresses. + (Trac #2404, git ce7db48d3ff5d5aad12b1da5e67ae60073cb2607) + +517. [func] stephen + Added IOAddress::toBytes() to get byte representation of address. + Also added convenience methods for V4/V6 address determination. + (Trac #2396, git c23f87e8ac3ea781b38d688f8f7b58539f85e35a) + +516. [bug] marcin + Fixed 'make distcheck' failure when running perfdhcp unit tests. + The unit tests used to read files from the folder specified + with the path relative to current folder, thus when the test was + run from a different folder the files could not be found. + (Trac #2479, git 4e8325e1b309f1d388a3055ec1e1df98c377f383) + +515. [bug] jinmei + The in-memory data source now accepts an RRSIG provided without + a covered RRset in loading. A subsequent query for its owner name + of the covered type would generally result in NXRRSET; if the + covered RRset is of type NSEC3, the corresponding NSEC3 processing + would result in SERVFAIL. + (Trac #2420, git 6744c100953f6def5500bcb4bfc330b9ffba0f5f) + +514. [bug] jelte + b10-msgq now handles socket errors more gracefully when sending data + to clients. It no longer exits with 'broken pipe' errors, and is + also better at resending data on temporary error codes from send(). + (Trac #2398, git 9f6b45ee210a253dca608848a58c824ff5e0d234) + +513. [func] marcin + Implemented the OptionCustom class for DHCPv4 and DHCPv6. + This class represents an option which has a defined + structure: a set of data fields of specific types and order. + It is used to represent those options that can't be + represented by any other specialized class. + (Trac #2312, git 28d885b457dda970d9aecc5de018ec1120143a10) + +512. [func] jelte + Added a new tool b10-certgen, to check and update the self-signed + SSL certificate used by b10-cmdctl. The original certificate + provided has been removed, and a fresh one is generated upon first + build. See the b10-certgen manpage for information on how to update + existing installed certificates. + (Trac #1044, git 510773dd9057ccf6caa8241e74a7a0b34ca971ab) + +511. [bug] stephen + Fixed a race condition in the DHCP tests whereby the test program + spawned a subprocess and attempted to read (without waiting) from + the interconnecting pipe before the subprocess had written + anything. The lack of output was being interpreted as a test + failure. + (Trac #2410, git f53e65cdceeb8e6da4723730e4ed0a17e4646579) + +510. [func] marcin + DHCP option instances can be created using a collection of strings. + Each string represents a value of a particular data field within + an option. The data field values, given as strings, are validated + against the actual types of option fields specified in the options + definitions. + (Trac #2490, git 56cfd6612fcaeae9acec4a94e1e5f1a88142c44d) + +509. [func] muks + Log messages now include the pid of the process that logged the + message. + (Trac #1745, git fc8bbf3d438e8154e7c2bdd322145a7f7854dc6a) + +508. [bug] stephen + Split the DHCP library into two directories, each with its own + Makefile. This properly solves the problem whereby a "make" + operation with multiple threads could fail because of the + dependencies between two libraries in the same directory. + (Trac #2475, git 834fa9e8f5097c6fd06845620f68547a97da8ff8) + +bind10-devel-20121115 released on November 15, 2012 + +507. [doc] jelte + Added a chapter about the use of the bindctl command tool to + to the BIND 10 guide. + (Trac #2305, git c4b0294b5bf4a9d32fb18ab62ca572f492788d72) + +506. [security] jinmei + Fixed a use-after-free case in handling DNAME record with the + in-memory data source. This could lead to a crash of b10-auth + if it serves a zone containing a DNAME RR from the in-memory + data source. This bug was introduced at bind10-devel-20120927. + (Trac #2471, git 2b1793ac78f972ddb1ae2fd092a7f539902223ff) + +505. [bug] jelte + Fixed a bug in b10-xfrin where a wrong call was made during the + final check of a TSIG-signed transfer, incorrectly rejecting the + transfer. + (Trac #2464, git eac81c0cbebee72f6478bdb5cda915f5470d08e1) + +504. [bug]* naokikambe + Fixed an XML format viewed from b10-stats-httpd. Regarding + per-zone counters as zones of Xfrout, a part of the item + values wasn't an exact XML format. A zone name can be + specified in URI as + /bind10/statistics/xml/Xfrout/zones/example.org/xfrreqdone. + XSD and XSL formats are also changed to constant ones due + to these changes. + (Trac #2298, git 512d2d46f3cb431bcdbf8d90af27bff8874ba075) + +503. [func] Stephen + Add initial version of a MySQL backend for the DHCP code. This + implements the basic IPv6 lease access functions - add lease, delete + lease and update lease. The backend is enabled by specifying + --with-dhcp-mysql on the "configure" command line: without this + switch, the MySQL code is not compiled, so leaving BIND 10 able to + be built on systems without MySQL installed. + (Trac #2342, git c7defffb89bd0f3fdd7ad2437c78950bcb86ad37) + +502. [func] vorner + TTLs can be specified with units as well as number of seconds now. + This allows specifications like "1D3H". + (Trac #2384, git 44c321c37e17347f33ced9d0868af0c891ff422b) + +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 @@ -33,7 +280,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 1ed0d6325f..fe995a7b8d 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. @@ -20,6 +20,13 @@ dist_doc_DATA = AUTHORS COPYING ChangeLog README .PHONY: check-valgrind check-valgrind-suppress +install-exec-hook: + -@echo -e "\033[1;33m" # switch to yellow color text + @echo "NOTE: BIND 10 does not automatically start DNS services when it is run" + @echo " in its default configuration. Please see the Guide for information" + @echo " on how to configure these services to be started automatically." + -@echo -e "\033[m" # switch back to normal + check-valgrind: if HAVE_VALGRIND @VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --track-origins=yes --num-callers=48 --leak-check=full --fullpath-after=" \ diff --git a/README b/README index 4ef941e886..fdc11a1998 100644 --- a/README +++ b/README @@ -1,16 +1,11 @@ -This is the source for the development version of BIND 10. +This is the source for the BIND 10 suite. BIND is the popular implementation of a DNS server, developer interfaces, and DNS tools. BIND 10 is a rewrite of BIND 9 and ISC DHCP. BIND 10 is written in C++ and Python and provides a modular environment for serving, maintaining, and developing DNS and DHCP. -BIND10-devel is new development leading up to the production -BIND 10 release. It contains prototype code and experimental -interfaces. Nevertheless it is ready to use now for testing the -new BIND 10 infrastructure ideas. - This release includes the bind10 master process, b10-msgq message bus, b10-auth authoritative DNS server (with SQLite3 and in-memory backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl @@ -62,3 +57,8 @@ For operating system specific tips see the wiki at: http://bind10.isc.org/wiki/SystemSpecificNotes Please see the wiki and the doc/ directory for various documentation. + +The BIND 10 suite is started by running "bind10". Note that the default +configuration does not start any DNS or DHCP services. Please see the +Guide for information on how to configure these services to be started +automatically. diff --git a/configure.ac b/configure.ac index 9fb813c28b..3f7a269b19 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,109 @@ 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 +# used as the default value for each specific AM_CXXFLAGS: +# AM_CXXFLAGS = $(B10_CXXFLAGS) +# AM_CXXFLAGS += ... # add module specific flags +# We need this so that we can disable some specific compiler warnings per +# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since +# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot +# specify the default warning flags in CXXFLAGS and let specific modules +# "override" the default. + +# This may be used to try linker flags. +AC_DEFUN([BIND10_CXX_TRY_FLAG], [ + AC_MSG_CHECKING([whether $CXX supports $1]) + + bind10_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $1" + + AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])], + [bind10_cxx_flag=yes], [bind10_cxx_flag=no]) + CXXFLAGS="$bind10_save_CXXFLAGS" + + if test "x$bind10_cxx_flag" = "xyes"; then + ifelse([$2], , :, [$2]) + else + ifelse([$3], , :, [$3]) + fi + + AC_MSG_RESULT([$bind10_cxx_flag]) +]) + +# SunStudio compiler requires special compiler options for boost +# (http://blogs.sun.com/sga/entry/boost_mini_howto) +if test "$SUNCXX" = "yes"; then +CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic" +MULTITHREADING_FLAG="-mt" +fi + +# Newer versions of clang++ promotes "unused driver arguments" warnings to +# a fatal error with -Werror, causing build failure. Since we use multiple +# compilers on multiple systems, this can easily happen due to settings for +# non clang++ environments that could be just ignored otherwise. It can also +# happen if clang++ is used via ccache. So, although probably suboptimal, +# we suppress this particular warning. Note that it doesn't weaken checks +# on the source code. +if test "$CLANGPP" = "yes"; then + B10_CXXFLAGS="$B10_CXXFLAGS -Qunused-arguments" +fi + +BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers], + [WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"]) +AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG) + +# gcc specific settings: +if test "X$GXX" = "Xyes"; then +B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare" +case "$host" in +*-solaris*) + MULTITHREADING_FLAG=-pthreads + # In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces + B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces" + ;; +*) + MULTITHREADING_FLAG=-pthread + ;; +esac + +# Don't use -Werror if configured not to +AC_ARG_WITH(werror, + AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]), + [ + case "${withval}" in + yes) with_werror=1 ;; + no) with_werror=0 ;; + *) AC_MSG_ERROR(bad value ${withval} for --with-werror) ;; + esac], + [with_werror=1]) + +werror_ok=0 + +# Certain versions of gcc (g++) have a bug that incorrectly warns about +# the use of anonymous name spaces even if they're closed in a single +# translation unit. For these versions we have to disable -Werror. +if test $with_werror = 1; then + CXXFLAGS_SAVED="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror" + AC_MSG_CHECKING(for in-TU anonymous namespace breakage) + AC_TRY_COMPILE([namespace { class Foo {}; } + namespace isc {class Bar {Foo foo_;};} ],, + [AC_MSG_RESULT(no) + werror_ok=1 + B10_CXXFLAGS="$B10_CXXFLAGS -Werror"], + [AC_MSG_RESULT(yes)]) + CXXFLAGS="$CXXFLAGS_SAVED" +fi + +fi dnl GXX = yes + +AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1) # allow building programs with static link. we need to make it selective # because loadable modules cannot be statically linked. @@ -103,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 @@ -214,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 @@ -256,95 +358,11 @@ fi # TODO: check for _sqlite3.py module -# Compiler dependent settings: define some mandatory CXXFLAGS here. -# We also use a separate variable B10_CXXFLAGS. This will (and should) be -# used as the default value for each specific AM_CXXFLAGS: -# AM_CXXFLAGS = $(B10_CXXFLAGS) -# AM_CXXFLAGS += ... # add module specific flags -# We need this so that we can disable some specific compiler warnings per -# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since -# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot -# specify the default warning flags in CXXFLAGS and let specific modules -# "override" the default. - -# This may be used to try linker flags. -AC_DEFUN([BIND10_CXX_TRY_FLAG], [ - AC_MSG_CHECKING([whether $CXX supports $1]) - - bind10_save_CXXFLAGS="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $1" - - AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])], - [bind10_cxx_flag=yes], [bind10_cxx_flag=no]) - CXXFLAGS="$bind10_save_CXXFLAGS" - - if test "x$bind10_cxx_flag" = "xyes"; then - ifelse([$2], , :, [$2]) - else - ifelse([$3], , :, [$3]) - fi - - AC_MSG_RESULT([$bind10_cxx_flag]) -]) - -# SunStudio compiler requires special compiler options for boost -# (http://blogs.sun.com/sga/entry/boost_mini_howto) -if test "$SUNCXX" = "yes"; then -CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic" -MULTITHREADING_FLAG="-mt" -fi - -BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers], - [WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"]) -AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG) - -# gcc specific settings: -if test "X$GXX" = "Xyes"; then -B10_CXXFLAGS="-Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare" -case "$host" in -*-solaris*) - MULTITHREADING_FLAG=-pthreads - # In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces - B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces" - ;; -*) - MULTITHREADING_FLAG=-pthread - ;; -esac - -# Don't use -Werror if configured not to -AC_ARG_WITH(werror, - AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]), - [ - case "${withval}" in - yes) with_werror=1 ;; - no) with_werror=0 ;; - *) AC_MSG_ERROR(bad value ${withval} for --with-werror) ;; - esac], - [with_werror=1]) - -werror_ok=0 - -# Certain versions of gcc (g++) have a bug that incorrectly warns about -# the use of anonymous name spaces even if they're closed in a single -# translation unit. For these versions we have to disable -Werror. -if test $with_werror = 1; then - CXXFLAGS_SAVED="$CXXFLAGS" - CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror" - AC_MSG_CHECKING(for in-TU anonymous namespace breakage) - AC_TRY_COMPILE([namespace { class Foo {}; } - namespace isc {class Bar {Foo foo_;};} ],, - [AC_MSG_RESULT(no) - werror_ok=1 - B10_CXXFLAGS="$B10_CXXFLAGS -Werror"], - [AC_MSG_RESULT(yes)]) - CXXFLAGS="$CXXFLAGS_SAVED" -fi - +# (g++ only check) # Python 3.2 has an unused parameter in one of its headers. This # has been reported, but not fixed as of yet, so we check if we need # to set -Wno-unused-parameter. -if test $werror_ok = 1; then +if test "X$GXX" = "Xyes" -a $werror_ok = 1; then CPPFLAGS_SAVED="$CPPFLAGS" CPPFLAGS=${PYTHON_INCLUDES} CXXFLAGS_SAVED="$CXXFLAGS" @@ -370,10 +388,6 @@ if test $werror_ok = 1; then CPPFLAGS="$CPPFLAGS_SAVED" fi -fi dnl GXX = yes - -AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1) - # produce PIC unless we disable shared libraries. need this for python bindings. if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then B10_CXXFLAGS="$B10_CXXFLAGS -fPIC" @@ -671,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) @@ -691,7 +705,6 @@ fi AC_SUBST(BOTAN_LDFLAGS) AC_SUBST(BOTAN_LIBS) AC_SUBST(BOTAN_INCLUDES) - # Even though chances are high we already performed a real compilation check # in the search for the right (pkg)config data, we try again here, to # be sure. @@ -719,6 +732,60 @@ AC_LINK_IFELSE( CPPFLAGS=$CPPFLAGS_SAVED LIBS=$LIBS_SAVED +# Check for MySql. The path to the mysql_config program is given with +# the --with-mysql-config (default to /usr/bin/mysql-config). By default, +# the software is not built with MySQL support enabled. +mysql_config="no" +AC_ARG_WITH([dhcp-mysql], + AC_HELP_STRING([--with-dhcp-mysql=PATH], + [path to the MySQL 'mysql_config' script (MySQL is used for the DHCP database)]), + [mysql_config="$withval"]) + +if test "${mysql_config}" = "yes" ; then + MYSQL_CONFIG="/usr/bin/mysql_config" +elif test "${mysql_config}" != "no" ; then + MYSQL_CONFIG="${withval}" +fi + +if test "$MYSQL_CONFIG" != "" ; then + if test -d "$MYSQL_CONFIG" -o ! -x "$MYSQL_CONFIG" ; then + AC_MSG_ERROR([--with-dhcp-mysql should point to a mysql_config program]) + fi + + MYSQL_CPPFLAGS=`$MYSQL_CONFIG --cflags` + MYSQL_LIBS=`$MYSQL_CONFIG --libs` + + AC_SUBST(MYSQL_CPPFLAGS) + AC_SUBST(MYSQL_LIBS) + + # Check that a simple program using MySQL functions can compile and link. + CPPFLAGS_SAVED="$CPPFLAGS" + LIBS_SAVED="$LIBS" + + CPPFLAGS="$MYSQL_CPPFLAGS $CPPFLAGS" + LIBS="$MYSQL_LIBS $LIBS" + + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ], + [MYSQL mysql_handle; + (void) mysql_init(&mysql_handle); + ])], + [AC_MSG_RESULT([checking for MySQL headers and library... yes])], + [AC_MSG_RESULT([checking for MySQL headers and library... no]) + AC_MSG_ERROR([Needs MySQL library])] + ) + + CPPFLAGS=$CPPFLAGS_SAVED + LIBS=$LIBS_SAVED + + # Note that MYSQL is present in the config.h file + AC_DEFINE([HAVE_MYSQL], [1], [MySQL is present]) +fi + +# ... and at the shell level, so Makefile.am can take action depending on this. +AM_CONDITIONAL(HAVE_MYSQL, test "$MYSQL_CONFIG" != "") + + # Check for log4cplus log4cplus_path="yes" AC_ARG_WITH([log4cplus], @@ -836,11 +903,12 @@ AC_SUBST(MULTITHREADING_FLAG) # GTEST_LDFLAGS= GTEST_LDADD= -# TODO: set DISTCHECK_GTEST_CONFIGURE_FLAG for --with-gtest too DISTCHECK_GTEST_CONFIGURE_FLAG= if test "x$enable_gtest" = "xyes" ; then + DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest=$gtest_path" + if test -n "$with_gtest_source" ; then if test "x$GTEST_SOURCE" = "xyes" ; then @@ -1055,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" @@ -1093,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 @@ -1188,6 +1264,8 @@ AC_CONFIG_FILES([Makefile src/lib/dns/benchmarks/Makefile src/lib/dhcp/Makefile src/lib/dhcp/tests/Makefile + src/lib/dhcpsrv/Makefile + src/lib/dhcpsrv/tests/Makefile src/lib/exceptions/Makefile src/lib/exceptions/tests/Makefile src/lib/datasrc/Makefile @@ -1231,7 +1309,7 @@ AC_CONFIG_FILES([Makefile tests/tools/badpacket/tests/Makefile tests/tools/perfdhcp/Makefile tests/tools/perfdhcp/tests/Makefile - tests/tools/perfdhcp/templates/Makefile + tests/tools/perfdhcp/tests/testdata/Makefile dns++.pc ]) AC_OUTPUT([doc/version.ent @@ -1261,6 +1339,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 @@ -1297,6 +1376,7 @@ AC_OUTPUT([doc/version.ent src/lib/log/tests/console_test.sh src/lib/log/tests/destination_test.sh src/lib/log/tests/init_logger_test.sh + src/lib/log/tests/buffer_logger_test.sh src/lib/log/tests/local_file_test.sh src/lib/log/tests/logger_lock_test.sh src/lib/log/tests/severity_test.sh @@ -1339,6 +1419,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 @@ -1397,11 +1478,24 @@ dnl includes too ${LOG4CPLUS_LIBS} SQLite: $SQLITE_CFLAGS $SQLITE_LIBS +END + +# Avoid confusion on DNS/DHCP and only mention MySQL if it +# were specified on the command line. +if test "$MYSQL_CPPFLAGS" != "" ; then +cat >> config.report << END + MySQL: $MYSQL_CPPFLAGS + $MYSQL_LIBS +END +fi + +cat >> config.report << END 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 f6b9fa0094..fbd082f745 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -580,8 +580,8 @@ 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 \ - ../tests/tools/perfdhcp devel + ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/lib/dhcpsrv \ + ../src/bin/dhcp4 ../tests/tools/perfdhcp devel # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/doc/devel/02-dhcp.dox b/doc/devel/02-dhcp.dox index 2a465e7c5f..5c59daafd2 100644 --- a/doc/devel/02-dhcp.dox +++ b/doc/devel/02-dhcp.dox @@ -1,74 +1,60 @@ /** - * @page libdhcp libdhcp++ + * @page dhcpv4 DHCPv4 Server Component * - * @section libdhcpIntro Libdhcp++ Library Introduction + * BIND10 offers DHCPv4 server implementation. It is implemented as + * b10-dhcp4 component. Its primary code is located in + * isc::dhcp::Dhcpv4Srv class. It uses \ref libdhcp extensively, + * especially isc::dhcp::Pkt4, isc::dhcp::Option and + * isc::dhcp::IfaceMgr classes. Currently this code offers skeleton + * functionality, i.e. it is able to receive and process incoming + * requests and trasmit responses. However, it does not have database + * management, so it returns only one, hardcoded lease to whoever asks + * for it. * - * 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: + * DHCPv4 server component does not support direct traffic (relayed + * only), as support for transmission to hosts without IPv4 address + * assigned is not implemented in IfaceMgr yet. * - * - isc::dhcp::Pkt4 - represents DHCPv4 packet. - * - isc::dhcp::Pkt6 - represents DHCPv6 packet. + * DHCPv4 server component does not use BIND10 logging yet. * - * 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. + * @section dhcpv4Session BIND10 message queue integration * - * 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). + * DHCPv4 server component is now integrated with BIND10 message queue. + * The integration is performed by establishSession() and disconnectSession() + * functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined + * in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv + * class that establishes connection with msgq and install necessary handlers + * for receiving commands and configuration updates. It is derived from + * a base isc::dhcp::Dhcpv4Srv class that implements DHCPv4 server functionality, + * without any controlling mechanisms. * - * 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. + * ControlledDhcpv4Srv instantiates several components to make management + * session possible. In particular, isc::cc::Session cc_session + * object uses ASIO for establishing connection. It registers its socket + * in isc::asiolink::IOService io_service object. Typically, other components + * (e.g. auth or resolver) that use ASIO for their communication, register their + * other sockets in the + * same io_service and then just call io_service.run() method that does + * not return, until one of the callback decides that it is time to shut down + * the whole component cal calls io_service.stop(). DHCPv4 works in a + * different way. It does receive messages using select() + * (see isc::dhcp::IfaceMgr::receive4()), which is incompatible with ASIO. + * To solve this problem, socket descriptor is extracted from cc_session + * object and is passed to IfaceMgr by using isc::dhcp::IfaceMgr::set_session_socket(). + * IfaceMgr then uses this socket in its select() call. If there is some + * data to be read, it calls registered callback that is supposed to + * read and process incoming data. * - * 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. + * This somewhat complicated approach is needed for a simple reason. In + * embedded deployments there will be no message queue. Not referring directly + * to anything related to message queue in isc::dhcp::Dhcpv4Srv and + * isc::dhcp::IfaceMgr classes brings in two benefits. First, the can + * be used with and without message queue. Second benefit is related to the + * first one: \ref libdhcp is supposed to be simple and robust and not require + * many dependencies. One notable example of a use case that benefits from + * this approach is a perfdhcp tool. Finally, the idea is that it should be + * possible to instantiate Dhcpv4Srv object directly, thus getting a server + * that does not support msgq. That is useful for embedded environments. + * It may also be useful in validation. * - * @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 de5d49c7a5..407723e3d9 100644 --- a/doc/devel/mainpage.dox +++ b/doc/devel/mainpage.dox @@ -31,6 +31,11 @@ * - @subpage libdhcp * - @subpage libdhcpIntro * - @subpage libdhcpIfaceMgr + * - @subpage libdhcpsrv + * - @subpage leasemgr + * - @subpage cfgmgr + * - @subpage allocengine + * - @subpage dhcp-database-backends * - @subpage perfdhcpInternals * * @section misc Miscellaneous topics diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml index 704f155c35..61a9ee4396 100644 --- a/doc/guide/bind10-guide.xml +++ b/doc/guide/bind10-guide.xml @@ -84,8 +84,7 @@ - This guide covers the experimental prototype of - BIND 10 version &__VERSION__;. + This guide covers BIND 10 version &__VERSION__;.
@@ -168,8 +167,8 @@ However, these processes are started, stopped, and maintained by a single command, bind10. This command starts a master process which will start other - processes as needed. - The processes started by the bind10 + required processes and other processes when configured. + The processes that may be started by the bind10 command have names starting with "b10-", including: @@ -285,8 +284,7 @@ - These are ran by bind10 - and do not need to be manually started independently. + These do not need to be manually started independently.
@@ -367,6 +365,106 @@ var/ + + Quick start + + + This quickly covers the standard steps for installing + and deploying BIND 10. + For further details, full customizations, and troubleshooting, + see the respective chapters in the BIND 10 guide. + + +
+ Quick start guide for authoritative DNS service + + + + + + Install required run-time and build dependencies. + + + + + + Download the BIND 10 source tar file from + . + + + + + Extract the tar file: + $ gzcat bind10-VERSION.tar.gz | tar -xvf - + + + + + Go into the source and run configure: + $ cd bind10-VERSION + $ ./configure + + + + + Build it: + $ make + + + + + Install it as root (to default /usr/local): + $ make install + + + + + Start the server (as root): + $ /usr/local/sbin/bind10 + + + + + DNS and DHCP components are not started in the default + configuration. In another console, enable the authoritative + DNS service (by using the bindctl utility + to configure the b10-auth component to + run): $ bindctl + (Login with the provided default username and password.) + +> config add Boss/components b10-auth +> config set Boss/components/b10-auth/special auth +> config set Boss/components/b10-auth/kind needed +> config commit +> quit + + + + + + Test it; for example: + $ dig @127.0.0.1 -c CH -t TXT version.bind + + + + + Load desired zone file(s), for example: + $ b10-loadzone your.zone.example.org + + + + + + Test the new zone. + + + + + +
+ +
+ Installation @@ -374,7 +472,7 @@ var/ Packages - Some operating systems or softare package vendors may + Some operating systems or software package vendors may provide ready-to-use, pre-built software packages for the BIND 10 suite. Installing a pre-built package means you do not need to @@ -514,90 +612,6 @@ as a dependency earlier --> -
- Quick start - - - This quickly covers the standard steps for installing - and deploying BIND 10 as an authoritative name server using - its defaults. For troubleshooting, full customizations and further - details, see the respective chapters in the BIND 10 guide. - - - - - To quickly get started with BIND 10, follow these steps. - - - - - - - Install required run-time and build dependencies. - - - - - - Download the BIND 10 source tar file from - . - - - - - Extract the tar file: - $ gzcat bind10-VERSION.tar.gz | tar -xvf - - - - - - Go into the source and run configure: - $ cd bind10-VERSION - $ ./configure - - - - - Build it: - $ make - - - - - Install it (to default /usr/local): - $ make install - - - - - Start the server: - $ /usr/local/sbin/bind10 - - - - - - Test it; for example: - $ dig @127.0.0.1 -c CH -t TXT authors.bind - - - - - Load desired zone file(s), for example: - $ b10-loadzone your.zone.example.org - - - - - - Test the new zone. - - - - - -
-
Installation from source @@ -674,7 +688,7 @@ as a dependency earlier -->
-
+
Configure before the build BIND 10 uses the GNU Build System to discover build environment @@ -745,6 +759,9 @@ as a dependency earlier --> dependencies. + + For notes on configuring and building DHCPv6 with MySQL see . +
@@ -767,7 +784,24 @@ as a dependency earlier --> The install step may require superuser privileges. - + + If required, run ldconfig as root with + /usr/local/lib (or with ${prefix}/lib if + configured with --prefix) in + /etc/ld.so.conf (or the relevant linker + cache configuration file for your OS): + $ ldconfig + + + + If you do not run ldconfig where it is + required, you may see errors like the following: + + program: error while loading shared libraries: libb10-something.so.1: + cannot open shared object file: No such file or directory + + +
@@ -820,16 +854,26 @@ as a dependency earlier --> b10-cmdctl for administration tools to communicate with the system, and b10-stats for statistics collection. + The DNS and DHCP servers are not started by default. + The configuration of components to start is covered in + .
Starting BIND 10 - To start the BIND 10 service, simply run bind10. + To start the BIND 10 service, simply run bind10 + as root. + It will run in the foreground and your shell prompt will not + be available. It will output various log messages as it starts up + and is used. Run it with the switch to get additional debugging or diagnostic output. - + + + + @@ -841,156 +885,6 @@ as a dependency earlier -->
-
- Configuration to start processes - - - The processes to be used can be configured for - bind10 to start, with the exception - of the required b10-sockcreator, - b10-msgq and b10-cfgmgr - components. - The configuration is in the Boss/components - section. Each element represents one component, which is - an abstraction of a process. - - - - To add a process to the set, let's say the resolver (which - is not started by default), you would do this: - > config add Boss/components b10-resolver -> config set Boss/components/b10-resolver/special resolver -> config set Boss/components/b10-resolver/kind needed -> config set Boss/components/b10-resolver/priority 10 -> config commit - - - Now, what it means. We add an entry called - b10-resolver. It is both a name used to - reference this component in the configuration and the name - of the process to start. Then we set some parameters on - how to start it. - - - - The special setting is for components - that need some kind of special care during startup or - shutdown. Unless specified, the component is started in a - usual way. This is the list of components that need to be - started in a special way, with the value of special used - for them: - - - Special startup components - - - - - ComponentSpecialDescription - - b10-authauthAuthoritative DNS server - b10-resolverresolverDNS resolver - b10-cmdctlcmdctlCommand control (remote control interface) - - - -
-
- - - The kind specifies how a failure of the - component should be handled. If it is set to - dispensable (the default unless you set - something else), it will get started again if it fails. If - it is set to needed and it fails at startup, - the whole bind10 shuts down and exits - with an error exit code. But if it fails some time later, it - is just started again. If you set it to core, - you indicate that the system is not usable without the - component and if such component fails, the system shuts - down no matter when the failure happened. This is the - behaviour of the core components (the ones you can't turn - off), but you can declare any other components as core as - well if you wish (but you can turn these off, they just - can't fail). - - - - The priority defines order in which the - components should start. The ones with higher numbers are - started sooner than the ones with lower ones. If you don't - set it, 0 (zero) is used as the priority. Usually, leaving - it at the default is enough. - - - - There are other parameters we didn't use in our example. - One of them is address. It is the address - used by the component on the b10-msgq - message bus. The special components already know their - address, but the usual ones don't. The address is by - convention the thing after b10-, with - the first letter capitalized (eg. b10-stats - would have Stats as its address). - - - - - - - - - The last one is process. It is the name - of the process to be started. It defaults to the name of - the component if not set, but you can use this to override - it. (The special components also already know their - executable name.) - - - - - - - The configuration is quite powerful, but that includes - a lot of space for mistakes. You could turn off the - b10-cmdctl, but then you couldn't - change it back the usual way, as it would require it to - be running (you would have to find and edit the configuration - directly). Also, some modules might have dependencies: - b10-stats-httpd needs - b10-stats, b10-xfrout - needs b10-auth to be running, etc. - - - - - - In short, you should think twice before disabling something here. - - - - It is possible to start some components multiple times (currently - b10-auth and b10-resolver). - You might want to do that to gain more performance (each one uses only - single core). Just put multiple entries under different names, like - this, with the same config: - > config add Boss/components b10-resolver-2 -> config set Boss/components/b10-resolver-2/special resolver -> config set Boss/components/b10-resolver-2/kind needed -> config commit - - - However, this is work in progress and the support is not yet complete. - For example, each resolver will have its own cache, each authoritative - server will keep its own copy of in-memory data and there could be - problems with locking the sqlite database, if used. The configuration - might be changed to something more convenient in future. - Other components don't expect such a situation, so it would - probably not do what you want. Such support is yet to be - implemented. - -
@@ -1046,7 +940,7 @@ address, but the usual ones don't." mean? --> - The development prototype release only provides + The current release only provides bindctl as a user interface to b10-cmdctl. Upcoming releases will provide another interactive command-line @@ -1268,13 +1162,21 @@ TODO Control and configure user interface - For this development prototype release, bindctl + For the current release, bindctl is the only user interface. It is expected that upcoming releases will provide another interactive command-line interface and a web-based interface for controlling and configuring BIND 10. + + bindctl has an internal command history, as + well as tab-completion for most of the commands and arguments. + However, these are only enabled if the python readline module + is available on the system. If not, neither of these + features will be supported. + + The bindctl tool provides an interactive prompt for configuring, controlling, and querying the BIND 10 @@ -1284,22 +1186,590 @@ TODO communicate to any other components directly. - +
+ bindctl command-line options + + + -a <address>, --address=<address> + + + IP address that BIND 10's b10-cmdctl + module is listening on. By default, this is 127.0.0.1. + + + + + -c <certificate file>, --certificate-chain=<certificate file> + + + PEM-formatted server certificate file. When this option is + given, bindctl will verify the server + certificate using the given file as the root of the + certificate chain. If not specified, bindctl + does not validate the certificate. + + + + + --csv-file-dir=<csv file> + + + bindctl stores the username and + password for logging in in a file called + default_user.csv; + this option specifies the directory where this file is + stored and read from. When not specified, + ~/.bind10/ is used. + Currently, this file contains an unencrypted password. + + + + + -h, --help + + + Shows a short overview of the command-line options of + bindctl, and exits. + + + + + --version + + + Shows the version of bindctl, and exits. + + + + + -p <port number>, --port=<port number> + + + Port number that BIND 10's b10-cmdctl + module is listening on. By default, this is port 8080. + + + + +
- - Configuration changes are actually commands to - b10-cfgmgr. So when bindctl - sends a configuration, it is sent to b10-cmdctl - (over a HTTPS connection); then b10-cmdctl - sends the command (over a b10-msgq command - channel) to b10-cfgmgr which then stores - the details and relays (over a b10-msgq command - channel) the configuration on to the specified module. - +
+ General syntax of bindctl commands + The bindctl tool is an interactive + command-line tool, with dynamic commands depending on the + BIND 10 modules that are running. There are a number of + fixed commands that have no module and that are always + available. - - + The general syntax of a command is + <module> <command> [argument(s)] + + For example, the Boss module has a 'shutdown' command to shut down + BIND 10, with an optional argument 'help': + + > Boss shutdown help +Command shutdown (Shut down BIND 10) + help (Get help for command) +This command has no parameters + + There are no mandatory arguments, only the optional 'help'. +
+ +
+ Bindctl help + help is both a command and an option that is available to all other commands. When run as a command directly, it shows the available modules. + > help +usage: <module name> <command name> [param1 = value1 [, param2 = value2]] +Type Tab character to get the hint of module/command/parameters. +Type "help(? h)" for help on bindctl. +Type "<module_name> help" for help on the specific module. +Type "<module_name> <command_name> help" for help on the specific command. + +Available module names: +(list of modules) + + + When 'help' is used as a command to a module, it shows the supported commands for the module; for example: + > Boss help +Module Boss Master process +Available commands: + help Get help for module. + shutdown Shut down BIND 10 + ping Ping the boss process + show_processes + List the running BIND 10 processes + + + And when added to a module command, it shows the description and parameters of that specific command; for example: + > Auth loadzone help +Command loadzone ((Re)load a specified zone) + help (Get help for command) +Parameters: + class (string, optional) + origin (string, mandatory) + + +
+ +
+ Command arguments + + Commands can have arguments, which can be either optional or + mandatory. They can be specified by name + (e.g. <command> <argument name>=<argument value>), or positionally, + (e.g. <command> <argument value 1> <argument value 2>). + + + <command> help + shows the arguments a command supports and which of those are + mandatory, and in which order the arguments are expected if + positional arguments are used. + + + For example, the loadzone command of the Auth + module, as shown in the last example of the previous section, has + two arguments, one of which is optional. The positional arguments in + this case are class first and origin second; for example: + > Auth loadzone IN example.com. + But since the class is optional (defaulting to IN), leaving it out + works as well: + > Auth loadzone example.com. + + + The arguments can also be provided with their names, in which + case the order does not matter: + > Auth loadzone origin="example.com." class="IN" + +
+ +
+ Module commands + Each module has its own set of commands (if any), which will only be + available if the module is running. For instance, the + Auth module has a loadzone command. + The commands a module provides are documented in + this guide in the section of that module or in the module's + corresponding manual page. +
+ +
+ Configuration commands + Configuration commands are used to view and change the configuration + of BIND 10 and its modules. Module configuration is only shown if + that module is running, but similar to commands, there are a number + of top-level configuration items that are always available (for + instance tsig_keys and + data_sources). + + Configuration changes (set, unset, add and remove) are done locally + first, and have no immediate effect. The changes can be viewed with + config diff, and either reverted + (config revert), or committed + (config commit). + In the latter case, all local changes are submitted + to the configuration manager, which verifies them, and if they are + accepted, applied and saved in persistent storage. + + When identifying items in configuration commands, the format is + Module/example/item + Sub-elements of names, lists and sets (see ) are separated with the '/' + character, and list indices are identified with [<index>]; for example: + + Module/example/list[2]/foo + +
+ List of configuration commands + The following configuration commands are available: + + + show [all] [item name] + + + Shows the current configuration of the given item. If 'all' + is given, it will recurse through the entire set, and show + every nested value. + + + + + show_json [item name] + + + Shows the full configuration of the given item in JSON format. + + + + + add <item name> [value] + + + Add an entry to configuration list or a named set (see ). + When adding to a list, the command has one optional + argument, a value to add to the list. The value must + be in correct JSON and complete. When adding to a + named set, it has one mandatory parameter (the name to + add), and an optional parameter value, similar to when + adding to a list. In either case, when no value is + given, an entry will be constructed with default + values. + + + + + remove + + + Remove an item from a configuration list or a named set. + When removing an item for a list, either the index needs to + be specified, or the complete value of the element to remove + must be specified (in JSON format). + + + + + set <item name> <value> + + + Directly set the value of the given item to the given value. + + + + + unset <item name> + + + Remove any user-specified value for the given item. + + + + + diff + + + Show all current local changes that have not been + committed yet. + + + + + revert + + + Revert all local changes without committing them. + + + + + commit + + + Send all local changes to the configuration manager, which + will validate them, and apply them if validation succeeds. + + + + + go + + + Go to a specific configuration part, similar to the 'cd' + command in a shell. + There are a number of problems with the current + implementation of go within bindctl, + and we recommend not using it for general cases. + + + + +
+ +
+ Configuration data types + Configuration data can be of different types, which can be modified + in ways that depend on the types. There are a few syntax + restrictions on these types, but only basic ones. Modules may impose + additional restrictions on the values of elements. + + + integer + + + A basic integer; can be set directly with config set, to any integer value. + + + + + real + + + A basic floating point number; can be set directly with config set, to any floating point value. + + + + + boolean + + + A basic boolean value; can be set directly with config set, to either true or false. + + + + + string + + + A basic string value; can be set directly with config set, so any string. Double quotation marks are optional. + + + + + null + + + This is a special type representing 'no value at all'; usable in compound structures that have optional elements that are not set. + + + + + + maps + + + Maps are (pre-defined) compound collections of other + elements of any other type. They are not usually + modified directly, but their elements are. Every + top-level element for a module is a map containing + the configuration values for that map, which can + themselves be maps again. For instance, the Auth + module configuration is a map containing the + elements 'listen_on' (list) and 'tcp_recv_timeout' + (integer). When changing one of its values, they can + be modified directly with config set + Auth/tcp_recv_timeout 3000. + + + Some map entries are optional. If they are, and + currently have a value, the value can be unset by + using either config unset + <item name> + or config set + <item name> + null. + + + + Maps can be modified as a whole, + but using the full JSON representation of + the entire map to set. + + Since this involves a lot of text, this is usually + not recommended. + + + Another example is the Logging virtual module, which + is, like any module, a map, but it only contains one + element: a list of loggers. Normally, an + administrator would only modify that list (or its + elements) directly, but it is possible to set the + entire map in one command; for example: + config set Logging { "loggers": [] } + + + + + + list + + + A list is a compound list of other elements of the + same type. Elements can be added with config + add <list name> [value], and removed with + config remove <list name> [value] or + config remove <list name><index>. + The index is of the form square bracket, number, + square bracket (e.g. + [0]), and it immediately follows + the list name (there is no separator or space + between them). List indices start with 0 for the + first element. + + + For addition, if the value is omitted, an entry with + default values will be added. For removal, either + the index or the full value (in JSON format) needs + to be specified. + + + Lists can also be used with + config set, + but like maps, only by specifying the + entire list value in JSON format. + + + For example, this command shows the port number used for the second element of the list listen_on in the Auth module: + config show Auth/listen_on[1]/port + + + + + + named set + + + Named sets are similar to lists, in that they are + sets of elements of the same type, but they are not + indexed by numbers, but by strings. + + + Values can be added with + config add <item name> <string> [value] + where 'string' is the name of the element. If 'value' + is ommitted, default values will be used. Elements + can be removed with config remove + <item + name> <string> + + + Elements in a named set can be addressed similarly + to maps. + + + For example, the Boss/components + elements is a named set; + adding, showing, and then removing an element + can be done with the following three commands (note + the '/'-character versus the space before + 'example_module'): + + + config add Boss/components example_module + + + config show Boss/components/example_module + + + config remove Boss/components example_module + + + + + any + + + The 'any' type is a special type that can have any + form. Apart from that, it must consist of elements as + described in this chapter, there is no restriction + on which element types are used. This type is used + in places where different data formats could be + used. Element modification commands depend on the + actual type of the value. For instance, if the value + of an 'any' element is a list, config add + and config remove work + as for other lists. + + + + +
+
+ +
+ The execute command + The execute command executes a set of commands, + either from a file + or from a pre-defined set. Currently, the only predefined set is + init_authoritative_server, which adds + b10-auth, b10-xfrin, and + b10-xfrout to the set of components to be + started by BIND 10. This + pre-defined set does not commit the changes, so these modules do not + show up for commands or configuration until the user enters + config commit after + execute init_authoritative_server. For example: + + > execute init_authoritative_server + + > execute file /tmp/example_commands + + The optional argument show displays the exact set of + commands that would be executed; for example: + + > execute init_authoritative_server show +!echo adding Authoritative server component +config add /Boss/components b10-auth +config set /Boss/components/b10-auth/kind needed +config set /Boss/components/b10-auth/special auth +!echo adding Xfrin component +config add /Boss/components b10-xfrin +config set /Boss/components/b10-xfrin/address Xfrin +config set /Boss/components/b10-xfrin/kind dispensable +!echo adding Xfrout component +config add /Boss/components b10-xfrout +config set /Boss/components/b10-xfrout/address Xfrout +config set /Boss/components/b10-xfrout/kind dispensable +!echo adding Zone Manager component +config add /Boss/components b10-zonemgr +config set /Boss/components/b10-zonemgr/address Zonemgr +config set /Boss/components/b10-zonemgr/kind dispensable +!echo Components added. Please enter "config commit" to +!echo finalize initial setup and run the components. + + + The optional show argument may also be used when + executing a script from a file; for example: + + > execute file /tmp/example_commands show + +
+ Execute directives + Within sets of commands to be run with the execute + command, a number of directives are supported: + + + !echo <string> + + + Prints the given string to bindctl's + output. + + + + + !verbose on + + + Enables verbose mode; all following commands that are to + be executed are also printed. + + + + + !verbose off + + + Disables verbose mode; following commands that are to + be executed are no longer printed. + + + + +
+ +
+ Notes on execute scripts + Within scripts, you can add or remove modules with the normal + configuration commands for Boss/components. + However, as module + configuration and commands do not show up until the module is + running, it is currently not possible to add a module and set + its configuration in one script. This will be addressed in the + future, but for now the only option is to add and configure + modules in separate commands and execute scripts. +
+
@@ -1371,10 +1841,8 @@ TODO The key ring lives in the configuration in "tsig_keys/keys". Most of the system uses the keys from there — ACLs, authoritative server to - sign responses to signed queries, and b10-xfrout - to sign transfers. The b10-xfrin uses its own - configuration for keys, but that will be fixed in Trac ticket - #1351. + sign responses to signed queries, and b10-xfrin + and b10-xfrout to sign transfers. @@ -1599,6 +2067,185 @@ AND_MATCH := "ALL": [ RULE_RAW, RULE_RAW, ... ]
+ + bind10 Control and Configuration + + + This chapter explains how to control and configure the + bind10 parent. + The startup of this resident process that runs the BIND 10 + daemons is covered in . + + +
+ Stopping bind10 + + The BIND 10 suite may be shut down by stopping the + parent bind10 process. This may be done + by running the Boss shutdown command + at the bindctl prompt. + +
+ +
+ Configuration to start processes + + + The processes to be used can be configured for + bind10 to start, with the exception + of the required b10-sockcreator, + b10-msgq and b10-cfgmgr + components. + The configuration is in the Boss/components + section. Each element represents one component, which is + an abstraction of a process. + + + + To add a process to the set, let's say the resolver (which + is not started by default), you would do this: + > config add Boss/components b10-resolver +> config set Boss/components/b10-resolver/special resolver +> config set Boss/components/b10-resolver/kind needed +> config set Boss/components/b10-resolver/priority 10 +> config commit + + + Now, what it means. We add an entry called + b10-resolver. It is both a name used to + reference this component in the configuration and the name + of the process to start. Then we set some parameters on + how to start it. + + + + The special setting is for components + that need some kind of special care during startup or + shutdown. Unless specified, the component is started in a + usual way. This is the list of components that need to be + started in a special way, with the value of special used + for them: + + + Special startup components + + + + + ComponentSpecialDescription + + b10-authauthAuthoritative DNS server + b10-resolverresolverDNS resolver + b10-cmdctlcmdctlCommand control (remote control interface) + + + +
+
+ + + The kind specifies how a failure of the + component should be handled. If it is set to + dispensable (the default unless you set + something else), it will get started again if it fails. If + it is set to needed and it fails at startup, + the whole bind10 shuts down and exits + with an error exit code. But if it fails some time later, it + is just started again. If you set it to core, + you indicate that the system is not usable without the + component and if such component fails, the system shuts + down no matter when the failure happened. This is the + behavior of the core components (the ones you can't turn + off), but you can declare any other components as core as + well if you wish (but you can turn these off, they just + can't fail). + + + + The priority defines order in which the + components should start. The ones with higher numbers are + started sooner than the ones with lower ones. If you don't + set it, 0 (zero) is used as the priority. Usually, leaving + it at the default is enough. + + + + There are other parameters we didn't use in our example. + One of them is address. It is the address + used by the component on the b10-msgq + message bus. The special components already know their + address, but the usual ones don't. The address is by + convention the thing after b10-, with + the first letter capitalized (eg. b10-stats + would have Stats as its address). + + + + + + + + + The last one is process. It is the name + of the process to be started. It defaults to the name of + the component if not set, but you can use this to override + it. (The special components also already know their + executable name.) + + + + + + + The configuration is quite powerful, but that includes + a lot of space for mistakes. You could turn off the + b10-cmdctl, but then you couldn't + change it back the usual way, as it would require it to + be running (you would have to find and edit the configuration + directly). Also, some modules might have dependencies: + b10-stats-httpd needs + b10-stats, b10-xfrout + needs b10-auth to be running, etc. + + + + + + In short, you should think twice before disabling something here. + + + + It is possible to start some components multiple times (currently + b10-auth and b10-resolver). + You might want to do that to gain more performance (each one uses only + single core). Just put multiple entries under different names, like + this, with the same config: + > config add Boss/components b10-resolver-2 +> config set Boss/components/b10-resolver-2/special resolver +> config set Boss/components/b10-resolver-2/kind needed +> config commit + + + However, this is work in progress and the support is not yet complete. + For example, each resolver will have its own cache, each authoritative + server will keep its own copy of in-memory data and there could be + problems with locking the sqlite database, if used. The configuration + might be changed to something more convenient in future. + Other components don't expect such a situation, so it would + probably not do what you want. Such support is yet to be + implemented. + + + + The running processes started by bind10 + may be listed by running Boss show_processes + using bindctl. + + +
+
+ Authoritative Server @@ -1660,8 +2307,7 @@ can use various data source backends. By default, this is empty. - In this development version, currently this is only used for the - memory data source. + Currently this is only used for the memory data source. Only the IN class is supported at this time. By default, the memory data source is disabled. Also, currently the zone file must be canonical such as @@ -1739,7 +2385,7 @@ can use various data source backends. (it defaults to memory). - In this development version, currently this only supports the + Currently this only supports the IN class and the memory data source. @@ -1797,7 +2443,7 @@ can use various data source backends. - In the development prototype release, b10-auth + In the current release, b10-auth can serve data from a SQLite3 data source backend and from master files. Upcoming versions will be able to use multiple different @@ -1997,7 +2643,7 @@ can use various data source backends. - In the development prototype release, only the SQLite3 back + In the current release, only the SQLite3 back end is used by b10-loadzone. By default, it stores the zone data in /usr/local/var/bind10-devel/zone.sqlite3 @@ -2073,13 +2719,22 @@ TODO +
+ TSIG + If you want to use TSIG for incoming transfers, a system wide TSIG + key ring must be configured (see ). + To specify a key to use, set tsig_key value to the name of the key + to use from the key ring. +> config set Xfrin/zones[0]/tsig_key "" +
+
Enabling IXFR As noted above, b10-xfrin uses AXFR for zone transfers by default. To enable IXFR for zone transfers - for a particular zone, set the use_ixfr - configuration parameter to true. + for a particular zone, set the use_ixfr + configuration parameter to true. In the above example of configuration sequence, you'll need to add the following before performing commit: > config set Xfrin/zones[0]/use_ixfr true @@ -2338,7 +2993,8 @@ what is XfroutClient xfr_client?? underlying data source storing the zone data be writable. In the current implementation this means the zone must be stored in an SQLite3-based data source. - Also, in this development version, the b10-ddns + + Also, in this current version, the b10-ddns component configures itself with the data source referring to the database_file configuration parameter of b10-auth. @@ -2694,20 +3350,20 @@ then change those defaults with config set Resolver/forward_addresses[0]/address - As of December 2011, both DHCPv4 and DHCPv6 components are - skeleton servers. That means that while they are capable of - performing DHCP configuration, they are not fully functional - yet. In particular, neither has functional lease - databases. This means that they will assign the same, fixed, + As of November 2012, the DHCPv4 component is a + skeleton server. That means that while it is capable of + performing DHCP configuration, it is not fully functional. + In particular, it does not have a functional lease + database. This means that they will assign the same, fixed, hardcoded addresses to any client that will ask. See and for + linkend="dhcp4-limit"/> for a detailed description.
DHCPv4 Server Usage - BIND 10 provides the DHCPv4 server component since December + BIND 10 has provided the DHCPv4 server component since December 2011. It is a skeleton server and can be described as an early prototype that is not fully functional yet. It is mature enough to conduct first tests in lab environment, but it has @@ -2724,10 +3380,8 @@ then change those defaults with config set Resolver/forward_addresses[0]/address > config commit - To shutdown running b10-dhcp4, please use the + To stop running b10-dhcp4, please use the following command: - > Dhcp4 shutdown - or > config remove Boss/components b10-dhcp4 > config commit @@ -2913,8 +3567,8 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1"; b10-dhcp4 does not support BOOTP. That is a design choice. This limitation is permanent. If you have legacy nodes that can't use DHCP and - require BOOTP support, please use latest version of ISC DHCP - . + require BOOTP support, please use the latest version of ISC DHCP + via . Interface detection is currently working on Linux @@ -2945,8 +3599,8 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1"; DHCPv6 Server - Dynamic Host Configuration Protocol for IPv6 (DHCPv6) is - specified in RFC3315. BIND 10 provides DHCPv6 server implementation + The Dynamic Host Configuration Protocol for IPv6 (DHCPv6) is + specified in RFC3315. BIND 10 provides a DHCPv6 server implementation that is described in this chapter. For a description of the DHCPv4 server implementation, see . @@ -2958,32 +3612,92 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1"; url="https://lists.isc.org/mailman/listinfo/bind10-dev">BIND 10 developers mailing list. - The DHCPv4 and DHCPv6 components in BIND 10 architecture are - internally code named Kea. - - As of December 2011, both DHCPv4 and DHCPv6 components are - skeleton servers. That means that while they are capable of - performing DHCP configuration, they are not fully functional - yet. In particular, neither has functional lease - databases. This means that they will assign the same, fixed, - hardcoded addresses to any client that will ask. See and for - detailed description. + As of November 2012, the DHCPv6 component is partially functioning, + having the following capabilities: + + + DHCPv6 server able to allocate leases (but not renew them). + + + Some configuration available through the BIND 10 configuration mechanism. + + + Lease storage in a MySQL database. + + +
+ DHCPv6 Server Build and Installation + + DHCPv6 is part of the BIND 10 suite of programs and is built as part of + the build of BIND 10. With the use of MySQL, some additional + installation steps are needed: + +
+ Install MySQL + + Install MySQL according to the instructions for your system. The client development + libraries must be installed. + +
+
+ Build and Install BIND 10 + + Build and install BIND 10 as described in , with + the following modification: to enable the MySQL database code, the + "configure" step (see ), specify the location of the + MySQL configuration program "mysql_config" with the "--with-mysql-config" switch, + i.e. + ./configure [other-options] --with-dhcp-mysql + ...if MySQL was installed in the default location, or: + ./configure [other-options] --with-dhcp-mysql=<path-to-mysql_config> + ...if not. + +
+
+ Create MySQL Database and BIND 10 User + + The next task is to create both the DHCPv6 lease database and the user under which the DHCPv6 server will + access it. Although the intention is to have the name of the database and the user configurable, + at the moment they are hard-coded as "kea", as is the associated password. ("kea" is an internal + code name for BIND 10 DHCP.) There are a number of steps required: + + + 1. Log into MySQL as "root": + $ mysql -u root -p +Enter password: + : +mysql> + + + 2. Create the database: + mysql> CREATE DATABASE kea; + + + 3. Create the database tables: + mysql> CONNECT kea; +mysql> SOURCE <path-to-bind10>/share/bind10-devel/dhcpdb_create.mysql + + + 4. Create the user under which BIND 10 will access the database and grant it access to the database tables: + mysql> CREATE USER 'kea'@'localhost' IDENTIFIED BY 'kea'; +mysql> GRANT ALL ON kea.* TO 'kea'@'localhost'; + + + 5. Exit MySQL: + mysql> quit +Bye +$ + +
+
+
DHCPv6 Server Usage - - BIND 10 provides the DHCPv6 server component since September - 2011. It is a skeleton server and can be described as an early - prototype that is not fully functional yet. It is mature - enough to conduct first tests in lab environment, but it has - significant limitations. See for - details. - b10-dhcp6 is a BIND 10 component and is being @@ -2995,10 +3709,8 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1"; - To shutdown running b10-dhcp6, please use the + To stop running b10-dhcp6, use the following command: - > Dhcp6 shutdown - or > config remove Boss/components b10-dhcp6 > config commit @@ -3007,12 +3719,11 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1"; During start-up the server will detect available network interfaces and will attempt to open UDP sockets on all interfaces that are up, running, are not loopback, are multicast-capable, and - have IPv6 address assigned. - - The server will then listen to incoming traffic. Currently - supported client messages are SOLICIT and REQUEST. The server + have IPv6 address assigned. It will then listen to incoming traffic. The + currently supported client messages are SOLICIT and REQUEST. The server will respond to them with ADVERTISE and REPLY, respectively. - + + Since the DHCPv6 server opens privileged ports, it requires root access. Make sure you run this daemon as root. @@ -3022,11 +3733,10 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
DHCPv6 Server Configuration - Once the server is started, it can be configured. To view the + Once the server has been started, it can be configured. To view the current configuration, use the following command in bindctl: - - > config show Dhcp6 - When starting Dhcp6 daemon for the first time, the default configuration + > config show Dhcp6 + When starting the Dhcp6 daemon for the first time, the default configuration will be available. It will look similar to this: > config show Dhcp6 @@ -3045,14 +3755,14 @@ Dhcp6/subnet6 [] list (default) > config set Dhcp6/valid-lifetime 7200 > config commit - Please note that most Dhcp6 parameters are of global scope + Most Dhcp6 parameters are of global scope and apply to all defined subnets, unless they are overridden on a per-subnet basis. - The essential role of DHCPv6 server is address assignment. The server - has to be configured with at least one subnet and one pool of dynamic + The essential role of a DHCPv6 server is address assignment. For this, + the server has to be configured with at least one subnet and one pool of dynamic addresses to be managed. For example, assume that the server is connected to a network segment that uses the 2001:db8:1::/64 prefix. The Administrator of that network has decided that addresses from range @@ -3068,7 +3778,7 @@ Dhcp6/subnet6 [] list (default) enclosed in square brackets, even though only one range of addresses is specified. It is possible to define more than one pool in a - subnet: continuing the previous example, further assume that + subnet: continuing the previous example, further assume that 2001:db8:1:0:5::/80 should be also be managed by the server. It could be written as 2001:db8:1:0:5:: to 2001:db8:1::5:ffff:ffff:ffff, but typing so many 'f's is cumbersome. It can be expressed more simply as 2001:db8:1:0:5::/80. Both @@ -3104,24 +3814,49 @@ Dhcp6/subnet6 [] list (default) 2001:db8:: address may be assigned as well. If you want to avoid this, please use min-max notation. - - Note: Although configuration is now accepted, it is not internally used - by they server yet. At this stage of development, the only way to alter - server configuration is to modify its source code. To do so, please edit - src/bin/dhcp6/dhcp6_srv.cc file, modify the following parameters and - recompile: + Options can also be configured: the following commands configure + the DNS-SERVERS option for all subnets with the following addresses: + 2001:db8:1::1 and 2001:db8:1::2 -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"; - - Lease database and configuration support is planned for 2012. +> config add Dhcp6/option-data +> config set Dhcp6/option-data[0]/name "dns-servers" +> config set Dhcp6/option-data[0]/code 23 +> config set Dhcp6/option-data[0]/data "2001 0DB8 0001 0000 0000 0000 + 0000 0001 2001 0DB8 0001 0000 0000 0000 0000 0002" +> config commit + + (The value for the setting of the "data" element is split across two + lines in this document for clarity: when entering the command, all the + string should be entered on the same line.) - + + Currently the only way to set option data is to specify the + data as a string of hexadecimal digits. It is planned to allow + alternative ways of specifying the data as a comma-separated list, + e.g. "2001:db8:1::1,2001:db8:1::2". + + + As with global settings, it is also possible to override options on a + per-subnet basis, e.g. the following commands override the global DNS + servers option for a particular subnet, setting a single DNS server with + address 2001:db8:1::3. + +> config add Dhcp6/subnet6[0]/option-data +> config set Dhcp6/subnet6[0]/option-data[0]/name "dns-servers" +> config set Dhcp6/subnet6[0]/option-data[0]/code 23 +> config set Dhcp6/subnet6[0]/option-data[0]/data "2001 0DB8 0001 0000 + 0000 0000 0000 0003" +> config commit + (As before, the setting of the "data" element has been split across two + lines for clarity.) + + + + With this version of BIND 10, there are a number of known limitations + and problems in the DHCPv6 server. See . + +
@@ -3142,30 +3877,39 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
DHCPv6 Server Limitations - These are the current limitations of the DHCPv6 server + These are the current limitations and known problems + with the the DHCPv6 server software. Most of them are reflections of the early stage of development and should be treated as not implemented yet, rather than actual limitations. - Relayed traffic is not supported. + The DHCPv6 server has only been tested on Debian + operating systems. There are known problems with the + handling of packets in CentOS and RHEL. - b10-dhcp6 provides a single, - fixed, hardcoded lease to any client that asks. There is no - lease manager implemented. If two clients request addresses, - they will both get the same fixed address. + Relayed traffic is not supported. + + + b10-dhcp6 only supports + a limited number of configuration options. - b10-dhcp6 does not support any - configuration mechanisms yet. The whole configuration is - currently hardcoded. The only way to tweak configuration - is to directly modify source code. See see for details. + + On startup, the DHCPv6 server does not get the full configuration from + BIND 10. To remedy this, after starting BIND 10, modify any parameter + and commit the changes, e.g. + +> config show Dhcp6/renew-timer +Dhcp6/renew-timer 1000 integer (default) +> config set Dhcp6/renew-timer 1001 +> config commit + - Upon start, the server will open sockets on all + Upon start, the server will open sockets on all interfaces that are not loopback, are up, running and are multicast capable and have IPv6 address. Support for multiple interfaces is not coded in reception routines yet, @@ -3173,34 +3917,29 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1"; interfaces and b10-dhcp6 happens to listen on wrong interface, the easiest way to work around this problem is to turn down other interfaces. This - limitation will be fixed shortly. + limitation will be fixed shortly. - ORO (Option Request Option, a list of options - requested by a client) is currently ignored and server - assigns DNS SERVER option. + ORO (Option Request Option, a list of options + requested by a client) is currently unsupported. - Temporary addresses are not supported yet. + Temporary addresses are not supported. - Prefix delegation is not supported yet. + Prefix delegation is not supported. - Address renewal (RENEW), rebinding (REBIND), + Address renewal (RENEW), rebinding (REBIND), confirmation (CONFIRM), duplication report (DECLINE) and - release (RELEASE) are not supported yet. + release (RELEASE) are not supported. - DNS Update is not supported yet. + DNS Update is not supported. - Interface detection is currently working on Linux - only. See for details. - - - -v (verbose) command line option is currently the - default, and cannot be disabled. + Interface detection is currently working on Linux + only. See for details. @@ -3210,38 +3949,53 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1"; libdhcp++ library - libdhcp++ is a common library written in C++ that handles - many DHCP-related tasks, like DHCPv4 and DHCPv6 packets parsing, - manipulation and assembly, option parsing, manipulation and - assembly, network interface detection and socket operations, like - socket creations, data transmission and reception and socket - closing. + + libdhcp++ is a common library written in C++ that handles + many DHCP-related tasks, including + + + DHCPv4 and DHCPv6 packets parsing, manipulation and assembly + + + Option parsing, manipulation and assembly + + + Network interface detection + + + Socket operations such as creation, data transmission and reception and socket closing. + + While this library is currently used by b10-dhcp4 and b10-dhcp6 - only, it is designed to be portable, universal library useful for + only, it is designed to be a portable, universal library, useful for any kind of DHCP-related software. + +
Interface detection - Both DHCPv4 and DHCPv6 components share network + Both the DHCPv4 and DHCPv6 components share network interface detection routines. Interface detection is currently only supported on Linux systems. - For non-Linux systems, there is currently stub - implementation provided. Interface manager detects loopback + For non-Linux systems, there is currently a stub + implementation provided. The interface manager detects loopback interfaces only as their name (lo or lo0) can be easily predicted. Please contact the BIND 10 development team if you are interested in running DHCP components on systems other than Linux.
+
@@ -3748,7 +4502,7 @@ Logging/loggers [] list - > config add Logging/loggers + > config add Logging/loggers > config show Logging Logging/loggers/ list (modified) diff --git a/examples/README b/examples/README index 65f777b693..08f53faba0 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 937968760d..37515d9472 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 e41614d884..77d19cafde 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 63e028c407..75c37c5f70 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 0000000000..91d9b8af1f --- /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 9eee9d4d54..7d29fccdcb 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 a471b7a87f..30a455d8e0 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_name": "opcode.other", "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_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_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_name": "rcode.reserved12", - "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_name": "rcode.reserved13", - "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_name": "rcode.reserved14", - "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_name": "rcode.reserved15", - "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_name": "rcode.badvers", + "item_name": "rcode.badsigvers", "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_name": "rcode.badkey", + "item_type": "integer", + "item_optional": true, + "item_default": 0, + "item_title": "Sent 'Key not recognized' response", + "item_description": "The number of total responses with rcode 17 (BADKEY)" + }, + { + "item_name": "rcode.badtime", + "item_type": "integer", + "item_optional": true, + "item_default": 0, + "item_title": "Sent 'Signature out of time window' response", + "item_description": "The number of total responses with rcode 18 (BADTIME)" + }, + { + "item_name": "rcode.badmode", + "item_type": "integer", + "item_optional": true, + "item_default": 0, + "item_title": "Sent 'Bad TKEY Mode' response", + "item_description": "The number of total responses with rcode 19 (BADMODE)" + }, + { + "item_name": "rcode.badname", + "item_type": "integer", + "item_optional": true, + "item_default": 0, + "item_title": "Sent 'Duplicate key name' response", + "item_description": "The number of total responses with rcode 20 (BADNAME)" + }, + { + "item_name": "rcode.badalg", + "item_type": "integer", + "item_optional": true, + "item_default": 0, + "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 8e816a3fb4..57fd270faa 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 d41eaeab77..fae7bd35a3 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 33d4432877..52b973ecc9 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 ae7be1e3ed..ac9ffb9874 100644 --- a/src/bin/auth/auth_messages.mes +++ b/src/bin/auth/auth_messages.mes @@ -47,16 +47,126 @@ 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_LOAD_ZONE_NOCACHE skipped loading zone %1/%2 due to no in-memory cache +This debug message is issued when the separate thread for maintaining data +source clients received a command to reload a zone but skipped it because +the specified zone is not loaded in-memory (but served from an underlying +data source). This could happen if the loadzone command is manually issued +by a user but the zone name is misspelled, but a more likely cause is +that the command is sent from another BIND 10 module (such as xfrin or DDNS). +In the latter case it can be simply ignored because there is no need +for explicit reloading. + +% 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 +193,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 e73eb528db..26a8489bcb 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,22 @@ 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 + // race with any other thread(s) such as the background loader. + 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(); @@ -683,6 +689,9 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message, return (true); // The message can contain some data from the locked resource. But outside // this method, we touch only the RCode of it, so it should be safe. + + // Lock on datasrc_clients_mgr_ acquired by datasrc_holder is + // released here upon its deletion. } bool @@ -690,9 +699,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 +830,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 +847,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 +878,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 +916,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 0849bdd1d0..ebd303447f 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 c525b665d3..27ddfc554c 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 37976a3358..77b3377434 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 d26cf091de..9fbfb42c5c 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 7db5cd697a..5058b81943 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 0964217336..908a7d1621 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 0000000000..5bbdb99b17 --- /dev/null +++ b/src/bin/auth/datasrc_clients_mgr.h @@ -0,0 +1,669 @@ +// 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); + if (!zwriter) { + return; + } + + 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) +{ + // getCachedZoneWriter() could get access to an underlying data source + // that can cause a race condition with the main thread using that data + // source for lookup. So we need to protect the access here. + datasrc::ConfigurableClientList::ZoneWriterPair writerpair; + { + typename MutexType::Locker locker(*map_mutex_); + 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: + LOG_DEBUG(auth_logger, DBG_AUTH_OPS, + AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE_NOCACHE) + .arg(origin).arg(rrclass); + break; // return NULL below + 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"); + } + + 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 62c3c7a6ba..4869050e6a 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 5707c6c8a0..1723161f66 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 99080662ff..e90d199d0e 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")); - } + // 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. - // 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. + 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")); } } @@ -155,7 +147,7 @@ main(int argc, char* argv[]) { // Initialize logging. If verbose, we'll use maximum verbosity. isc::log::initLogger(AUTH_NAME, (verbose ? isc::log::DEBUG : isc::log::INFO), - isc::log::MAX_DEBUG_LEVEL, NULL); + isc::log::MAX_DEBUG_LEVEL, NULL, true); int ret = 0; @@ -264,7 +256,9 @@ main(int argc, char* argv[]) { // If we haven't registered callback for data sources, this will be just // no-op. - config_session->removeRemoteConfig("data_sources"); + if (config_session != NULL) { + config_session->removeRemoteConfig("data_sources"); + } delete xfrin_session; delete config_session; diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc index 69278d4e6d..5b71565896 100644 --- a/src/bin/auth/query.cc +++ b/src/bin/auth/query.cc @@ -410,9 +410,9 @@ Query::process(datasrc::ClientList& client_list, */ assert(db_context->rrset->getRdataCount() > 0); // Get the data of DNAME + RdataIteratorPtr rit = db_context->rrset->getRdataIterator(); const rdata::generic::DNAME& dname( - dynamic_cast( - db_context->rrset->getRdataIterator()->getCurrent())); + dynamic_cast(rit->getCurrent())); // The yet unmatched prefix dname const Name prefix(qname_->split(0, qname_->getLabelCount() - db_context->rrset->getName().getLabelCount())); diff --git a/src/bin/auth/statistics.cc b/src/bin/auth/statistics.cc index 2d5f336195..b310b23bf6 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 0ca8da4d39..d60c6814a4 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. + /// \brief Increment counters according to the parameters. /// - /// \param type Type of a counter to increment. - /// - /// \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 0000000000..5839206481 --- /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 6b9d385298..42f202b696 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 @@ -50,7 +50,11 @@ run_unittests_SOURCES += config_syntax_unittest.cc run_unittests_SOURCES += command_unittest.cc run_unittests_SOURCES += common_unittest.cc run_unittests_SOURCES += query_unittest.cc +run_unittests_SOURCES += query_inmemory_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 +76,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 e248e81483..7f89fda23a 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); + } } } } @@ -205,7 +225,7 @@ createBuiltinVersionResponse(const qid_t qid, vector& data) { message.setHeaderFlag(Message::HEADERFLAG_AA); RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(), RRType::TXT(), RRTTL(0))); - rrset_version->addRdata(generic::TXT(PACKAGE_STRING)); + rrset_version->addRdata(generic::TXT("\"" PACKAGE_STRING "\"")); message.addRRset(Message::SECTION_ANSWER, rrset_version); RRsetPtr rrset_version_ns = RRsetPtr(new RRset(apex_name, RRClass::CH(), @@ -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()); +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) - AuthSrv::DataSrcClientListsPtr lists; // initially empty - - // The lists don't exist. Therefore, the list of RRClasses is empty. - EXPECT_TRUE(server.swapDataSrcClientLists(lists)->empty()); - - // 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 8aa2322151..be90d736f8 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 0000000000..838e032f3f --- /dev/null +++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc @@ -0,0 +1,526 @@ +// 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)); + + // loadZone involves two critical sections: one for getting the zone + // writer, and one for actually updating the zone data. So the lock/unlock + // count should be incremented by 2. + EXPECT_EQ(2, map_mutex.lock_count); + EXPECT_EQ(2, map_mutex.unlock_count); + + 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. In this + // case the command is simply ignored. + const size_t orig_lock_count = map_mutex.lock_count; + const size_t orig_unlock_count = map_mutex.unlock_count; + const ConstElementPtr config2(Element::fromJSON("{" + "\"IN\": [{" + " \"type\": \"sqlite3\"," + " \"params\": {\"database_file\": \"" + test_db + "\"}," + " \"cache-enable\": false," + " \"cache-zones\": [\"example.org\"]" + "}]}")); + clients_map = configureDataSource(config2); + builder.handleCommand( + Command(LOADZONE, Element::fromJSON( + "{\"class\": \"IN\"," + " \"origin\": \"example.org\"}"))); + // Only one mutex was needed because there was no actual reload. + EXPECT_EQ(orig_lock_count + 1, map_mutex.lock_count); + EXPECT_EQ(orig_unlock_count + 1, map_mutex.unlock_count); + + // 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 0000000000..c37ef115a9 --- /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 877f92166f..b555aa69ad 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 07ebc0c25e..974833266b 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/query_inmemory_unittest.cc b/src/bin/auth/tests/query_inmemory_unittest.cc new file mode 100644 index 0000000000..f587ba21ad --- /dev/null +++ b/src/bin/auth/tests/query_inmemory_unittest.cc @@ -0,0 +1,123 @@ +// 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 + +using namespace isc::dns; +using namespace isc::auth; +using namespace isc::testutils; +using isc::datasrc::ConfigurableClientList; +using std::string; + +namespace { + +// The DNAME to do tests against +const char* const dname_txt = + "dname.example.com. 3600 IN DNAME " + "somethinglong.dnametarget.example.com.\n"; +// This is not inside the zone, this is created at runtime +const char* const synthetized_cname_txt = + "www.dname.example.com. 3600 IN CNAME " + "www.somethinglong.dnametarget.example.com.\n"; + +// This is a subset of QueryTest using (subset of) the same test data, but +// with the production in-memory data source. Both tests should be eventually +// unified to avoid duplicates. +class InMemoryQueryTest : public ::testing::Test { +protected: + InMemoryQueryTest() : list(RRClass::IN()), response(Message::RENDER) { + response.setRcode(Rcode::NOERROR()); + response.setOpcode(Opcode::QUERY()); + list.configure(isc::data::Element::fromJSON( + "[{\"type\": \"MasterFiles\"," + " \"cache-enable\": true, " + " \"params\": {\"example.com\": \"" + + string(TEST_OWN_DATA_DIR "/example.zone") + + "\"}}]"), true); + } + + ConfigurableClientList list; + Message response; + Query query; +}; + +// A wrapper to check resulting response message commonly used in +// tests below. +// check_origin needs to be specified only when the authority section has +// an SOA RR. The interface is not generic enough but should be okay +// for our test cases in practice. +void +responseCheck(Message& response, const isc::dns::Rcode& rcode, + unsigned int flags, const unsigned int ancount, + const unsigned int nscount, const unsigned int arcount, + const char* const expected_answer, + const char* const expected_authority, + const char* const expected_additional, + const Name& check_origin = Name::ROOT_NAME()) +{ + // In our test cases QID, Opcode, and QDCOUNT should be constant, so + // we don't bother the test cases specifying these values. + headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(), + flags, 0, ancount, nscount, arcount); + if (expected_answer != NULL) { + rrsetsCheck(expected_answer, + response.beginSection(Message::SECTION_ANSWER), + response.endSection(Message::SECTION_ANSWER), + check_origin); + } + if (expected_authority != NULL) { + rrsetsCheck(expected_authority, + response.beginSection(Message::SECTION_AUTHORITY), + response.endSection(Message::SECTION_AUTHORITY), + check_origin); + } + if (expected_additional != NULL) { + rrsetsCheck(expected_additional, + response.beginSection(Message::SECTION_ADDITIONAL), + response.endSection(Message::SECTION_ADDITIONAL)); + } +} + +/* + * Test a query under a domain with DNAME. We should get a synthetized CNAME + * as well as the DNAME. + * + * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs + * as well. This includes tests pointing inside the zone, outside the zone, + * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME). + */ +TEST_F(InMemoryQueryTest, DNAME) { + query.process(list, Name("www.dname.example.com"), RRType::A(), + response); + + responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0, + (string(dname_txt) + synthetized_cname_txt).c_str(), + NULL, NULL); +} +} diff --git a/src/bin/auth/tests/statistics_unittest.cc b/src/bin/auth/tests/statistics_unittest.cc index d2f5a5ab95..052a70e83e 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*) {} +checkCountersAllZeroExcept(const isc::data::ConstElementPtr counters, + const std::set& except_for) { + std::map stats_map = counters->mapValue(); -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); + 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(); + } } -bool -AuthCountersTest::MockSession::hasQueuedMsgs() const { - return (false); +TEST_F(CountersTest, incrementNormalQuery) { + Message response(Message::RENDER); + QRAttributes qrattrs; + std::set expect_nonzero; + + expect_nonzero.clear(); + checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero); + + qrattrs.setQueryIPVersion(AF_INET6); + qrattrs.setQueryTransportProtocol(IPPROTO_UDP); + qrattrs.setQueryOpCode(Opcode::QUERY_CODE); + qrattrs.setQueryEDNS(true, false); + qrattrs.setQueryDO(true); + qrattrs.answerWasSent(); + + response.setRcode(Rcode::REFUSED()); + response.addQuestion(Question(Name("example.com"), + RRClass::IN(), RRType::AAAA())); + + counters.inc(qrattrs, response); + + expect_nonzero.clear(); + expect_nonzero.insert("opcode.query"); + expect_nonzero.insert("queries.udp"); + expect_nonzero.insert("rcode.refused"); + checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero); } 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)); +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 { - EXPECT_EQ(expected[i], data->get(item_name)->intValue()); + count += countTreeElements(tree[i].sub_tree); } } - // We should have examined all names - ASSERT_EQ(static_cast(NULL), item_names[i]); + return count; } -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()); - } - } - // We should have examined all names - ASSERT_EQ(static_cast(NULL), item_names[i]); +TEST(StatisticsItemsTest, QRItemNamesCheck) { + EXPECT_EQ(QR_COUNTER_TYPES, countTreeElements(QRCounterTree)); } -TEST_F(AuthCountersTest, getStatisticsWithoutValidator) { - // Get statistics data. - // Validate if it answers correct data. - - // 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); - 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()); - - // 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); -} - -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)); - } - } -} - -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)); - } - } -} - -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_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 0000000000..82937c0c5e --- /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 0000000000..9b1a3672cd --- /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/auth/tests/testdata/Makefile.am b/src/bin/auth/tests/testdata/Makefile.am index 7e42b70402..a4ea1a528f 100644 --- a/src/bin/auth/tests/testdata/Makefile.am +++ b/src/bin/auth/tests/testdata/Makefile.am @@ -21,6 +21,7 @@ EXTRA_DIST += simpleresponse_fromWire.spec EXTRA_DIST += spec.spec EXTRA_DIST += example.com +EXTRA_DIST += example.zone EXTRA_DIST += example.sqlite3 .spec.wire: diff --git a/src/bin/auth/tests/testdata/example.zone b/src/bin/auth/tests/testdata/example.zone new file mode 100644 index 0000000000..efbbaf23e9 --- /dev/null +++ b/src/bin/auth/tests/testdata/example.zone @@ -0,0 +1,121 @@ +;; +;; This is a complete (but crafted and somewhat broken) zone file used +;; in query tests. +;; + +example.com. 3600 IN SOA . . 0 0 0 0 0 +example.com. 3600 IN NS glue.delegation.example.com. +example.com. 3600 IN NS noglue.example.com. +example.com. 3600 IN NS example.net. +example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B +glue.delegation.example.com. 3600 IN A 192.0.2.153 +glue.delegation.example.com. 3600 IN AAAA 2001:db8::53 +noglue.example.com. 3600 IN A 192.0.2.53 +delegation.example.com. 3600 IN NS glue.delegation.example.com. +delegation.example.com. 3600 IN NS noglue.example.com. +delegation.example.com. 3600 IN NS cname.example.com. +delegation.example.com. 3600 IN NS example.org. +;; Borrowed from the RFC4035 +delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B +mx.example.com. 3600 IN MX 10 www.example.com. +mx.example.com. 3600 IN MX 20 mailer.example.org. +mx.example.com. 3600 IN MX 30 mx.delegation.example.com. +www.example.com. 3600 IN A 192.0.2.80 +cname.example.com. 3600 IN CNAME www.example.com. +cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com. +;; CNAME Leading out of zone +cnameout.example.com. 3600 IN CNAME www.example.org. +;; The DNAME to do tests against +dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com. +;; Some data at the dname node (allowed by RFC 2672) +dname.example.com. 3600 IN A 192.0.2.5 +;; The rest of data won't be referenced from the test cases. +cnamemailer.example.com. 3600 IN CNAME www.example.com. +cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com. +mx.delegation.example.com. 3600 IN A 192.0.2.100 +;; Wildcards +*.wild.example.com. 3600 IN A 192.0.2.7 +*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG +*.cnamewild.example.com. 3600 IN CNAME www.example.org. +*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG +;; Wildcard_nxrrset +*.uwild.example.com. 3600 IN A 192.0.2.9 +*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG +www.uwild.example.com. 3600 IN A 192.0.2.11 +www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG +;; Wildcard empty +b.*.t.example.com. 3600 IN A 192.0.2.13 +b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG +t.example.com. 3600 IN A 192.0.2.15 +t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG +;; Used in NXDOMAIN proof test. We are going to test some unusual case where +;; the best possible wildcard is below the "next domain" of the NSEC RR that +;; proves the NXDOMAIN, i.e., +;; mx.example.com. (exist) +;; (.no.example.com. (qname, NXDOMAIN) +;; ).no.example.com. (exist) +;; *.no.example.com. (best possible wildcard, not exist) +).no.example.com. 3600 IN AAAA 2001:db8::53 +;; NSEC records. +example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG +mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG +).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG +;; We'll also test the case where a single NSEC proves both NXDOMAIN and the +;; non existence of wildcard. The following records will be used for that +;; test. +;; ).no.example.com. (exist, whose NSEC proves everything) +;; *.no.example.com. (best possible wildcard, not exist) +;; nx.no.example.com. (NXDOMAIN) +;; nz.no.example.com. (exist) +nz.no.example.com. 3600 IN AAAA 2001:db8::5300 +nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG +noglue.example.com. 3600 IN NSEC nonsec.example.com. A + +;; NSEC for the normal NXRRSET case +www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG + +;; Authoritative data without NSEC +nonsec.example.com. 3600 IN A 192.0.2.0 + +;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_. +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG +0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE +q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG +q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE + +;; NSEC3 for wild.example.com (used in wildcard tests, will be added on +;; demand not to confuse other tests) +ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en + +;; NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on +;; demand not to confuse other tests) +k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en + +;; NSEC3 for *.uwild.example.com (will be added on demand not to confuse +;; other tests) +b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG +;; NSEC3 for uwild.example.com. (will be added on demand) +t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG + +;; (Secure) delegation data; Delegation with DS record +signed-delegation.example.com. 3600 IN NS ns.example.net. +signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA + +;; (Secure) delegation data; Delegation without DS record (and both NSEC +;; and NSEC3 denying its existence) +unsigned-delegation.example.com. 3600 IN NS ns.example.net. +unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC +;; This one will be added on demand +q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG + +;; Delegation without DS record, and no direct matching NSEC3 record +unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net. +unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC + +;; (Secure) delegation data; Delegation where the DS lookup will raise an +;; exception. +bad-delegation.example.com. 3600 IN NS ns.example.net. + +;; Delegation from an unsigned parent. There's no DS, and there's no NSEC +;; or NSEC3 that proves it. +nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net. diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes index 2f48325498..aba8da5ce0 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 32c3152099..882653b3ba 100755 --- a/src/bin/bind10/bind10_src.py.in +++ b/src/bin/bind10/bind10_src.py.in @@ -48,7 +48,7 @@ else: PREFIX = "@prefix@" DATAROOTDIR = "@datarootdir@" SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX) - + import subprocess import signal import re @@ -76,7 +76,7 @@ import isc.bind10.socket_cache import libutil_io_python import tempfile -isc.log.init("b10-boss") +isc.log.init("b10-boss", buffer=True) logger = isc.log.Logger("boss") # Pending system-wide debug level definitions, the ones we @@ -166,14 +166,14 @@ class ProcessStartError(Exception): pass class BoB: """Boss of BIND class.""" - + def __init__(self, msgq_socket_file=None, data_path=None, config_filename=None, clear_config=False, verbose=False, nokill=False, setuid=None, setgid=None, username=None, cmdctl_port=None, wait_time=10): """ Initialize the Boss of BIND. This is a singleton (only one can run). - + The msgq_socket_file specifies the UNIX domain socket file that the msgq process listens on. If verbose is True, then the boss reports what it is doing. @@ -216,6 +216,12 @@ class BoB: self.clear_config = clear_config self.cmdctl_port = cmdctl_port self.wait_time = wait_time + self.msgq_timeout = 5 + + # _run_under_unittests is only meant to be used when testing. It + # bypasses execution of some code to help with testing. + self._run_under_unittests = False + self._component_configurator = isc.bind10.component.Configurator(self, isc.bind10.special_component.get_specials()) # The priorities here make them start in the correct order. First @@ -331,10 +337,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.__kill_children(True) self.components = {} def _read_bind10_config(self): @@ -404,7 +407,7 @@ class BoB: logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg) except: logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg) - + return False # The next few methods start the individual processes of BIND-10. They @@ -412,21 +415,35 @@ class BoB: # raised which is caught by the caller of start_all_processes(); this kills # processes started up to that point before terminating the program. + def _make_process_info(self, name, args, env, + dev_null_stdout=False, dev_null_stderr=False): + """ + Wrapper around ProcessInfo(), useful to override + ProcessInfo() creation during testing. + """ + return ProcessInfo(name, args, env, dev_null_stdout, dev_null_stderr) + def start_msgq(self): """ Start the message queue and connect to the command channel. """ self.log_starting("b10-msgq") - msgq_proc = ProcessInfo("b10-msgq", ["b10-msgq"], self.c_channel_env, - True, not self.verbose) + msgq_proc = self._make_process_info("b10-msgq", ["b10-msgq"], + self.c_channel_env, + True, not self.verbose) msgq_proc.spawn() self.log_started(msgq_proc.pid) # Now connect to the c-channel cc_connect_start = time.time() while self.cc_session is None: + # if we are run under unittests, break + if self._run_under_unittests: + break + # if we have been trying for "a while" give up - if (time.time() - cc_connect_start) > 5: + if (time.time() - cc_connect_start) > self.msgq_timeout: + 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 @@ -437,7 +454,8 @@ class BoB: # Subscribe to the message queue. The only messages we expect to receive # on this channel are once relating to process startup. - self.cc_session.group_subscribe("Boss") + if self.cc_session is not None: + self.cc_session.group_subscribe("Boss") return msgq_proc @@ -453,13 +471,14 @@ class BoB: args.append("--config-filename=" + self.config_filename) if self.clear_config: args.append("--clear-config") - bind_cfgd = ProcessInfo("b10-cfgmgr", args, - self.c_channel_env) + bind_cfgd = self._make_process_info("b10-cfgmgr", args, + self.c_channel_env) bind_cfgd.spawn() self.log_started(bind_cfgd.pid) - # Wait for the configuration manager to start up as subsequent initialization - # cannot proceed without it. The time to wait can be set on the command line. + # Wait for the configuration manager to start up as + # subsequent initialization cannot proceed without it. The + # time to wait can be set on the command line. time_remaining = self.wait_time msg, env = self.cc_session.group_recvmsg() while time_remaining > 0 and not self.process_running(msg, "ConfigManager"): @@ -467,7 +486,7 @@ class BoB: time.sleep(1) time_remaining = time_remaining - 1 msg, env = self.cc_session.group_recvmsg() - + if not self.process_running(msg, "ConfigManager"): raise ProcessStartError("Configuration manager process has not started") @@ -484,7 +503,7 @@ class BoB: process, the log_starting/log_started methods are not used. """ logger.info(BIND10_STARTING_CC) - self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, + self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler, socket_file = self.msgq_socket_file) @@ -502,7 +521,7 @@ class BoB: The port and address arguments are for log messages only. """ self.log_starting(name, port, address) - newproc = ProcessInfo(name, args, c_channel_env) + newproc = self._make_process_info(name, args, c_channel_env) newproc.spawn() self.log_started(newproc.pid) return newproc @@ -546,8 +565,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'] @@ -616,7 +633,6 @@ class BoB: if self.msgq_socket_file is not None: c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING) - # try to connect, and if we can't wait a short while try: self.cc_session = isc.cc.Session(self.msgq_socket_file) logger.fatal(BIND10_MSGQ_ALREADY_RUNNING) @@ -684,7 +700,7 @@ class BoB: except: pass # XXX: some delay probably useful... how much is uncertain - # I have changed the delay from 0.5 to 1, but sometime it's + # I have changed the delay from 0.5 to 1, but sometime it's # still not enough. time.sleep(1) self.reap_children() @@ -693,47 +709,59 @@ 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) def reap_children(self): - """Check to see if any of our child processes have exited, - and note this for later handling. + """Check to see if any of our child processes have exited, + and note this for later handling. """ while True: try: (pid, exit_status) = self._get_process_exit_status() except OSError as o: - if o.errno == errno.ECHILD: break + if o.errno == errno.ECHILD: + break # XXX: should be impossible to get any other error here raise - if pid == 0: break + if pid == 0: + break if pid in self.components: # One of the components we know about. Get information on it. component = self.components.pop(pid) @@ -755,11 +783,11 @@ class BoB: """ Restart any dead processes: - * Returns the time when the next process is ready to be restarted. + * Returns the time when the next process is ready to be restarted. * If the server is shutting down, returns 0. * If there are no processes, returns None. - The values returned can be safely passed into select() as the + The values returned can be safely passed into select() as the timeout value. """ @@ -904,8 +932,10 @@ class BoB: """ if self._srv_socket is not None: self._srv_socket.close() - os.remove(self._socket_path) - os.rmdir(self._tmpdir) + if os.path.exists(self._socket_path): + os.remove(self._socket_path) + if os.path.isdir(self._tmpdir): + os.rmdir(self._tmpdir) def _srv_accept(self): """ @@ -1001,7 +1031,7 @@ boss_of_bind = None def reaper(signal_number, stack_frame): """A child process has died (SIGCHLD received).""" - # don't do anything... + # don't do anything... # the Python signal handler has been set up to write # down a pipe, waking up our select() bit pass @@ -1137,6 +1167,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 @@ -1153,7 +1198,7 @@ def main(): except KeyError: pass - # Next try getting information about the user, assuming user name + # Next try getting information about the user, assuming user name # passed. # If the information is both a valid user name and user number, we # prefer the name because we try it second. A minor point, hopefully. @@ -1169,9 +1214,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 0b119606e0..409790c810 100644 --- a/src/bin/bind10/tests/bind10_test.py.in +++ b/src/bin/bind10/tests/bind10_test.py.in @@ -25,6 +25,7 @@ import bind10_src import unittest import sys import os +import os.path import copy import signal import socket @@ -34,6 +35,7 @@ import isc import isc.log import isc.bind10.socket_cache import errno +import random from isc.testutils.parse_args import TestOptParser, OptsError from isc.testutils.ccsession_mock import MockModuleCCSession @@ -366,6 +368,53 @@ class TestBoB(unittest.TestCase): self.assertEqual(creator, bob._socket_cache._creator) self.assertRaises(ValueError, bob.set_creator, creator) + def test_socket_srv(self): + """Tests init_socket_srv() and remove_socket_srv() work as expected.""" + bob = BoB() + + self.assertIsNone(bob._srv_socket) + self.assertIsNone(bob._tmpdir) + self.assertIsNone(bob._socket_path) + + bob.init_socket_srv() + + self.assertIsNotNone(bob._srv_socket) + self.assertNotEqual(-1, bob._srv_socket.fileno()) + self.assertEqual(os.path.join(bob._tmpdir, 'sockcreator'), + bob._srv_socket.getsockname()) + + self.assertIsNotNone(bob._tmpdir) + self.assertTrue(os.path.isdir(bob._tmpdir)) + self.assertIsNotNone(bob._socket_path) + self.assertTrue(os.path.exists(bob._socket_path)) + + # Check that it's possible to connect to the socket file (this + # only works if the socket file exists and the server listens on + # it). + s = socket.socket(socket.AF_UNIX) + try: + s.connect(bob._socket_path) + can_connect = True + s.close() + except socket.error as e: + can_connect = False + + self.assertTrue(can_connect) + + bob.remove_socket_srv() + + self.assertEqual(-1, bob._srv_socket.fileno()) + self.assertFalse(os.path.exists(bob._socket_path)) + self.assertFalse(os.path.isdir(bob._tmpdir)) + + # These should not fail either: + + # second call + bob.remove_socket_srv() + + bob._srv_socket = None + bob.remove_socket_srv() + def test_init_alternate_socket(self): bob = BoB("alt_socket_file") self.assertEqual(bob.verbose, False) @@ -461,6 +510,22 @@ class TestBoB(unittest.TestCase): self.assertEqual({'command': ['shutdown', {'pid': 42}]}, bob.cc_session.msg) +# Mock class for testing BoB's usage of ProcessInfo +class MockProcessInfo: + def __init__(self, name, args, env={}, dev_null_stdout=False, + dev_null_stderr=False): + self.name = name + self.args = args + self.env = env + self.dev_null_stdout = dev_null_stdout + self.dev_null_stderr = dev_null_stderr + self.process = None + self.pid = None + + def spawn(self): + # set some pid (only used for testing that it is not None anymore) + self.pid = 42147 + # Class for testing the BoB without actually starting processes. # This is used for testing the start/stop components routines and # the BoB commands. @@ -490,6 +555,7 @@ class MockBob(BoB): self.c_channel_env = {} self.components = { } self.creator = False + self.get_process_exit_status_called = False class MockSockCreator(isc.bind10.component.Component): def __init__(self, process, boss, kind, address=None, params=None): @@ -661,6 +727,52 @@ class MockBob(BoB): del self.components[12] self.cmdctl = False + def _get_process_exit_status(self): + if self.get_process_exit_status_called: + return (0, 0) + self.get_process_exit_status_called = True + return (53, 0) + + def _get_process_exit_status_unknown_pid(self): + if self.get_process_exit_status_called: + return (0, 0) + self.get_process_exit_status_called = True + return (42, 0) + + def _get_process_exit_status_raises_oserror_echild(self): + raise OSError(errno.ECHILD, 'Mock error') + + def _get_process_exit_status_raises_oserror_other(self): + raise OSError(0, 'Mock error') + + def _get_process_exit_status_raises_other(self): + raise Exception('Mock error') + + def _make_mock_process_info(self, name, args, c_channel_env, + dev_null_stdout=False, dev_null_stderr=False): + return MockProcessInfo(name, args, c_channel_env, + dev_null_stdout, dev_null_stderr) + +class MockBobSimple(BoB): + def __init__(self): + BoB.__init__(self) + # Set which process has been started + self.started_process_name = None + self.started_process_args = None + self.started_process_env = None + + def _make_mock_process_info(self, name, args, c_channel_env, + dev_null_stdout=False, dev_null_stderr=False): + return MockProcessInfo(name, args, c_channel_env, + dev_null_stdout, dev_null_stderr) + + def start_process(self, name, args, c_channel_env, port=None, + address=None): + self.started_process_name = name + self.started_process_args = args + self.started_process_env = c_channel_env + return None + class TestStartStopProcessesBob(unittest.TestCase): """ Check that the start_all_components method starts the right combination @@ -930,6 +1042,9 @@ class MockComponent: self.pid = lambda: pid self.address = lambda: address self.restarted = False + self.forceful = False + self.running = True + self.has_failed = False def get_restart_time(self): return 0 # arbitrary dummy value @@ -938,6 +1053,15 @@ class MockComponent: self.restarted = True return True + def is_running(self): + return self.running + + def failed(self, status): + return self.has_failed + + def kill(self, forceful): + self.forceful = forceful + class TestBossCmd(unittest.TestCase): def test_ping(self): """ @@ -1107,6 +1231,20 @@ class TestBossComponents(unittest.TestCase): 'process': 'cat' } } + self._tmp_time = None + self._tmp_sleep = None + self._tmp_module_cc_session = None + self._tmp_cc_session = None + + def tearDown(self): + if self._tmp_time is not None: + time.time = self._tmp_time + if self._tmp_sleep is not None: + time.sleep = self._tmp_sleep + if self._tmp_module_cc_session is not None: + isc.config.ModuleCCSession = self._tmp_module_cc_session + if self._tmp_cc_session is not None: + isc.cc.Session = self._tmp_cc_session def __unary_hook(self, param): """ @@ -1181,7 +1319,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 +1333,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 +1377,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 +1392,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 @@ -1292,14 +1462,618 @@ class TestBossComponents(unittest.TestCase): bob._component_configurator._components['test'] = (None, component) self.__setup_restart(bob, component) self.assertTrue(component.restarted) - self.assertFalse(component in bob.components_to_restart) + self.assertNotIn(component, bob.components_to_restart) # Remove the component from the configuration. It won't be restarted # even if scheduled, nor will remain in the to-be-restarted list. del bob._component_configurator._components['test'] self.__setup_restart(bob, component) self.assertFalse(component.restarted) - self.assertFalse(component in bob.components_to_restart) + self.assertNotIn(component, bob.components_to_restart) + + def test_get_processes(self): + '''Test that procsses are returned correctly, sorted by pid.''' + bob = MockBob() + + pids = list(range(0, 20)) + random.shuffle(pids) + + for i in range(0, 20): + pid = pids[i] + component = MockComponent('test' + str(pid), pid, + 'Test' + str(pid)) + bob.components[pid] = component + + process_list = bob.get_processes() + self.assertEqual(20, len(process_list)) + + last_pid = -1 + for process in process_list: + pid = process[0] + self.assertLessEqual(last_pid, pid) + last_pid = pid + self.assertEqual([pid, 'test' + str(pid), 'Test' + str(pid)], + process) + + def _test_reap_children_helper(self, runnable, is_running, failed): + '''Construct a BoB instance, set various data in it according to + passed args and check if the component was added to the list of + components to restart.''' + bob = MockBob() + bob.runnable = runnable + + component = MockComponent('test', 53) + component.running = is_running + component.has_failed = failed + bob.components[53] = component + + self.assertNotIn(component, bob.components_to_restart) + + bob.reap_children() + + if runnable and is_running and not failed: + self.assertIn(component, bob.components_to_restart) + else: + self.assertEqual([], bob.components_to_restart) + + def test_reap_children(self): + '''Test that children are queued to be restarted when they ask for it.''' + # test various combinations of 3 booleans + # (BoB.runnable, component.is_running(), component.failed()) + self._test_reap_children_helper(False, False, False) + self._test_reap_children_helper(False, False, True) + self._test_reap_children_helper(False, True, False) + self._test_reap_children_helper(False, True, True) + self._test_reap_children_helper(True, False, False) + self._test_reap_children_helper(True, False, True) + self._test_reap_children_helper(True, True, False) + self._test_reap_children_helper(True, True, True) + + # setup for more tests below + bob = MockBob() + bob.runnable = True + component = MockComponent('test', 53) + bob.components[53] = component + + # case where the returned pid is unknown to us. nothing should + # happpen then. + bob.get_process_exit_status_called = False + bob._get_process_exit_status = bob._get_process_exit_status_unknown_pid + bob.components_to_restart = [] + # this should do nothing as the pid is unknown + bob.reap_children() + self.assertEqual([], bob.components_to_restart) + + # case where bob._get_process_exit_status() raises OSError with + # errno.ECHILD + bob._get_process_exit_status = \ + bob._get_process_exit_status_raises_oserror_echild + bob.components_to_restart = [] + # this should catch and handle the OSError + bob.reap_children() + self.assertEqual([], bob.components_to_restart) + + # case where bob._get_process_exit_status() raises OSError with + # errno other than ECHILD + bob._get_process_exit_status = \ + bob._get_process_exit_status_raises_oserror_other + with self.assertRaises(OSError): + bob.reap_children() + + # case where bob._get_process_exit_status() raises something + # other than OSError + bob._get_process_exit_status = \ + bob._get_process_exit_status_raises_other + with self.assertRaises(Exception): + bob.reap_children() + + def test_kill_started_components(self): + '''Test that started components are killed.''' + bob = MockBob() + + component = MockComponent('test', 53, 'Test') + bob.components[53] = component + + self.assertEqual([[53, 'test', 'Test']], bob.get_processes()) + bob.kill_started_components() + self.assertEqual([], bob.get_processes()) + self.assertTrue(component.forceful) + + def _start_msgq_helper(self, bob, verbose): + bob.verbose = verbose + pi = bob.start_msgq() + self.assertEqual('b10-msgq', pi.name) + self.assertEqual(['b10-msgq'], pi.args) + self.assertTrue(pi.dev_null_stdout) + self.assertEqual(pi.dev_null_stderr, not verbose) + self.assertEqual({'FOO': 'an env string'}, pi.env) + + # this is set by ProcessInfo.spawn() + self.assertEqual(42147, pi.pid) + + def test_start_msgq(self): + '''Test that b10-msgq is started.''' + bob = MockBobSimple() + bob.c_channel_env = {'FOO': 'an env string'} + bob._run_under_unittests = True + + # use the MockProcessInfo creator + bob._make_process_info = bob._make_mock_process_info + + # non-verbose case + self._start_msgq_helper(bob, False) + + # verbose case + self._start_msgq_helper(bob, True) + + def test_start_msgq_timeout(self): + '''Test that b10-msgq startup attempts connections several times + and times out eventually.''' + bob = MockBobSimple() + bob.c_channel_env = {} + # set the timeout to an arbitrary pre-determined value (which + # code below depends on) + bob.msgq_timeout = 1 + bob._run_under_unittests = False + + # use the MockProcessInfo creator + bob._make_process_info = bob._make_mock_process_info + + global attempts + global tsec + attempts = 0 + tsec = 0 + self._tmp_time = time.time + self._tmp_sleep = time.sleep + def _my_time(): + global attempts + global tsec + attempts += 1 + return tsec + def _my_sleep(nsec): + global tsec + tsec += nsec + time.time = _my_time + time.sleep = _my_sleep + + global cc_sub + cc_sub = None + class DummySessionAlwaysFails(): + def __init__(self, socket_file): + raise isc.cc.session.SessionError('Connection fails') + def group_subscribe(self, s): + global cc_sub + cc_sub = s + + isc.cc.Session = DummySessionAlwaysFails + + with self.assertRaises(bind10_src.CChannelConnectError): + # An exception will be thrown here when it eventually times + # out. + pi = bob.start_msgq() + + # time.time() should be called 12 times within the while loop: + # starting from 0, and 11 more times from 0.1 to 1.1. There's + # another call to time.time() outside the loop, which makes it + # 13. + self.assertEqual(attempts, 13) + + # group_subscribe() should not have been called here. + self.assertIsNone(cc_sub) + + global cc_socket_file + cc_socket_file = None + cc_sub = None + class DummySession(): + def __init__(self, socket_file): + global cc_socket_file + cc_socket_file = socket_file + def group_subscribe(self, s): + global cc_sub + cc_sub = s + + isc.cc.Session = DummySession + + # reset values + attempts = 0 + tsec = 0 + + pi = bob.start_msgq() + + # just one attempt, but 2 calls to time.time() + self.assertEqual(attempts, 2) + + self.assertEqual(cc_socket_file, bob.msgq_socket_file) + self.assertEqual(cc_sub, 'Boss') + + # isc.cc.Session, time.time() and time.sleep() are restored + # during tearDown(). + + def _start_cfgmgr_helper(self, bob, data_path, filename, clear_config): + expect_args = ['b10-cfgmgr'] + if data_path is not None: + bob.data_path = data_path + expect_args.append('--data-path=' + data_path) + if filename is not None: + bob.config_filename = filename + expect_args.append('--config-filename=' + filename) + if clear_config: + bob.clear_config = clear_config + expect_args.append('--clear-config') + + pi = bob.start_cfgmgr() + self.assertEqual('b10-cfgmgr', pi.name) + self.assertEqual(expect_args, pi.args) + self.assertEqual({'TESTENV': 'A test string'}, pi.env) + + # this is set by ProcessInfo.spawn() + self.assertEqual(42147, pi.pid) + + def test_start_cfgmgr(self): + '''Test that b10-cfgmgr is started.''' + class DummySession(): + def __init__(self): + self._tries = 0 + def group_recvmsg(self): + self._tries += 1 + # return running on the 3rd try onwards + if self._tries >= 3: + return ({'running': 'ConfigManager'}, None) + else: + return ({}, None) + + bob = MockBobSimple() + bob.c_channel_env = {'TESTENV': 'A test string'} + bob.cc_session = DummySession() + bob.wait_time = 5 + + # use the MockProcessInfo creator + bob._make_process_info = bob._make_mock_process_info + + global attempts + attempts = 0 + self._tmp_sleep = time.sleep + def _my_sleep(nsec): + global attempts + attempts += 1 + time.sleep = _my_sleep + + # defaults + self._start_cfgmgr_helper(bob, None, None, False) + + # check that 2 attempts were made. on the 3rd attempt, + # process_running() returns that ConfigManager is running. + self.assertEqual(attempts, 2) + + # data_path is specified + self._start_cfgmgr_helper(bob, '/var/lib/test', None, False) + + # config_filename is specified. Because `bob` is not + # reconstructed, data_path is retained from the last call to + # _start_cfgmgr_helper(). + self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', False) + + # clear_config is specified. Because `bob` is not reconstructed, + # data_path and config_filename are retained from the last call + # to _start_cfgmgr_helper(). + self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', True) + + def test_start_cfgmgr_timeout(self): + '''Test that b10-cfgmgr startup attempts connections several times + and times out eventually.''' + class DummySession(): + def group_recvmsg(self): + return (None, None) + bob = MockBobSimple() + bob.c_channel_env = {} + bob.cc_session = DummySession() + # set wait_time to an arbitrary pre-determined value (which code + # below depends on) + bob.wait_time = 2 + + # use the MockProcessInfo creator + bob._make_process_info = bob._make_mock_process_info + + global attempts + attempts = 0 + self._tmp_sleep = time.sleep + def _my_sleep(nsec): + global attempts + attempts += 1 + time.sleep = _my_sleep + + # We just check that an exception was thrown, and that several + # attempts were made to connect. + with self.assertRaises(bind10_src.ProcessStartError): + pi = bob.start_cfgmgr() + + # 2 seconds of attempts every 1 second should result in 2 attempts + self.assertEqual(attempts, 2) + + # time.sleep() is restored during tearDown(). + + def test_start_ccsession(self): + '''Test that CC session is started.''' + class DummySession(): + def __init__(self, specfile, config_handler, command_handler, + socket_file): + self.specfile = specfile + self.config_handler = config_handler + self.command_handler = command_handler + self.socket_file = socket_file + self.started = False + def start(self): + self.started = True + bob = MockBobSimple() + self._tmp_module_cc_session = isc.config.ModuleCCSession + isc.config.ModuleCCSession = DummySession + + bob.start_ccsession({}) + self.assertEqual(bind10_src.SPECFILE_LOCATION, bob.ccs.specfile) + self.assertEqual(bob.config_handler, bob.ccs.config_handler) + self.assertEqual(bob.command_handler, bob.ccs.command_handler) + self.assertEqual(bob.msgq_socket_file, bob.ccs.socket_file) + self.assertTrue(bob.ccs.started) + + # isc.config.ModuleCCSession is restored during tearDown(). + + def test_start_process(self): + '''Test that processes can be started.''' + bob = MockBob() + + # use the MockProcessInfo creator + bob._make_process_info = bob._make_mock_process_info + + pi = bob.start_process('Test Process', ['/bin/true'], {}) + self.assertEqual('Test Process', pi.name) + self.assertEqual(['/bin/true'], pi.args) + self.assertEqual({}, pi.env) + + # this is set by ProcessInfo.spawn() + self.assertEqual(42147, pi.pid) + + def test_register_process(self): + '''Test that processes can be registered with BoB.''' + bob = MockBob() + component = MockComponent('test', 53, 'Test') + + self.assertFalse(53 in bob.components) + bob.register_process(53, component) + self.assertTrue(53 in bob.components) + self.assertEqual(bob.components[53].name(), 'test') + self.assertEqual(bob.components[53].pid(), 53) + self.assertEqual(bob.components[53].address(), 'Test') + + def _start_simple_helper(self, bob, verbose): + bob.verbose = verbose + + args = ['/bin/true'] + if verbose: + args.append('-v') + + bob.start_simple('/bin/true') + self.assertEqual('/bin/true', bob.started_process_name) + self.assertEqual(args, bob.started_process_args) + self.assertEqual({'TESTENV': 'A test string'}, bob.started_process_env) + + def test_start_simple(self): + '''Test simple process startup.''' + bob = MockBobSimple() + bob.c_channel_env = {'TESTENV': 'A test string'} + + # non-verbose case + self._start_simple_helper(bob, False) + + # verbose case + self._start_simple_helper(bob, True) + + def _start_auth_helper(self, bob, verbose): + bob.verbose = verbose + + args = ['b10-auth'] + if verbose: + args.append('-v') + + bob.start_auth() + self.assertEqual('b10-auth', bob.started_process_name) + self.assertEqual(args, bob.started_process_args) + self.assertEqual({'FOO': 'an env string'}, bob.started_process_env) + + def test_start_auth(self): + '''Test that b10-auth is started.''' + bob = MockBobSimple() + bob.c_channel_env = {'FOO': 'an env string'} + + # non-verbose case + self._start_auth_helper(bob, False) + + # verbose case + self._start_auth_helper(bob, True) + + def _start_resolver_helper(self, bob, verbose): + bob.verbose = verbose + + args = ['b10-resolver'] + if verbose: + args.append('-v') + + bob.start_resolver() + self.assertEqual('b10-resolver', bob.started_process_name) + self.assertEqual(args, bob.started_process_args) + self.assertEqual({'BAR': 'an env string'}, bob.started_process_env) + + def test_start_resolver(self): + '''Test that b10-resolver is started.''' + bob = MockBobSimple() + bob.c_channel_env = {'BAR': 'an env string'} + + # non-verbose case + self._start_resolver_helper(bob, False) + + # verbose case + self._start_resolver_helper(bob, True) + + def _start_cmdctl_helper(self, bob, verbose, port = None): + bob.verbose = verbose + + args = ['b10-cmdctl'] + + if port is not None: + bob.cmdctl_port = port + args.append('--port=9353') + + if verbose: + args.append('-v') + + bob.start_cmdctl() + self.assertEqual('b10-cmdctl', bob.started_process_name) + self.assertEqual(args, bob.started_process_args) + self.assertEqual({'BAZ': 'an env string'}, bob.started_process_env) + + def test_start_cmdctl(self): + '''Test that b10-cmdctl is started.''' + bob = MockBobSimple() + bob.c_channel_env = {'BAZ': 'an env string'} + + # non-verbose case + self._start_cmdctl_helper(bob, False) + + # verbose case + self._start_cmdctl_helper(bob, True) + + # with port, non-verbose case + self._start_cmdctl_helper(bob, False, 9353) + + # with port, verbose case + self._start_cmdctl_helper(bob, True, 9353) + + def test_socket_data(self): + '''Test that BoB._socket_data works as expected.''' + class MockSock: + def __init__(self, fd, throw): + self.fd = fd + self.throw = throw + self.buf = b'Hello World.\nYou are so nice today.\nXX' + self.i = 0 + + def recv(self, bufsize, flags = 0): + if bufsize != 1: + raise Exception('bufsize != 1') + if flags != socket.MSG_DONTWAIT: + raise Exception('flags != socket.MSG_DONTWAIT') + # after 15 recv()s, throw a socket.error with EAGAIN to + # get _socket_data() to save back what's been read. The + # number 15 is arbitrarily chosen, but the checks then + # depend on this being 15, i.e., if you adjust this + # number, you may have to adjust the checks below too. + if self.throw and self.i > 15: + raise socket.error(errno.EAGAIN, 'Try again') + if self.i >= len(self.buf): + return b''; + t = self.i + self.i += 1 + return self.buf[t:t+1] + + def close(self): + return + + class MockBobSocketData(BoB): + def __init__(self, throw): + self._unix_sockets = {42: (MockSock(42, throw), b'')} + self.requests = [] + self.dead = [] + + def socket_request_handler(self, previous, sock): + self.requests.append({sock.fd: previous}) + + def socket_consumer_dead(self, sock): + self.dead.append(sock.fd) + + # Case where we get data every time we call recv() + bob = MockBobSocketData(False) + bob._socket_data(42) + self.assertEqual(bob.requests, + [{42: b'Hello World.'}, + {42: b'You are so nice today.'}]) + self.assertEqual(bob.dead, [42]) + self.assertEqual({}, bob._unix_sockets) + + # Case where socket.recv() raises EAGAIN. In this case, the + # routine is supposed to save what it has back to + # BoB._unix_sockets. + bob = MockBobSocketData(True) + bob._socket_data(42) + self.assertEqual(bob.requests, [{42: b'Hello World.'}]) + self.assertFalse(bob.dead) + self.assertEqual(len(bob._unix_sockets), 1) + self.assertEqual(bob._unix_sockets[42][1], b'You') + + def test_startup(self): + '''Test that BoB.startup() handles failures properly.''' + class MockBobStartup(BoB): + def __init__(self, throw): + self.throw = throw + self.started = False + self.killed = False + self.msgq_socket_file = None + self.curproc = 'myproc' + self.runnable = False + + def start_all_components(self): + self.started = True + if self.throw: + raise Exception('Assume starting components has failed.') + + def kill_started_components(self): + self.killed = True + + class DummySession(): + def __init__(self, socket_file): + raise isc.cc.session.SessionError('This is the expected case.') + + class DummySessionSocketExists(): + def __init__(self, socket_file): + # simulate that connect passes + return + + isc.cc.Session = DummySession + + # All is well case, where all components are started + # successfully. We check that the actual call to + # start_all_components() is made, and BoB.runnable is true. + bob = MockBobStartup(False) + r = bob.startup() + self.assertIsNone(r) + self.assertTrue(bob.started) + self.assertFalse(bob.killed) + self.assertTrue(bob.runnable) + self.assertEqual({}, bob.c_channel_env) + + # Case where starting components fails. We check that + # kill_started_components() is called right after, and + # BoB.runnable is not modified. + bob = MockBobStartup(True) + r = bob.startup() + # r contains an error message + self.assertEqual(r, 'Unable to start myproc: Assume starting components has failed.') + self.assertTrue(bob.started) + self.assertTrue(bob.killed) + self.assertFalse(bob.runnable) + self.assertEqual({}, bob.c_channel_env) + + # Check if msgq_socket_file is carried over + bob = MockBobStartup(False) + bob.msgq_socket_file = 'foo' + r = bob.startup() + self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env) + + # Check the case when socket file already exists + isc.cc.Session = DummySessionSocketExists + bob = MockBobStartup(False) + r = bob.startup() + self.assertIn('already running', r) + + # isc.cc.Session is restored during tearDown(). class SocketSrvTest(unittest.TestCase): """ @@ -1531,6 +2305,25 @@ class TestFunctions(unittest.TestCase): # second call should not assert anyway bind10_src.remove_lock_files() + def test_get_signame(self): + # just test with some samples + signame = bind10_src.get_signame(signal.SIGTERM) + self.assertEqual('SIGTERM', signame) + signame = bind10_src.get_signame(signal.SIGKILL) + self.assertEqual('SIGKILL', signame) + # 59426 is hopefully an unused signal on most platforms + signame = bind10_src.get_signame(59426) + self.assertEqual('Unknown signal 59426', signame) + + def test_fatal_signal(self): + self.assertIsNone(bind10_src.boss_of_bind) + bind10_src.boss_of_bind = BoB() + bind10_src.boss_of_bind.runnable = True + bind10_src.fatal_signal(signal.SIGTERM, None) + # Now, runnable must be False + self.assertFalse(bind10_src.boss_of_bind.runnable) + bind10_src.boss_of_bind = None + if __name__ == '__main__': # store os.environ for test_unchanged_environment original_os_environ = copy.deepcopy(os.environ) diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py index b4e71bf182..37b74e7fd7 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/moduleinfo.py b/src/bin/bindctl/moduleinfo.py index 6c3a3044eb..33114d8027 100644 --- a/src/bin/bindctl/moduleinfo.py +++ b/src/bin/bindctl/moduleinfo.py @@ -221,7 +221,8 @@ class ModuleInfo: def module_help(self): """Prints the help info for this module to stdout""" - print("Module ", self, "\nAvailable commands:") + print("Module " + str(self)) + print("Available commands:") for k in self.commands.values(): n = k.get_name() if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH: diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py index f598472ce0..5c6aeb2804 100644 --- a/src/bin/bindctl/tests/bindctl_test.py +++ b/src/bin/bindctl/tests/bindctl_test.py @@ -364,16 +364,24 @@ class TestConfigCommands(unittest.TestCase): socket_err_output = io.StringIO() sys.stdout = socket_err_output self.assertEqual(1, self.tool.run()) - self.assertEqual("Failed to send request, the connection is closed\n", - socket_err_output.getvalue()) + + # First few lines may be some kind of heading, or a warning that + # Python readline is unavailable, so we do a sub-string check. + self.assertIn("Failed to send request, the connection is closed", + socket_err_output.getvalue()) + socket_err_output.close() # validate log message for http.client.CannotSendRequest cannot_send_output = io.StringIO() sys.stdout = cannot_send_output self.assertEqual(1, self.tool.run()) - self.assertEqual("Can not send request, the connection is busy\n", - cannot_send_output.getvalue()) + + # First few lines may be some kind of heading, or a warning that + # Python readline is unavailable, so we do a sub-string check. + self.assertIn("Can not send request, the connection is busy", + cannot_send_output.getvalue()) + cannot_send_output.close() def test_apply_cfg_command_int(self): @@ -511,10 +519,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 e9e0ccaf85..9c73f79ee6 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/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in index a3763c9373..315e3c515b 100755 --- a/src/bin/cfgmgr/b10-cfgmgr.py.in +++ b/src/bin/cfgmgr/b10-cfgmgr.py.in @@ -27,7 +27,7 @@ import glob import os.path import imp import isc.log -isc.log.init("b10-cfgmgr") +isc.log.init("b10-cfgmgr", buffer=True) from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError, logger from isc.log_messages.cfgmgr_messages import * diff --git a/src/bin/cfgmgr/local_plugins/.gitignore b/src/bin/cfgmgr/local_plugins/.gitignore new file mode 100644 index 0000000000..724690c573 --- /dev/null +++ b/src/bin/cfgmgr/local_plugins/.gitignore @@ -0,0 +1 @@ +/datasrc.spec diff --git a/src/bin/cfgmgr/local_plugins/Makefile.am b/src/bin/cfgmgr/local_plugins/Makefile.am new file mode 100644 index 0000000000..2f4dd39767 --- /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 e6ed1275c8..5967abd4ac 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 f2c6a974a2..6d5bd77e75 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/cmdctl/.gitignore b/src/bin/cmdctl/.gitignore index 01e3ef054e..e702e49ab8 100644 --- a/src/bin/cmdctl/.gitignore +++ b/src/bin/cmdctl/.gitignore @@ -1,6 +1,10 @@ +/b10-certgen +/b10-certgen.1 /b10-cmdctl +/b10-cmdctl.8 +/cmdctl-certfile.pem +/cmdctl-keyfile.pem /cmdctl.py /cmdctl.spec /cmdctl.spec.pre /run_b10-cmdctl.sh -/b10-cmdctl.8 diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am index 3b88f4b6c3..bfc13af41f 100644 --- a/src/bin/cmdctl/Makefile.am +++ b/src/bin/cmdctl/Makefile.am @@ -4,6 +4,8 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@ pkglibexec_SCRIPTS = b10-cmdctl +bin_PROGRAMS = b10-certgen + nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py pylogmessagedir = $(pyexecdir)/isc/log_messages/ @@ -25,15 +27,18 @@ CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc -man_MANS = b10-cmdctl.8 -DISTCLEANFILES = $(man_MANS) -EXTRA_DIST += $(man_MANS) b10-cmdctl.xml cmdctl_messages.mes +man_MANS = b10-cmdctl.8 b10-certgen.1 +DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem +EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes if GENERATE_DOCS b10-cmdctl.8: b10-cmdctl.xml @XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-cmdctl.xml +b10-certgen.1: b10-certgen.xml + @XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-certgen.xml + else $(man_MANS): @@ -54,12 +59,23 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py $(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@ chmod a+x $@ +b10_certgen_SOURCES = b10-certgen.cc +b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES) +b10_certgen_LDFLAGS = $(BOTAN_LIBS) + +# Generate the initial certificates immediately +cmdctl-certfile.pem: b10-certgen + ./b10-certgen -q -w + +cmdctl-keyfile.pem: b10-certgen + ./b10-certgen -q -w + if INSTALL_CONFIGURATIONS # Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA) # because these file will contain sensitive information. install-data-local: - $(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@ + $(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@ for f in $(CMDCTL_CONFIGURATIONS) ; do \ if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then \ ${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ; \ diff --git a/src/bin/cmdctl/b10-certgen.cc b/src/bin/cmdctl/b10-certgen.cc new file mode 100644 index 0000000000..579ae60e19 --- /dev/null +++ b/src/bin/cmdctl/b10-certgen.cc @@ -0,0 +1,429 @@ +// 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 +using namespace Botan; + +#include +#include +#include +#include + +// For cleaner 'does not exist or is not readable' output than +// botan provides +#include +#include + +// This is a simple tool that creates a self-signed PEM certificate +// for use with BIND 10. It creates a simple certificate for initial +// setup. Currently, all values are hardcoded defaults. For future +// versions, we may want to add more options for administrators. + +// It will create a PEM file containing a certificate with the following +// values: +// common name: localhost +// organization: BIND10 +// country code: US + +// Additional error return codes; these are specifically +// chosen to be distinct from validation error codes as +// provided by Botan. Their main use is to distinguish +// error cases in the unit tests. +const int DECODING_ERROR = 100; +const int BAD_OPTIONS = 101; +const int READ_ERROR = 102; +const int WRITE_ERROR = 103; +const int UNKNOWN_ERROR = 104; +const int NO_SUCH_FILE = 105; +const int FILE_PERMISSION_ERROR = 106; + +void +usage() { + std::cout << "Usage: b10-certgen [OPTION]..." << std::endl; + std::cout << "Validate, create, or update a self-signed certificate for " + "use with b10-cmdctl" << std::endl; + std::cout << "" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate" + << std::endl; + std::cout << "-f, --force\t\t\toverwrite existing certficate even if it" + << std::endl <<"\t\t\t\tis valid" << std::endl; + std::cout << "-h, --help\t\t\tshow this help" << std::endl; + std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key" + << std::endl; + std::cout << "-w, --write\t\t\tcreate a new certificate if the given file" + << std::endl << "\t\t\t\tdoes not exist, or if is is not valid" + << std::endl; + std::cout << "-q, --quiet\t\t\tprint no output when creating or validating" + << std::endl; +} + +/// \brief Returns true if the given file exists +/// +/// \param filename The file to check +/// \return true if file exists +bool +fileExists(const std::string& filename) { + return (access(filename.c_str(), F_OK) == 0); +} + +/// \brief Returns true if the given file exists and is readable +/// +/// \param filename The file to check +/// \return true if file exists and is readable +bool +fileIsReadable(const std::string& filename) { + return (access(filename.c_str(), R_OK) == 0); +} + +/// \brief Returns true if the given file exists and is writable +/// +/// \param filename The file to check +/// \return true if file exists and is writable +bool +fileIsWritable(const std::string& filename) { + return (access(filename.c_str(), W_OK) == 0); +} + +/// \brief Helper function for readable error output; +/// +/// Returns string representation of X509 result code +/// This does not appear to be provided by Botan itself +/// +/// \param code An \c X509_Code instance +/// \return A human-readable c string +const char* +X509CodeToString(const X509_Code& code) { + // note that this list provides more than we would + // need in this context, it is just the enum from + // the source code of Botan. + switch (code) { + case VERIFIED: + return ("verified"); + case UNKNOWN_X509_ERROR: + return ("unknown x509 error"); + case CANNOT_ESTABLISH_TRUST: + return ("cannot establish trust"); + case CERT_CHAIN_TOO_LONG: + return ("certificate chain too long"); + case SIGNATURE_ERROR: + return ("signature error"); + case POLICY_ERROR: + return ("policy error"); + case INVALID_USAGE: + return ("invalid usage"); + case CERT_FORMAT_ERROR: + return ("certificate format error"); + case CERT_ISSUER_NOT_FOUND: + return ("certificate issuer not found"); + case CERT_NOT_YET_VALID: + return ("certificate not yet valid"); + case CERT_HAS_EXPIRED: + return ("certificate has expired"); + case CERT_IS_REVOKED: + return ("certificate has been revoked"); + case CRL_FORMAT_ERROR: + return ("crl format error"); + case CRL_NOT_YET_VALID: + return ("crl not yet valid"); + case CRL_HAS_EXPIRED: + return ("crl has expired"); + case CA_CERT_CANNOT_SIGN: + return ("CA cert cannot sign"); + case CA_CERT_NOT_FOR_CERT_ISSUER: + return ("CA certificate not for certificate issuer"); + case CA_CERT_NOT_FOR_CRL_ISSUER: + return ("CA certificate not for crl issuer"); + default: + return ("Unknown X509 code"); + } +} + +class CertificateTool { +public: + CertificateTool(bool quiet) : quiet_(quiet) {} + + int + createKeyAndCertificate(const std::string& key_file_name, + const std::string& cert_file_name) { + try { + AutoSeeded_RNG rng; + + // Create and store a private key + print("Creating key file " + key_file_name); + RSA_PrivateKey key(rng, 2048); + std::ofstream key_file(key_file_name.c_str()); + if (!key_file.good()) { + print(std::string("Error writing to ") + key_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + key_file << PKCS8::PEM_encode(key, rng, ""); + if (!key_file.good()) { + print(std::string("Error writing to ") + key_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + key_file.close(); + + // Certificate options, currently hardcoded. + // For a future version we may want to make these + // settable. + X509_Cert_Options opts; + opts.common_name = "localhost"; + opts.organization = "UNKNOWN"; + opts.country = "XX"; + + opts.CA_key(); + + print("Creating certificate file " + cert_file_name); + + // The exact call changed aftert 1.8, adding the + // hash function option +#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0) + X509_Certificate cert = + X509::create_self_signed_cert(opts, key, "SHA-256", rng); +#else + X509_Certificate cert = + X509::create_self_signed_cert(opts, key, rng); +#endif + + std::ofstream cert_file(cert_file_name.c_str()); + if (!cert_file.good()) { + print(std::string("Error writing to ") + cert_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + cert_file << cert.PEM_encode(); + if (!cert_file.good()) { + print(std::string("Error writing to ") + cert_file_name + + ": " + std::strerror(errno)); + return (WRITE_ERROR); + } + cert_file.close(); + } catch(std::exception& e) { + std::cout << "Error creating key or certificate: " << e.what() + << std::endl; + return (UNKNOWN_ERROR); + } + return (0); + } + + int + validateCertificate(const std::string& certfile) { + // Since we are dealing with a self-signed certificate here, we + // also use the certificate to check itself; i.e. we add it + // as a trusted certificate, then validate the certficate itself. + //const X509_Certificate cert(certfile); + try { + X509_Store store; + DataSource_Stream in(certfile); + store.add_trusted_certs(in); + + const X509_Code result = store.validate_cert(certfile); + + if (result == VERIFIED) { + print(certfile + " is valid"); + } else { + print(certfile + " failed to verify: " + + X509CodeToString(result)); + } + return (result); + } catch (const Botan::Decoding_Error& bde) { + print(certfile + " failed to verify: " + bde.what()); + return (DECODING_ERROR); + } catch (const Botan::Stream_IO_Error& bsie) { + print(certfile + " not read: " + bsie.what()); + return (READ_ERROR); + } + } + + /// \brief Runs the tool + /// + /// \param create_cert Create certificate if true, validate if false. + /// Does nothing if certificate exists and is valid. + /// \param force_create Create new certificate even if it is valid. + /// \param certfile Certificate file to read to or write from. + /// \param keyfile Key file to write if certificate is created. + /// Ignored if create_cert is false + /// \return zero on success, non-zero on failure + int + run(bool create_cert, bool force_create, const std::string& certfile, + const std::string& keyfile) + { + if (create_cert) { + // Unless force is given, only create it if the current + // one is not OK + + // First do some basic permission checks; both files + // should either not exist, or be both readable + // and writable + // The checks are done one by one so all errors can + // be enumerated in one go + if (fileExists(certfile)) { + if (!fileIsReadable(certfile)) { + print(certfile + " not readable: " + std::strerror(errno)); + create_cert = false; + } + if (!fileIsWritable(certfile)) { + print(certfile + " not writable: " + std::strerror(errno)); + create_cert = false; + } + } + // The key file really only needs write permissions (for + // b10-certgen that is) + if (fileExists(keyfile)) { + if (!fileIsWritable(keyfile)) { + print(keyfile + " not writable: " + std::strerror(errno)); + create_cert = false; + } + } + if (!create_cert) { + print("Not creating new certificate, " + "check file permissions"); + return (FILE_PERMISSION_ERROR); + } + + // If we reach this, we know that if they exist, we can both + // read and write them, so now it's up to content checking + // and/or force_create + + if (force_create || !fileExists(certfile) || + validateCertificate(certfile) != VERIFIED) { + return (createKeyAndCertificate(keyfile, certfile)); + } else { + print("Not creating a new certificate (use -f to force)"); + } + } else { + if (!fileExists(certfile)) { + print(certfile + ": " + std::strerror(errno)); + return (NO_SUCH_FILE); + } + if (!fileIsReadable(certfile)) { + print(certfile + " not readable: " + std::strerror(errno)); + return (FILE_PERMISSION_ERROR); + } + int result = validateCertificate(certfile); + if (result != 0) { + print("Running with -w would overwrite the certificate"); + } + return (result); + } + return (0); + } +private: + /// Prints the message to stdout unless quiet_ is true + void print(const std::string& msg) { + if (!quiet_) { + std::cout << msg << std::endl; + } + } + + bool quiet_; +}; + +int +main(int argc, char* argv[]) +{ + Botan::LibraryInitializer init; + + // create or check certificate + bool create_cert = false; + // force creation even if not necessary + bool force_create = false; + // don't print any output + bool quiet = false; + + // default certificate file + std::string certfile("cmdctl-certfile.pem"); + // default key file + std::string keyfile("cmdctl-keyfile.pem"); + + // whether or not the above values have been + // overridden (used in command line checking) + bool certfile_default = true; + bool keyfile_default = true; + + // It would appear some environments insist on + // char* here (Sunstudio on Solaris), so we const_cast + // them to get rid of compiler warnings. + const struct option long_options[] = { + { const_cast("certfile"), required_argument, NULL, 'c' }, + { const_cast("force"), no_argument, NULL, 'f' }, + { const_cast("help"), no_argument, NULL, 'h' }, + { const_cast("keyfile"), required_argument, NULL, 'k' }, + { const_cast("write"), no_argument, NULL, 'w' }, + { const_cast("quiet"), no_argument, NULL, 'q' }, + { NULL, 0, NULL, 0 } + }; + + int opt, option_index; + while ((opt = getopt_long(argc, argv, "c:fhk:wq", long_options, + &option_index)) != -1) { + switch (opt) { + case 'c': + certfile = optarg; + certfile_default = false; + break; + case 'f': + force_create = true; + break; + case 'h': + usage(); + return (0); + break; + case 'k': + keyfile = optarg; + keyfile_default = false; + break; + case 'w': + create_cert = true; + break; + case 'q': + quiet = true; + break; + default: + // A message will have already been output about the error. + return (BAD_OPTIONS); + } + } + + if (optind < argc) { + std::cout << "Error: extraneous arguments" << std::endl << std::endl; + usage(); + return (BAD_OPTIONS); + } + + // Some sanity checks on option combinations + if (create_cert && (certfile_default ^ keyfile_default)) { + std::cout << "Error: keyfile and certfile must both be specified " + "if one of them is when calling b10-certgen in write " + "mode." << std::endl; + return (BAD_OPTIONS); + } + if (!create_cert && !keyfile_default) { + std::cout << "Error: keyfile is not used when not in write mode" + << std::endl; + return (BAD_OPTIONS); + } + + // Initialize the tool and perform the appropriate action(s) + CertificateTool tool(quiet); + return (tool.run(create_cert, force_create, certfile, keyfile)); +} diff --git a/src/bin/cmdctl/b10-certgen.xml b/src/bin/cmdctl/b10-certgen.xml new file mode 100644 index 0000000000..1e3c8e3bbb --- /dev/null +++ b/src/bin/cmdctl/b10-certgen.xml @@ -0,0 +1,214 @@ +]> + + + + + + November 15, 2012 + + + + b10-certgen + 1 + BIND10 + + + + b10-certgen + X509 Certificate generation tool for use with b10-cmdctl + + + + + 2012 + Internet Systems Consortium, Inc. ("ISC") + + + + + + b10-certgen + + + + + + + + DESCRIPTION + The b10-certgen tool validates, creates, or + updates a self-signed X509 certificate for use in b10-cmdctl. + + + + The connection between bindctl and + b10-cmdctl is done over HTTPS, and therefore + b10-cmdctl needs a certificate. Since these + certificates have expiry dates, they also need to be regenerated at + some point. + + There are many tools to do so, but for ease of use, + b10-certgen can create a simple self-signed certificate. + + By default, it will not create anything, but it will merely check an + existing certificate (if not specified, cmdctl-certfile.pem, in the + current working directory). And print whether it is valid, and + whether it would update if the option '-w' is given. + + With that option, the certificate could then be replaced by a newly + created one. If the certificate is still valid, it would still not + be overwritten (however, if it is found to be invalid, for example + because it has expired, it would create a new one). + + A new certificate is always created if the certificate file does + not exist, or if creation is forced (with the -f option). + + + + + ARGUMENTS + + The arguments are as follows: + + + + + + , + + + + + File to read the certificate from, or write the certificate to. + If and are used, + is mandatory as well. + + + + + + , + + + + + Force updating of certificate when is used, + even if the existing certificate is still valid. + + + + + + , + + + + + Print the command line arguments and exit. + + + + + + , + + + + + File to write the private key to. This option is only valid when is used, and if this option is used, is mandatory as well. + + + + + + , + + + + + Check the given certificate file. If it does not exist, a new + private key and certificate are created. If it does exist, the + certificate is validated. If it is not valid (for instance + because it has expired), it is overwritten with a newly created + certificate. If it is valid, nothing happens (use + to force an update in that case). + + + + + + , + + + + + Don't print informational messages (only command-line errors are + printed). Useful in scripts when only the return code is needed. + + + + + + + + SEE ALSO + + + b10-cmdctl8 + , + BIND 10 Guide. + + + + + HISTORY + + The b10-certgen tool was first implemented + in November 2012 for the ISC BIND 10 project. + + + + + EXAMPLE + + To update an expired certificate in BIND 10 that has been installed to + /usr/local: + +$> cd /usr/local/etc/bind10-devel/ + +$> b10-certgen +cmdctl-certfile.pem failed to verify: certificate has expired +Running with -w would overwrite the certificate + +$> b10-certgen --write +cmdctl-certfile.pem failed to verify: certificate has expired +Creating key file cmdctl-keyfile.pem +Creating certificate file cmdctl-certfile.pem + +$> b10-certgen --write +cmdctl-certfile.pem is valid +Not creating a new certificate (use -f to force) + + + + diff --git a/src/bin/cmdctl/cmdctl-keyfile.pem b/src/bin/cmdctl/cmdctl-keyfile.pem deleted file mode 100644 index 8fff2dcdad..0000000000 --- a/src/bin/cmdctl/cmdctl-keyfile.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDpICWxJGKMvUhLFPbf5n8ZWogqjYcQqqoHqHVRHYjyiey6FZdt -ZkY2s1gYh0G0NXtimlIgic+vEcFe7vdmyKntW7DYDaqAj0KrED7RKAj8324jNbSJ -HtLP4evvJep3vsoNtTvNuceQJ46vukxyxgg3DuC9kVqPuD8CZ1Rq4ATyiwIDAQAB -AoGBAOJlOtV+DUq6Y2Ou91VXRiU8GzKgAQP5iWgoe84Ljbxkn4XThBxVD2j94Fbp -u7AjpDCMx6cbzpoo9w6XqaGizAmAehIfTE3eFYs74N/FM09Wg2OSDyxMY0jgyECU -A4ukjlPwcGDbmgbmlY3i+FVHp+zCgtZEsMC1IAosMac1BoX5AkEA/lrXWaVtH8bo -mut3GBaXvubZMdaUr0BUd5a9q+tt4dQcKG1kFqgCNKhNhBIcpiMVcz+jGmOuopNA -8dnUGqv3FQJBAOqiJ54ZvOTWNDpJIe02wIXRxRmc1xhHFCqYP23KxBVrAcTYB19J -lesov/hEbnGLCbKS/naZJ1zrTImUPNRLqx8CQCzDtA7U7GWhTiKluioFH+O7IRKC -X1yQh80cPHlbT9VkzSfYSLssCmdWD35k6aHbntTPqFbmoD+AhveJjKi9BxkCQDwX -1c+/RcrSNcQr0N2hZUOgyztZGRnlsnuKTMyA3yGhK23P6mt0PEpjQG+Ej0jTVGOB -FF0pspQwy4R9C+tPif8CQH36NNlXBfVNmT7kDtyLmaE6pID0vY9duX56BJbU1R0x -SQ8/LcfJagk6gvp08OyYCPA+WZ7u/bas9R/nMTCLivc= ------END RSA PRIVATE KEY----- diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in index 4076f4cbbb..52af54ad3c 100755 --- a/src/bin/cmdctl/cmdctl.py.in +++ b/src/bin/cmdctl/cmdctl.py.in @@ -49,7 +49,7 @@ from hashlib import sha1 from isc.util import socketserver_mixin from isc.log_messages.cmdctl_messages import * -isc.log.init("b10-cmdctl") +isc.log.init("b10-cmdctl", buffer=True) logger = isc.log.Logger("cmdctl") # Debug level for communication with BIND10 diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am index b5b65f6b33..6d8f282229 100644 --- a/src/bin/cmdctl/tests/Makefile.am +++ b/src/bin/cmdctl/tests/Makefile.am @@ -1,6 +1,9 @@ PYCOVERAGE_RUN=@PYCOVERAGE_RUN@ -PYTESTS = cmdctl_test.py +PYTESTS = cmdctl_test.py b10-certgen_test.py EXTRA_DIST = $(PYTESTS) +EXTRA_DIST += testdata/expired-certfile.pem +EXTRA_DIST += testdata/mangled-certfile.pem +EXTRA_DIST += testdata/noca-certfile.pem # If necessary (rare cases), explicitly specify paths to dynamic libraries # required by loadable python modules. @@ -9,10 +12,12 @@ if SET_ENV_LIBRARY_PATH LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH) endif +CLEANFILES = test-keyfile.pem test-certfile.pem + # test using command-line arguments, so use check-local target instead of TESTS check-local: if ENABLE_PYTHON_COVERAGE - touch $(abs_top_srcdir)/.coverage + touch $(abs_top_srcdir)/.coverage rm -f .coverage ${LN_S} $(abs_top_srcdir)/.coverage .coverage endif diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/b10-certgen_test.py new file mode 100644 index 0000000000..d54efa3fdd --- /dev/null +++ b/src/bin/cmdctl/tests/b10-certgen_test.py @@ -0,0 +1,254 @@ +# Copyright (C) 2012 Internet Systems Consortium. +# +# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM +# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# INTERNET SYSTEMS CONSORTIUM 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. + +# Note: the main code is in C++, but what we are mostly testing is +# options and behaviour (output/file creation, etc), which is easier +# to test in python. + +import unittest +import os +from subprocess import call +import subprocess +import ssl +import stat + +def run(command): + """ + Small helper function that returns a tuple of (rcode, stdout, stderr) after + running the given command (an array of command and arguments, as passed on + to subprocess). + """ + subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = subp.communicate() + return (subp.returncode, stdout, stderr) + +class FileDeleterContext: + """ + Simple Context Manager that deletes a given set of files when the context + is left. + """ + def __init__(self, files): + self.files = files + + def __enter__(self): + pass + + def __exit__(self, type, value, traceback): + for f in self.files: + if os.path.exists(f): + os.unlink(f) + +class FilePermissionContext: + """ + Simple Context Manager that temporarily modifies file permissions for + a given file + """ + def __init__(self, f, unset_flags = [], set_flags = []): + """ + Initialize file permission context. + See the stat module for possible flags to set or unset. + The flags are changed when the context is entered (i.e. + you can create the context first without any change) + The flags are changed back when the context is left. + + Parameters: + f: string, file to change permissions for + unset_flags: list of flags to unset + set_flags: list of flags to set + """ + self.file = f + self.orig_mode = os.stat(f).st_mode + new_mode = self.orig_mode + for flag in unset_flags: + new_mode = new_mode & ~flag + for flag in set_flags: + new_mode = new_mode | flag + self.new_mode = new_mode + + def __enter__(self): + os.chmod(self.file, self.new_mode) + + def __exit__(self, type, value, traceback): + os.chmod(self.file, self.orig_mode) + +def read_file_data(filename): + """ + Simple text file reader that returns its contents as an array + """ + with open(filename) as f: + return f.readlines() + +class TestCertGenTool(unittest.TestCase): + TOOL = '../b10-certgen' + + def run_check(self, expected_returncode, expected_stdout, expected_stderr, command): + """ + Runs the given command, and checks return code, and outputs (if provided). + Arguments: + expected_returncode, return code of the command + expected_stdout, (multiline) string that is checked agains stdout. + May be None, in which case the check is skipped. + expected_stderr, (multiline) string that is checked agains stderr. + May be None, in which case the check is skipped. + """ + (returncode, stdout, stderr) = run(command) + self.assertEqual(expected_returncode, returncode, " ".join(command)) + if expected_stdout is not None: + self.assertEqual(expected_stdout, stdout.decode()) + if expected_stderr is not None: + self.assertEqual(expected_stderr, stderr.decode()) + + def validate_certificate(self, expected_result, certfile): + """ + Validate a certificate, using the quiet option of the tool; it runs + the check option (-c) for the given base name of the certificate (-f + ), and compares the return code to the given + expected_result value + """ + self.run_check(expected_result, '', '', + [self.TOOL, '-q', '-c', certfile]) + # Same with long options + self.run_check(expected_result, '', '', + [self.TOOL, '--quiet', '--certfile', certfile]) + + + def test_basic_creation(self): + """ + Tests whether basic creation with no arguments (except output + file name) successfully creates a key and certificate + """ + keyfile = 'test-keyfile.pem' + certfile = 'test-certfile.pem' + command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ] + self.creation_helper(command, certfile, keyfile) + # Do same with long options + command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ] + self.creation_helper(command, certfile, keyfile) + + def creation_helper(self, command, certfile, keyfile): + """ + Helper method for test_basic_creation. + Performs the actual checks + """ + with FileDeleterContext([keyfile, certfile]): + self.assertFalse(os.path.exists(keyfile)) + self.assertFalse(os.path.exists(certfile)) + self.run_check(0, '', '', command) + self.assertTrue(os.path.exists(keyfile)) + self.assertTrue(os.path.exists(certfile)) + + # Validate the certificate that was just created + self.validate_certificate(0, certfile) + + # When run with the same options, it should *not* create it again, + # as the current certificate should still be valid + certdata = read_file_data(certfile) + keydata = read_file_data(keyfile) + + self.run_check(0, '', '', command) + + self.assertEqual(certdata, read_file_data(certfile)) + self.assertEqual(keydata, read_file_data(keyfile)) + + # but if we add -f, it should force a new creation + command.append('-f') + self.run_check(0, '', '', command) + self.assertNotEqual(certdata, read_file_data(certfile)) + self.assertNotEqual(keydata, read_file_data(keyfile)) + + def test_check_bad_certificates(self): + """ + Tests a few pre-created certificates with the -c option + """ + if ('CMDCTL_SRC_PATH' in os.environ): + path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/" + else: + path = "testdata/" + self.validate_certificate(10, path + 'expired-certfile.pem') + self.validate_certificate(100, path + 'mangled-certfile.pem') + self.validate_certificate(17, path + 'noca-certfile.pem') + + def test_bad_options(self): + """ + Tests some combinations of commands that should fail. + """ + # specify -c but not -k + self.run_check(101, + 'Error: keyfile and certfile must both be specified ' + 'if one of them is when calling b10-certgen in write ' + 'mode.\n', + '', [self.TOOL, '-w', '-c', 'foo']) + self.run_check(101, + 'Error: keyfile and certfile must both be specified ' + 'if one of them is when calling b10-certgen in write ' + 'mode.\n', + '', [self.TOOL, '-w', '-k', 'foo']) + self.run_check(101, + 'Error: keyfile is not used when not in write mode\n', + '', [self.TOOL, '-k', 'foo']) + # Extraneous argument + self.run_check(101, None, None, [self.TOOL, 'foo']) + # No such file + self.run_check(105, None, None, [self.TOOL, '-c', 'foo']) + + def test_permissions(self): + """ + Test some combinations of correct and bad permissions. + """ + keyfile = 'mod-keyfile.pem' + certfile = 'mod-certfile.pem' + command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ] + # Delete them at the end + with FileDeleterContext([keyfile, certfile]): + # Create the two files first + self.run_check(0, '', '', command) + self.validate_certificate(0, certfile) + + # Make the key file unwritable + with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]): + self.run_check(106, '', '', command) + # Should have no effect on validation + self.validate_certificate(0, certfile) + + # Make the cert file unwritable + with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]): + self.run_check(106, '', '', command) + # Should have no effect on validation + self.validate_certificate(0, certfile) + + # Make the key file unreadable (this should not matter) + with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]): + self.run_check(0, '', '', command) + + # unreadable key file should also not have any effect on + # validation + self.validate_certificate(0, certfile) + + # Make the cert file unreadable (this should matter) + with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]): + self.run_check(106, '', '', command) + + # Unreadable cert file should also fail validation + self.validate_certificate(106, certfile) + + # Not directly a permission problem, but trying to check or create + # in a nonexistent directory returns different error codes + self.validate_certificate(105, 'fakedir/cert') + self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c', + 'fakedir/cert', '-k', 'fakedir/key' ]) + +if __name__== '__main__': + unittest.main() + diff --git a/src/bin/cmdctl/cmdctl-certfile.pem b/src/bin/cmdctl/tests/testdata/expired-certfile.pem similarity index 100% rename from src/bin/cmdctl/cmdctl-certfile.pem rename to src/bin/cmdctl/tests/testdata/expired-certfile.pem diff --git a/src/bin/cmdctl/tests/testdata/mangled-certfile.pem b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem new file mode 100644 index 0000000000..7c47fda511 --- /dev/null +++ b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD +VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG +A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu +MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy +NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi +ZWlqaW5nMraWDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE +CxMFY25uaWMxeZaRBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po +YW5nbGlrdW5AY25UAwMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg +JbEkYoy9SEsU9t/mfxLAICqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka +UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21 +O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O +BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw +tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp +amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT +BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu +Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG +9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV +jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz +EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA== +-----END CERTIFICATE----- diff --git a/src/bin/cmdctl/tests/testdata/noca-certfile.pem b/src/bin/cmdctl/tests/testdata/noca-certfile.pem new file mode 100644 index 0000000000..56368696cf --- /dev/null +++ b/src/bin/cmdctl/tests/testdata/noca-certfile.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBjCCAe6gAwIBAgIRALUIj3nnW5uDE/+fglPvUDwwDQYJKoZIhvcNAQELBQAw +HjELMAkGA1UEBhMCVVMxDzANBgNVBAMTBkJJTkQxMDAeFw0xMjExMTQxMjQ5MjVa +Fw0xMzExMTQxMjQ5MjVaMB4xCzAJBgNVBAYTAlVTMQ8wDQYDVQQDEwZCSU5EMTAw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkIOPfs3Aw9kNDu1JqA2w3 +84/n9oUgAwAlHVmuJv7ZDw1MDaIKHjsh3DW09z+nv67GVksI7pFtAw5O4mnTDxpa +JT0NKzhvYGfe8VdV/hWDogTIdk1QBJNZ2/id8z0h8z5001sARXPf+4mHBJslenH3 +YtZs22BG5RBLULtZ/2Nr7JkdfLlc6D5PCoDG22r1OiFkYVdCWfLDjisVIbSYPBtY +BlKAIrvbmOtWcaGM+vQAhl0T5N8WRCKhaQH0DEmzQNckkYd7rSECo57KYiuvOdzp +d+3bWTgGGy2ff0o3LZypv0O5s0TDC2H6hYtN4bUbcChUJbFu9b5sVZaOEVZtUsyD +AgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQW +BBSqGzsEDNs9E7gBL5pD6XVAwUo4DTANBgkqhkiG9w0BAQsFAAOCAQEAMTNB8NCU +dnLFZ0jNpvecbECkX/OWGlBYU4/CsoNiibwp4CtUYS2A4NFVjWAyuzLSHhRQi0vJ +CCWLpKL4VTkaDN5Oft42iUhvEXMnriJqpfXHnjCiBwFFSPl5WKfMIaRNK+tF4zbB +F+FGNEEmYG3t/ni82orDLq4oy+7CoQwzZNzj5yoV6q7O9kLR9OMPNwJrc27A4erB +7VMRZslSrNA4uA6YhMZl8iEvO1H801ct0zTxawrCihPOZOCSLew35xjztO7d3YH8 +YavOu5kzeu7AgZ2n75H/qU47ZgBjbonn9Osvrct+RIwZuWTB2bDML8JhNaZCq0aA +TDBC0QWqIYypLg== +-----END CERTIFICATE----- diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index 35314e818e..d60f18678c 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/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in index eaeb06c79e..094e0ecdb8 100755 --- a/src/bin/ddns/ddns.py.in +++ b/src/bin/ddns/ddns.py.in @@ -45,7 +45,7 @@ import os.path import signal import socket -isc.log.init("b10-ddns") +isc.log.init("b10-ddns", buffer=True) logger = isc.log.Logger("ddns") TRACE_BASIC = logger.DBGLVL_TRACE_BASIC diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am index 3939915115..c8965911d7 100644 --- a/src/bin/dhcp4/Makefile.am +++ b/src/bin/dhcp4/Makefile.am @@ -58,7 +58,7 @@ b10_dhcp4_CXXFLAGS = -Wno-unused-parameter endif b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la -b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la +b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc index 08ea894336..aa2fa4f3d8 100644 --- a/src/bin/dhcp4/config_parser.cc +++ b/src/bin/dhcp4/config_parser.cc @@ -13,7 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include +#include #include #include #include diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index c9366af833..20eedebc05 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -17,18 +17,20 @@ #include #include #include -#include #include +#include #include #include #include #include -#include #include #include #include #include +#include +#include + using namespace isc::asiolink; using namespace isc::cc; using namespace isc::config; diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 76c5d6d1be..c1a26cc557 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -15,11 +15,11 @@ #ifndef CTRL_DHCPV4_SRV_H #define CTRL_DHCPV4_SRV_H -#include #include +#include #include #include -#include +#include namespace isc { namespace dhcp { diff --git a/src/bin/dhcp4/dhcp4_log.cc b/src/bin/dhcp4/dhcp4_log.cc index 678223b134..32c5a47a34 100644 --- a/src/bin/dhcp4/dhcp4_log.cc +++ b/src/bin/dhcp4/dhcp4_log.cc @@ -14,7 +14,7 @@ /// Defines the logger used by the top-level component of b10-dhcp4. -#include "dhcp4_log.h" +#include namespace isc { namespace dhcp { diff --git a/src/bin/dhcp4/dhcp4_log.h b/src/bin/dhcp4/dhcp4_log.h index 3717b62d92..07d009a982 100644 --- a/src/bin/dhcp4/dhcp4_log.h +++ b/src/bin/dhcp4/dhcp4_log.h @@ -12,11 +12,11 @@ // 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 +#include #include namespace isc { @@ -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/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 88b0435a7e..994f004dbc 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -62,17 +62,17 @@ server is about to open sockets on the specified port. The IPv4 DHCP server has received a packet that it is unable to interpret. The reason why the packet is invalid is included in the message. -% DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1 -The IPv4 DHCP server tried to receive a packet but an error -occured during this attempt. The reason for the error is included in -the message. - % DHCP4_PACKET_RECEIVED %1 (type %2) packet received on interface %3 A debug message noting that the server has received the specified type of packet on the specified interface. 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 a received OFFER packet as UNKNOWN). +% DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1 +The IPv4 DHCP server tried to receive a packet but an error +occured during this attempt. The reason for the error is included in +the message. + % DHCP4_PACKET_SEND_FAIL failed to send DHCPv4 packet: %1 This error is output if the IPv4 DHCP server fails to send an assembled DHCP message to a client. The reason for the error is included in the diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index aa79ce9cb4..59edc01fe1 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -12,13 +12,13 @@ // 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 +#include +#include +#include using namespace isc; using namespace isc::asiolink; diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 10470e41b6..724b351f56 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -18,7 +18,9 @@ #include #include #include + #include + #include namespace isc { diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc index 31f0b0251b..45f1de1612 100644 --- a/src/bin/dhcp4/main.cc +++ b/src/bin/dhcp4/main.cc @@ -13,13 +13,15 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include - -#include #include #include #include +#include + +#include + +#include using namespace isc::dhcp; using namespace std; @@ -92,9 +94,10 @@ main(int argc, char* argv[]) { } // Initialize logging. If verbose, we'll use maximum verbosity. + // If standalone is enabled, do not buffer initial log messages isc::log::initLogger(DHCP4_NAME, (verbose_mode ? isc::log::DEBUG : isc::log::INFO), - isc::log::MAX_DEBUG_LEVEL, NULL); + isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone); LOG_INFO(dhcp4_logger, DHCP4_STARTING); LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO) .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no") @@ -111,6 +114,10 @@ main(int argc, char* argv[]) { LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what()); // Let's continue. It is useful to have the ability to run // DHCP server in stand-alone mode, e.g. for testing + // We do need to make sure logging is no longer buffered + // since then it would not print until dhcp6 is stopped + isc::log::LoggerManager log_manager; + log_manager.process(); } } else { LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE); diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index 5dd55b35d0..e60191993d 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -67,7 +67,7 @@ dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) dhcp4_unittests_LDADD = $(GTEST_LDADD) dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la -dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la +dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index c27999686b..22307df664 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -20,8 +20,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc index 5af0cc9857..fd4e90eaa3 100644 --- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc @@ -13,16 +13,18 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include + +#include +#include +#include + +#include + #include +#include #include #include -#include - -#include -#include -#include using namespace std; using namespace isc; diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index fca06ad988..ac6f0bbf79 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -13,17 +13,19 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include -#include #include -#include +#include +#include +#include +#include + #include -#include -#include -#include -#include +#include +#include + +#include using namespace std; using namespace isc; diff --git a/src/bin/dhcp4/tests/dhcp4_test.py b/src/bin/dhcp4/tests/dhcp4_test.py index 444dfcfd2a..e493e044a3 100644 --- a/src/bin/dhcp4/tests/dhcp4_test.py +++ b/src/bin/dhcp4/tests/dhcp4_test.py @@ -45,11 +45,30 @@ class TestDhcpv4Daemon(unittest.TestCase): def tearDown(self): pass + def readPipe(self, pipe_fd): + """ + Reads bytes from a pipe and returns a character string. If nothing is + read, or if there is an error, an empty string is returned. + + pipe_fd - Pipe file descriptor to read + """ + try: + data = os.read(pipe_fd, 16384) + # Make sure we have a string + if (data is None): + data = "" + else: + data = str(data) + except OSError: + data = "" + + return data + def runCommand(self, params, wait=1): """ - This method runs dhcp4 and returns a tuple: (returncode, stdout, stderr) + This method runs a command and returns a tuple: (returncode, stdout, stderr) """ - ## @todo: Convert this into generic method and reuse it in dhcp6 + ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6 print("Running command: %s" % (" ".join(params))) @@ -89,46 +108,48 @@ class TestDhcpv4Daemon(unittest.TestCase): fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) - # There's potential problem if b10-dhcp4 prints out more - # than 16kB of text - try: - output = os.read(self.stdout_pipes[0], 16384) - except OSError: - print("No data available from stdout") - output = "" + # As we don't know how long the subprocess will take to start and + # produce output, we'll loop and sleep for 250 ms between each + # iteration. To avoid an infinite loop, we'll loop for a maximum + # of five seconds: that should be enough. + for count in range(20): + # Read something from stderr and stdout (these reads don't block). + output = self.readPipe(self.stdout_pipes[0]) + error = self.readPipe(self.stderr_pipes[0]) - # read can return None. Make sure we have a string - if (output is None): - output = "" + # If the process has already exited, or if it has output something, + # quit the loop now. + if pi.process.poll() is not None or len(error) > 0 or len(output) > 0: + break - try: - error = os.read(self.stderr_pipes[0], 16384) - except OSError: - print("No data available on stderr") - error = "" + # Process still running, try again in 250 ms. + time.sleep(0.25) - # read can return None. Make sure we have a string - if (error is None): - error = "" - - - try: - if (not pi.process.poll()): - # let's be nice at first... + # Exited loop, kill the process if it is still running + if pi.process.poll() is None: + try: pi.process.terminate() - except OSError: - print("Ignoring failed kill attempt. Process is dead already.") + except OSError: + print("Ignoring failed kill attempt. Process is dead already.") # call this to get returncode, process should be dead by now rc = pi.process.wait() # Clean up our stdout/stderr munging. os.dup2(self.stdout_old, sys.stdout.fileno()) + os.close(self.stdout_old) os.close(self.stdout_pipes[0]) os.dup2(self.stderr_old, sys.stderr.fileno()) + os.close(self.stderr_old) os.close(self.stderr_pipes[0]) + # Free up resources (file descriptors) from the ProcessInfo object + # TODO: For some reason, this gives an error if the process has ended, + # although it does cause all descriptors still allocated to the + # object to be freed. + pi = None + print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes" % (rc, len(output), len(error)) ) diff --git a/src/bin/dhcp4/tests/dhcp4_unittests.cc b/src/bin/dhcp4/tests/dhcp4_unittests.cc index ebc72fbc92..0ed61b7a40 100644 --- a/src/bin/dhcp4/tests/dhcp4_unittests.cc +++ b/src/bin/dhcp4/tests/dhcp4_unittests.cc @@ -12,10 +12,10 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include -#include #include +#include + int main(int argc, char* argv[]) { diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am index 68aadea5ad..1d9766f51e 100644 --- a/src/bin/dhcp6/Makefile.am +++ b/src/bin/dhcp6/Makefile.am @@ -60,10 +60,11 @@ 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 -b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la +b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc index 486cfd4655..3db6aeceea 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 @@ -12,25 +12,30 @@ // 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 #include #include -#include -#include -#include -#include -#include +#include #include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include using namespace std; using namespace isc::data; @@ -39,13 +44,13 @@ using namespace isc::asiolink; namespace isc { namespace dhcp { -/// @brief auxiliary type used for storing element name and its parser +/// @brief an auxiliary type used for storing an element name and its parser typedef pair ConfigPair; /// @brief a factory method that will create a parser for a given element name typedef DhcpConfigParser* ParserFactory(const std::string& config_id); -/// @brief a collection of factories that creates parsers for specified element names +/// @brief a collection of factories that create parsers for specified element names typedef std::map FactoryMap; /// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900) @@ -56,21 +61,29 @@ typedef std::map StringStorage; /// @brief a collection of pools /// -/// That type is used as intermediate storage, when pools are parsed, but there is +/// This type is used as intermediate storage, when pools are parsed, but there is /// no subnet object created yet to store them. typedef std::vector PoolStorage; +/// @brief Collection of option descriptors. This container allows searching for +/// options using the option code or persistency flag. This is useful when merging +/// existing options with newly configured options. +typedef Subnet::OptionContainer 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, +/// This is a debugging parser. It does not configure anything, /// will accept any configuration and will just print it out -/// on commit. Useful for debugging existing configurations and +/// on commit. Useful for debugging existing configurations and /// adding new ones. class DebugParser : public DhcpConfigParser { public: @@ -97,7 +110,7 @@ public: /// @brief pretends to apply the configuration /// - /// This is a method required by base class. It pretends to apply the + /// This is a method required by the base class. It pretends to apply the /// configuration, but in fact it only prints the parameter out. /// /// See \ref DhcpConfigParser class for details. @@ -135,6 +148,9 @@ private: /// /// For overview of usability of this generic purpose parser, see /// \ref dhcp6-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,25 +167,51 @@ 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."); } + + // If a given parameter already exists in the storage we override + // its value. If it doesn't we insert a new element. (*storage_)[param_name_] = value_; } /// @brief does nothing /// - /// This method is required for all parser. The value itself + /// This method is required for all parsers. The value itself /// is not commited anywhere. Higher level parsers are expected to /// use values stored in the storage, e.g. renew-timer for a given /// subnet is stored in subnet-specific storage. It is not commited /// here, but is rather used by \ref Subnet6Parser when constructing /// the subnet. - virtual void commit() { - } + virtual void commit() { } /// @brief factory that constructs Uint32Parser objects /// @@ -201,9 +243,9 @@ private: /// @brief Configuration parser for string parameters /// /// This class is a generic parser that is able to handle any string -/// parameter. By default it stores the value in external global container +/// parameter. By default it stores the value in an external global container /// (string_defaults). If used in smaller scopes (e.g. to parse parameters -/// in subnet config), it can be pointed to a different storage, using +/// in subnet config), it can be pointed to a different storage, using the /// setStorage() method. This class follows the parser interface, laid out /// in its base class, \ref DhcpConfigParser. /// @@ -220,26 +262,28 @@ public: /// @brief parses parameter value /// - /// Parses configuration entry and stored it in storage. See + /// Parses configuration entry and stores it in storage. See /// \ref setStorage() for details. /// /// @param value pointer to the content of parsed values virtual void build(ConstElementPtr value) { value_ = value->str(); boost::erase_all(value_, "\""); + + // If a given parameter already exists in the storage we override + // its value. If it doesn't we insert a new element. (*storage_)[param_name_] = value_; } /// @brief does nothing /// - /// This method is required for all parser. The value itself + /// This method is required for all parsers. The value itself /// is not commited anywhere. Higher level parsers are expected to /// use values stored in the storage, e.g. renew-timer for a given /// subnet is stored in subnet-specific storage. It is not commited /// here, but is rather used by its parent parser when constructing /// an object, e.g. the subnet. - virtual void commit() { - } + virtual void commit() { } /// @brief factory that constructs StringParser objects /// @@ -331,7 +375,7 @@ private: /// and stored in chosen PoolStorage container. /// /// As there are no default values for pool, setStorage() must be called -/// before build(). Otherwise exception will be thrown. +/// before build(). Otherwise an exception will be thrown. /// /// It is useful for parsing Dhcp6/subnet6[X]/pool parameters. class PoolParser : public DhcpConfigParser { @@ -360,7 +404,7 @@ public: BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) { // That should be a single pool representation. It should contain - // text is form prefix/len or first - last. Note that spaces + // text in the form prefix/len or first - last. Note that spaces // are allowed string txt = text_pool->stringValue(); @@ -379,7 +423,7 @@ public: // start with the first character after / string prefix_len = txt.substr(pos + 1); - // It is lexical cast to int and then downcast to uint8_t. + // It is lexically cast to int and then downcast to uint8_t. // Direct cast to uint8_t (which is really an unsigned char) // will result in interpreting the first digit as output // value and throwing exception if length is written on two @@ -428,7 +472,7 @@ public: /// @brief does nothing. /// - /// This method is required for all parser. The value itself + /// This method is required for all parsers. The value itself /// is not commited anywhere. Higher level parsers (for subnet) are expected /// to use values stored in the storage. virtual void commit() {} @@ -443,11 +487,349 @@ public: private: /// @brief pointer to the actual Pools storage /// - /// That is typically a storage somewhere in Subnet parser + /// This is typically a storage somewhere in Subnet parser /// (an upper level parser). 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 an +/// instance of an option is created and added to the storage provided +/// by the calling class. +/// +/// @todo This class parses and validates the 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), + // initialize option to NULL ptr + option_descriptor_(false) { } + + /// @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 Commits option value. + /// + /// This function adds a new option to the storage or replaces an existing option + /// with the same code. + /// + /// @throw isc::InvalidOperation if failed to set pointer to storage or failed + /// to call build() prior to commit. If that happens data in the storage + /// remain un-modified. + virtual void commit() { + if (options_ == NULL) { + isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before " + "commiting option data."); + } else if (!option_descriptor_.option) { + // Before we can commit the new option should be configured. If it is not + // than somebody must have called commit() before build(). + isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and" + " thus there is nothing to commit. Has build() been called?"); + } + uint16_t opt_type = option_descriptor_.option->getType(); + Subnet::OptionContainerTypeIndex& idx = options_->get<1>(); + // Try to find options with the particular option code in the main + // storage. If found, remove these options because they will be + // replaced with new one. + Subnet::OptionContainerTypeRange range = + idx.equal_range(opt_type); + if (std::distance(range.first, range.second) > 0) { + idx.erase(range.first, range.second); + } + // Append new option to the main storage. + options_->push_back(option_descriptor_); + } + + /// @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 this function emits an exception. + /// + /// @warning this function does not check if options_ storage pointer + /// is intitialized but this check is not needed here because it is done + /// in the \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 that 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 an error will be issued if an 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)); + // The created option is stored in option_descriptor_ class member until the + // commit stage when it is inserted into the main storage. If an option with the + // same code exists in main storage already the old option is replaced. + option_descriptor_.option = option; + option_descriptor_.persistent = false; + } else { + // We have exactly one option definition for the particular option code + // use it to create the option instance. + const OptionDefinitionPtr& def = *(range.first); + try { + OptionPtr option = def->optionFactory(Option::V6, option_code, binary); + Subnet::OptionDescriptor desc(option, false); + option_descriptor_.option = option; + option_descriptor_.persistent = false; + } 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_; + /// Option descriptor holds newly configured option. + Subnet::OptionDescriptor option_descriptor_; +}; + +/// @brief Parser for option data values within a 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 container (option_default). That storage location + /// is overriden on a subnet basis. + OptionDataListParser(const std::string&) + : options_(&option_defaults), local_options_() { } + + /// @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(&local_options_); + // Build the instance of a single option. + parser->build(option_value); + // Store a parser as it will be used to commit. + parsers_.push_back(parser); + } + } + + /// @brief Set storage for option instances. + /// + /// @param storage pointer to options storage. + void setStorage(OptionStorage* storage) { + options_ = storage; + } + + + /// @brief Commit all option values. + /// + /// This function invokes commit for all option values. + void commit() { + BOOST_FOREACH(ParserPtr parser, parsers_) { + parser->commit(); + } + // Parsing was successful and we have all configured + // options in local storage. We can now replace old values + // with new values. + std::swap(local_options_, *options_); + } + + /// @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)); + } + + /// Intermediate option storage. This storage is used by + /// lower level parsers to add new options. Values held + /// in this storage are assigned to main storage (options_) + /// if overall parsing was successful. + OptionStorage local_options_; + /// Pointer to options instances storage. + OptionStorage* options_; + /// Collection of parsers; + ParserCollection parsers_; +}; + /// @brief this class parses a single subnet /// /// This class parses the whole subnet definition. It creates parsers @@ -457,8 +839,8 @@ public: /// @brief constructor Subnet6ConfigParser(const std::string& ) { - // The parameter should always be "subnet", but we don't check here - // against it in case some wants to reuse this parser somewhere. + // The parameter should always be "subnet", but we don't check + // against that here in case some wants to reuse this parser somewhere. } /// @brief parses parameter value @@ -467,35 +849,36 @@ public: void build(ConstElementPtr subnet) { BOOST_FOREACH(ConfigPair param, subnet->mapValue()) { - ParserPtr parser(createSubnet6ConfigParser(param.first)); + // The actual type of the parser is unknown here. We have to discover + // the parser type here to invoke the 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. - // 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_); - } - } + // 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 } @@ -506,6 +889,10 @@ public: /// created in other parsers are used here and added to newly created Subnet6 /// objects. Subnet6 are then added to DHCP CfgMgr. void commit() { + // Invoke commit on all sub-data parsers. + BOOST_FOREACH(ParserPtr parser, parsers_) { + parser->commit(); + } StringStorage::const_iterator it = string_values_.find("subnet"); if (it == string_values_.end()) { @@ -543,11 +930,79 @@ public: subnet->addPool6(*it); } + const Subnet::OptionContainer& options = subnet->getOptions(); + const Subnet::OptionContainerTypeIndex& idx = options.get<1>(); + + // Add subnet specific options. + BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) { + Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType()); + if (std::distance(range.first, range.second) > 0) { + LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE) + .arg(desc.option->getType()).arg(addr.toText()); + } + subnet->addOption(desc.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 precedence. + BOOST_FOREACH(Subnet::OptionDescriptor desc, 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(desc.option->getType()); + // @todo: In the future we will be searching for options using either + // an 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 the 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 a warning about dropping the configuration of + // a global option if one already exsists. + if (std::distance(range.first, range.second) == 0) { + subnet->addOption(desc.option); + } + } + CfgMgr::instance().addSubnet6(subnet); } private: + /// @brief Set storage for a parser and invoke build. + /// + /// This helper method casts the provided parser pointer to the specified + /// type. If the cast is successful it sets the corresponding storage for + /// this parser, invokes build on it and saves 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 /// /// @todo Add subnet-specific things here (e.g. subnet-specific options) @@ -573,6 +1028,10 @@ private: 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. @@ -587,7 +1046,7 @@ private: /// @brief returns value for a given parameter (after using inheritance) /// - /// This method implements inheritance. For a given parameter name, it first + /// This method implements inheritance. For a given parameter name, it first /// checks if there is a global value for it and overwrites it with specific /// value if such value was defined in subnet. /// @@ -627,11 +1086,14 @@ private: /// storage for pools belonging to this subnet PoolStorage pools_; + /// storage for options belonging to this subnet + OptionStorage options_; + /// parsers are stored here ParserCollection parsers_; }; -/// @brief this class parses list of subnets +/// @brief this class parses a list of subnets /// /// This is a wrapper parser that handles the whole list of Subnet6 /// definitions. It iterates over all entries and creates Subnet6ConfigParser @@ -647,7 +1109,7 @@ public: /// @brief parses contents of the list /// - /// Iterates over all entries on the list and creates Subnet6ConfigParser + /// Iterates over all entries on the list and creates a Subnet6ConfigParser /// for each entry. /// /// @param subnets_list pointer to a list of IPv6 subnets @@ -704,7 +1166,6 @@ public: DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) { FactoryMap factories; - // factories.insert(pair( "preferred-lifetime", Uint32Parser::Factory)); factories.insert(pair( @@ -719,6 +1180,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)); diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h index f8fe76c82f..ed44bb9e3b 100644 --- a/src/bin/dhcp6/config_parser.h +++ b/src/bin/dhcp6/config_parser.h @@ -12,15 +12,15 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include -#include -#include - #ifndef DHCP6_CONFIG_PARSER_H #define DHCP6_CONFIG_PARSER_H /// @todo: This header file and its .cc counterpart are very similar between -/// DHCPv4 and DHCPv6. They should be merged. A ticket #2355. +/// DHCPv4 and DHCPv6. They should be merged. See ticket #2355. + +#include +#include +#include namespace isc { namespace dhcp { diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index 7370583d68..5d4d9904ca 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -14,22 +14,22 @@ #include -#include -#include - #include #include #include #include #include +#include +#include #include #include #include -#include -#include #include #include +#include +#include + using namespace isc::asiolink; using namespace isc::cc; using namespace isc::config; @@ -42,6 +42,7 @@ using namespace std; namespace isc { namespace dhcp { + ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL; ConstElementPtr @@ -149,8 +150,8 @@ void ControlledDhcpv6Srv::disconnectSession() { IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL); } -ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port /*= DHCP6_SERVER_PORT*/) - :Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) { +ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port, const char* dbconfig) + : Dhcpv6Srv(port, dbconfig), cc_session_(NULL), config_session_(NULL) { server_ = this; // remember this instance for use in callback } diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index 91fc80acb2..22cb3b65c6 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h @@ -15,11 +15,11 @@ #ifndef CTRL_DHCPV6_SRV_H #define CTRL_DHCPV6_SRV_H -#include #include +#include #include #include -#include +#include namespace isc { namespace dhcp { @@ -41,7 +41,9 @@ public: /// @brief Constructor /// /// @param port UDP port to be opened for DHCP traffic - ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT); + /// @param dbconfig Lease manager database configuration string + ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT, + const char* dbconfig = "type=memfile"); /// @brief Destructor. ~ControlledDhcpv6Srv(); diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox index e34f9a5072..7e9204a9e5 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 dhcp6-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 @@ -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 f35f606213..c5e9565f5f 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.cc b/src/bin/dhcp6/dhcp6_log.cc index d89383492c..875f595aeb 100644 --- a/src/bin/dhcp6/dhcp6_log.cc +++ b/src/bin/dhcp6/dhcp6_log.cc @@ -14,7 +14,7 @@ /// Defines the logger used by the top-level component of b10-dhcp6. -#include "dhcp6_log.h" +#include namespace isc { namespace dhcp { diff --git a/src/bin/dhcp6/dhcp6_log.h b/src/bin/dhcp6/dhcp6_log.h index 6d7f4e33b0..23202da6a4 100644 --- a/src/bin/dhcp6/dhcp6_log.h +++ b/src/bin/dhcp6/dhcp6_log.h @@ -12,12 +12,12 @@ // 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 #include +#include +#include namespace isc { namespace dhcp { @@ -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 2399c19036..6ab42b3c96 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -22,14 +22,77 @@ successfully established a session with the BIND 10 control channel. This debug message is issued just before the IPv6 DHCP server attempts to establish a session with the BIND 10 control channel. +% DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped +This error message indicates that the received message is being dropped +because it does not include the mandatory client-id option necessary for +address assignment. The most likely cause is a problem with the client. + % DHCP6_COMMAND_RECEIVED received command %1, arguments: %2 A debug message listing the command (and possible arguments) received from the BIND 10 control system by the IPv6 DHCP server. +% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1 +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_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 +served until the configuration has been corrected. + +% 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. + +% 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. + +% DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1 +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_UPDATE updated configuration received: %1 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 (type: %1, name: %2) +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_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_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 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_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_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3 +This message indicates that received DHCPv6 packet is invalid. This may be due +to a number of reasons, e.g. the mandatory client-id option is missing, +the server-id forbidden in that particular type of message is present, +there is more than one instance of client-id or server-id present, +etc. The exact reason for rejecting the packet is included in the message. + % 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. @@ -38,6 +101,18 @@ IPv6 DHCP server but it is not running. During startup the IPv6 DHCP server failed to detect any network interfaces and is therefore shutting down. +% 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_OPEN_SOCKET opening sockets on port %1 A debug message issued during startup, this indicates that the IPv6 DHCP server is about to open sockets on the specified port. @@ -45,17 +120,17 @@ server is about to open sockets on the specified port. % DHCP6_PACKET_PARSE_FAIL failed to parse incoming packet The IPv6 DHCP server has received a packet that it is unable to interpret. -% DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1 -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 a received OFFER packet as UNKNOWN). +% DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1 +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_SEND_FAIL failed to send DHCPv6 packet: %1 This error is output if the IPv6 DHCP server fails to send an assembled DHCP message to a client. The reason for the error is included in the @@ -66,10 +141,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,22 +190,29 @@ 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_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 -served until the configuration has been corrected. +% 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_CONFIG_START DHCPv6 server is processing the following configuration: %1 -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_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_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. +% DHCP6_UNKNOWN_RENEW received RENEW from client (duid=%1, iaid=%2) in subnet %3 +This warning message is printed when client attempts to renew a lease, +but no such lease is known by the server. It typically means that +client has attempted to use its lease past its lifetime: causes of this +include a adjustment of the clients date/time setting or poor support +for sleep/recovery. A properly implemented client will recover from such +a situation by restarting the lease allocation process after receiving +a negative reply from the server. -% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1 -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. +An alternative cause could be that the server has lost its database +recently and does not recognize its well-behaving clients. This is more +probable if you see many such messages. Clients will recover from this, +but they will most likely get a different IP addresses and experience +a brief service interruption. diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 7c57a6139c..2482833cc3 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -12,72 +12,87 @@ // 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 +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include +#include + +#include +#include + using namespace isc; using namespace isc::asiolink; 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, const char* dbconfig) + : alloc_engine_(), serverid_(), shutdown_(true) { 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; + // 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); + return; + } + IfaceMgr::instance().openSockets6(port); } - IfaceMgr::instance().openSockets6(port); - setServerID(); - /// @todo: instantiate LeaseMgr here once it is imlpemented. + // Instantiate LeaseMgr + LeaseMgrFactory::create(dbconfig); + LOG_INFO(dhcp6_logger, DHCP6_DB_BACKEND_STARTED) + .arg(LeaseMgrFactory::instance().getType()) + .arg(LeaseMgrFactory::instance().getName()); + + // Instantiate allocation engine + alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)); } catch (const std::exception &e) { LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what()); - shutdown_ = true; return; } + // All done, so can proceed shutdown_ = false; } Dhcpv6Srv::~Dhcpv6Srv() { IfaceMgr::instance().closeSockets(); + + LeaseMgrFactory::destroy(); } void Dhcpv6Srv::shutdown() { @@ -87,7 +102,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,51 +127,57 @@ bool Dhcpv6Srv::run() { continue; } LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED) - .arg(serverReceivedPacketName(query->getType())) - .arg(query->getType()); + .arg(query->getName()); 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()); - switch (query->getType()) { - case DHCPV6_SOLICIT: - rsp = processSolicit(query); - break; + try { + switch (query->getType()) { + case DHCPV6_SOLICIT: + rsp = processSolicit(query); + break; - case DHCPV6_REQUEST: - rsp = processRequest(query); - break; + case DHCPV6_REQUEST: + rsp = processRequest(query); + break; - case DHCPV6_RENEW: - rsp = processRenew(query); - break; + case DHCPV6_RENEW: + rsp = processRenew(query); + break; - case DHCPV6_REBIND: - rsp = processRebind(query); - break; + case DHCPV6_REBIND: + rsp = processRebind(query); + break; - case DHCPV6_CONFIRM: - rsp = processConfirm(query); - break; + case DHCPV6_CONFIRM: + rsp = processConfirm(query); + break; - case DHCPV6_RELEASE: + case DHCPV6_RELEASE: rsp = processRelease(query); break; - case DHCPV6_DECLINE: - rsp = processDecline(query); - break; + case DHCPV6_DECLINE: + rsp = processDecline(query); + break; - case DHCPV6_INFORMATION_REQUEST: - rsp = processInfRequest(query); - break; + case DHCPV6_INFORMATION_REQUEST: + rsp = processInfRequest(query); + break; - default: - // Only action is to output a message if debug is enabled, - // and that will be covered by the debug statement before - // the "switch" statement. - ; + default: + // Only action is to output a message if debug is enabled, + // and that will be covered by the debug statement before + // the "switch" statement. + ; + } + } catch (const RFCViolation& e) { + LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL) + .arg(query->getName()) + .arg(query->getRemoteAddr()) + .arg(e.what()); } if (rsp) { @@ -177,9 +203,6 @@ bool Dhcpv6Srv::run() { } } } - - // TODO add support for config session (see src/bin/auth/main.cc) - // so this daemon can be controlled from bob } return (true); @@ -196,7 +219,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 +240,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 +266,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 - (The site-wide master_addr and master_port configurations are deprecated; use the zones list configuration instead.) - diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py index 7b9100ef88..b7fe056294 100644 --- a/src/bin/xfrin/tests/xfrin_test.py +++ b/src/bin/xfrin/tests/xfrin_test.py @@ -26,6 +26,7 @@ from xfrin import * import xfrin from isc.xfrin.diff import Diff import isc.log +from isc.server_common.tsig_keyring import init_keyring, get_keyring # If we use any python library that is basically a wrapper for # a library we use as well (like sqlite3 in our datasources), # we must make sure we import ours first; If we have special @@ -139,6 +140,16 @@ class MockCC(MockModuleCCSession): if identifier == "zones/use_ixfr": return False + def add_remote_config_by_name(self, name, callback): + pass + + def get_remote_config_value(self, module, identifier): + if module == 'tsig_keys' and identifier == 'keys': + return (['example.com.key.:EvAAsfU2h7uofnmqaTCrhHunGsc='], True) + else: + raise Exception('MockCC requested for unknown config value ' + + + module + "/" + identifier) + def remove_remote_config(self, module_name): pass @@ -229,6 +240,7 @@ class MockXfrin(Xfrin): def _cc_setup(self): self._tsig_key = None self._module_cc = MockCC() + init_keyring(self._module_cc) pass def _get_db_file(self): @@ -570,7 +582,7 @@ class TestXfrinIXFRAdd(TestXfrinState): # difference, starting with removing that SOA. self.conn._diff.add_data(self.ns_rrset) # put some dummy change self.conn._tsig_ctx = MockTSIGContext(TSIG_KEY) - self.conn._tsig_ctx.last_has_signature = lambda: False + self.conn._tsig_ctx.last_had_signature = lambda: False # First, push a starting SOA inside. This should be OK, nothing checked # yet. self.state.handle_rr(self.conn, self.begin_soa) @@ -821,7 +833,7 @@ class TestAXFR(TestXfrinConnection): mock_ctx = MockTSIGContext(key) mock_ctx.error = error if not has_last_signature: - mock_ctx.last_has_signature = lambda: False + mock_ctx.last_had_signature = lambda: False return mock_ctx def __match_exception(self, expected_exception, expected_msg, expression): @@ -2427,9 +2439,10 @@ class TestXfrin(unittest.TestCase): self.assertEqual(str(zone_info.master_addr), zone_config['master_addr']) self.assertEqual(zone_info.master_port, zone_config['master_port']) if 'tsig_key' in zone_config: - self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text()) + self.assertEqual(zone_info.tsig_key_name.to_text(), + Name(zone_config['tsig_key']).to_text()) else: - self.assertIsNone(zone_info.tsig_key) + self.assertIsNone(zone_info.tsig_key_name) if 'use_ixfr' in zone_config and\ zone_config.get('use_ixfr'): self.assertTrue(zone_info.use_ixfr) @@ -2562,7 +2575,7 @@ class TestXfrin(unittest.TestCase): { 'name': 'test2.example.', 'master_addr': '192.0.2.9', 'master_port': 53, - 'tsig_key': 'badkey' + 'tsig_key': 'badkey..' } ]} self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1) @@ -2581,13 +2594,14 @@ class TestXfrin(unittest.TestCase): self.assertEqual(self.xfr.config_handler(config)['result'][0], 0) self._check_zones_config(config) - def common_ixfr_setup(self, xfr_mode, use_ixfr): + def common_ixfr_setup(self, xfr_mode, use_ixfr, tsig_key_str = None): # This helper method explicitly sets up a zone configuration with # use_ixfr, and invokes either retransfer or refresh. # Shared by some of the following test cases. config = {'zones': [ {'name': 'example.com.', 'master_addr': '192.0.2.1', + 'tsig_key': tsig_key_str, 'use_ixfr': use_ixfr}]} self.assertEqual(self.xfr.config_handler(config)['result'][0], 0) self.assertEqual(self.xfr.command_handler(xfr_mode, @@ -2603,6 +2617,34 @@ class TestXfrin(unittest.TestCase): self.common_ixfr_setup('refresh', True) self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type) + def test_command_handler_retransfer_with_tsig(self): + self.common_ixfr_setup('retransfer', False, 'example.com.key') + self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type) + + def test_command_handler_retransfer_with_tsig_bad_key(self): + # bad keys should not reach xfrin, but should they somehow, + # they are ignored (and result in 'key not found' + error log). + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'retransfer', False, 'bad.key') + + def test_command_handler_retransfer_with_tsig_unknown_key(self): + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'retransfer', False, 'no.such.key') + + def test_command_handler_refresh_with_tsig(self): + self.common_ixfr_setup('refresh', False, 'example.com.key') + self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type) + + def test_command_handler_refresh_with_tsig_bad_key(self): + # bad keys should not reach xfrin, but should they somehow, + # they are ignored (and result in 'key not found' + error log). + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'refresh', False, 'bad.key') + + def test_command_handler_refresh_with_tsig_unknown_key(self): + self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup, + 'refresh', False, 'no.such.key') + def test_command_handler_retransfer_ixfr_disabled(self): # Similar to the previous case, but explicitly disabled. AXFR should # be used. diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in index 2b65311baa..da0f20702e 100755 --- a/src/bin/xfrin/xfrin.py.in +++ b/src/bin/xfrin/xfrin.py.in @@ -34,9 +34,10 @@ from isc.datasrc import DataSourceClient, ZoneFinder import isc.net.parse from isc.xfrin.diff import Diff from isc.server_common.auth_command import auth_loadzone_command +from isc.server_common.tsig_keyring import init_keyring, get_keyring from isc.log_messages.xfrin_messages import * -isc.log.init("b10-xfrin") +isc.log.init("b10-xfrin", buffer=True) logger = isc.log.Logger("xfrin") # Pending system-wide debug level definitions, the ones we @@ -69,7 +70,10 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec" AUTH_MODULE_NAME = 'Auth' XFROUT_MODULE_NAME = 'Xfrout' + +# Remote module and identifiers (according to their spec files) ZONE_MANAGER_MODULE_NAME = 'Zonemgr' + REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr' # Constants for debug levels. @@ -797,7 +801,7 @@ class XfrinConnection(asyncore.dispatcher): Check there's a signature at the last message. """ if self._tsig_ctx is not None: - if not self._tsig_ctx.last_has_signature(): + if not self._tsig_ctx.last_had_signature(): raise XfrinProtocolError('TSIG verify fail: no TSIG on last '+ 'message') @@ -1179,7 +1183,7 @@ class ZoneInfo: self.set_master_port(config_data.get('master_port')) self.set_zone_class(config_data.get('class')) - self.set_tsig_key(config_data.get('tsig_key')) + self.set_tsig_key_name(config_data.get('tsig_key')) self.set_use_ixfr(config_data.get('use_ixfr')) def set_name(self, name_str): @@ -1240,20 +1244,32 @@ class ZoneInfo: errmsg = "invalid zone class: " + zone_class_str raise XfrinZoneInfoException(errmsg) - def set_tsig_key(self, tsig_key_str): - """Set the tsig_key for this zone, given a TSIG key string - representation. If tsig_key_str is None, no TSIG key will - be set. Raises XfrinZoneInfoException if tsig_key_str cannot - be parsed.""" + def set_tsig_key_name(self, tsig_key_str): + """Set the name of the tsig_key for this zone. If tsig_key_str + is None, no TSIG key will be used. This name is used to + find the TSIG key to use for transfers in the global TSIG + key ring. + Raises XfrinZoneInfoException if tsig_key_str is not a valid + (dns) name.""" if tsig_key_str is None: - self.tsig_key = None + self.tsig_key_name = None else: + # can throw a number of exceptions but it is just one + # call, so Exception should be OK here try: - self.tsig_key = TSIGKey(tsig_key_str) - except InvalidParameter as ipe: - logger.error(XFRIN_BAD_TSIG_KEY_STRING, tsig_key_str) - errmsg = "bad TSIG key string: " + tsig_key_str - raise XfrinZoneInfoException(errmsg) + self.tsig_key_name = Name(tsig_key_str) + except Exception as exc: + raise XfrinZoneInfoException("Bad TSIG key name: " + str(exc)) + + def get_tsig_key(self): + if self.tsig_key_name is None: + return None + result, key = get_keyring().find(self.tsig_key_name) + if result != isc.dns.TSIGKeyRing.SUCCESS: + raise XfrinZoneInfoException("TSIG key not found in keyring: " + + self.tsig_key_name.to_text()) + else: + return key def set_use_ixfr(self, use_ixfr): """Set use_ixfr. If set to True, it will use @@ -1308,6 +1324,7 @@ class Xfrin: self.config_handler(config_data) self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION, self._auth_config_handler) + init_keyring(self._module_cc) def _cc_check_command(self): '''This is a straightforward wrapper for cc.check_command, @@ -1468,7 +1485,7 @@ class Xfrin: rrclass, self._get_db_file(), master_addr, - zone_info.tsig_key, request_type, + zone_info.get_tsig_key(), request_type, True) answer = create_answer(ret[0], ret[1]) else: @@ -1491,7 +1508,7 @@ class Xfrin: tsig_key = None request_type = RRType.AXFR() if zone_info: - tsig_key = zone_info.tsig_key + tsig_key = zone_info.get_tsig_key() if zone_info.use_ixfr: request_type = RRType.IXFR() db_file = args.get('db_file') or self._get_db_file() diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes index 554a1952fc..837cafa9bf 100644 --- a/src/bin/xfrin/xfrin_messages.mes +++ b/src/bin/xfrin/xfrin_messages.mes @@ -43,7 +43,7 @@ The master port as read from the configuration is not a valid port number. % XFRIN_BAD_TSIG_KEY_STRING bad TSIG key string: %1 The TSIG key string as read from the configuration does not represent -a valid TSIG key. +a valid TSIG key. The key is ignored. % XFRIN_BAD_ZONE_CLASS Invalid zone class: %1 The zone class as read from the configuration is not a valid DNS class. @@ -160,6 +160,13 @@ run time: Time (in seconds) the complete axfr took bytes/second: Transfer speed +% XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1 +An attempt to start a transfer with TSIG was made, but the configured TSIG +key name was not found in the TSIG key ring (configuration option +tsig_keys/keys). The transfer is aborted. The key name that could not be +found is shown in the log message. Check the configuration and the +TSIG key ring. + % XFRIN_UNKNOWN_ERROR unknown error: %1 An uncaught exception was raised while running the xfrin daemon. The exception message is printed in the log message. diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in index 835696ef1e..f869955cc1 100755 --- a/src/bin/xfrout/xfrout.py.in +++ b/src/bin/xfrout/xfrout.py.in @@ -38,7 +38,7 @@ import isc.server_common.tsig_keyring from isc.log_messages.xfrout_messages import * -isc.log.init("b10-xfrout") +isc.log.init("b10-xfrout", buffer=True) logger = isc.log.Logger("xfrout") # Pending system-wide debug level definitions, the ones we diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes index bef6080ba1..97b7a31234 100644 --- a/src/bin/xfrout/xfrout_messages.mes +++ b/src/bin/xfrout/xfrout_messages.mes @@ -107,10 +107,6 @@ received from the configuration manager. The xfrout daemon received a command on the command channel that NOTIFY packets should be sent for the given zone. -% XFROUT_RECEIVED_GETSTATS_COMMAND received command to get statistics data -The xfrout daemon received a command on the command channel that -statistics data should be sent to the stats daemon. - % XFROUT_PARSE_QUERY_ERROR error parsing query: %1 There was a parse error while reading an incoming query. The parse error is shown in the log message. A remote client sent a packet we @@ -151,6 +147,10 @@ given host. This is because of ACLs. The %2 represents the IP address and port of the peer requesting the transfer, and the %3 represents the zone name and class. +% XFROUT_RECEIVED_GETSTATS_COMMAND received command to get statistics data +The xfrout daemon received a command on the command channel that +statistics data should be sent to the stats daemon. + % XFROUT_RECEIVED_SHUTDOWN_COMMAND shutdown command received The xfrout daemon received a shutdown command from the command channel and will now shut down. diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in index 8cb616d917..0412e3f2ad 100755 --- a/src/bin/zonemgr/zonemgr.py.in +++ b/src/bin/zonemgr/zonemgr.py.in @@ -42,7 +42,7 @@ from isc.log_messages.zonemgr_messages import * from isc.notify import notify_out # Initialize logging for called modules. -isc.log.init("b10-zonemgr") +isc.log.init("b10-zonemgr", buffer=True) logger = isc.log.Logger("zonemgr") # Pending system-wide debug level definitions, the ones we @@ -193,7 +193,8 @@ class ZonemgrRefresh: def zone_handle_notify(self, zone_name_class, master): """Handle zone notify""" if (self._zone_not_exist(zone_name_class)): - logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0], zone_name_class[1]) + logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0], + zone_name_class[1], master) raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) " "doesn't belong to zonemgr" % zone_name_class) self._set_zone_notifier_master(zone_name_class, master) diff --git a/src/bin/zonemgr/zonemgr_messages.mes b/src/bin/zonemgr/zonemgr_messages.mes index 88f8dcf237..4f58271cc9 100644 --- a/src/bin/zonemgr/zonemgr_messages.mes +++ b/src/bin/zonemgr/zonemgr_messages.mes @@ -138,7 +138,7 @@ zone, or, if this error appears without the administrator giving transfer commands, it can indicate an error in the program, as it should not have initiated transfers of unknown zones on its own. -% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager +% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1/%2 from %3 is not known to the zone manager A NOTIFY was received but the zone that was the subject of the operation is not being managed by the zone manager. This may indicate an error in the program (as the operation should not have been initiated if this diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 9ebd541474..f636c0d1c4 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -1,3 +1,3 @@ SUBDIRS = exceptions util log cryptolink dns cc config acl xfr bench \ asiolink asiodns nsas cache resolve testutils datasrc \ - server_common python dhcp statistics + server_common python dhcp dhcpsrv statistics diff --git a/src/lib/acl/dnsname_check.h b/src/lib/acl/dnsname_check.h index 7498d99f64..7403c16c38 100644 --- a/src/lib/acl/dnsname_check.h +++ b/src/lib/acl/dnsname_check.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DNSNAME_CHECK_H -#define __DNSNAME_CHECK_H 1 +#ifndef DNSNAME_CHECK_H +#define DNSNAME_CHECK_H 1 #include @@ -76,7 +76,7 @@ private: } // namespace acl } // namespace isc -#endif // __DNSNAME_CHECK_H +#endif // DNSNAME_CHECK_H // Local Variables: // mode: c++ diff --git a/src/lib/acl/ip_check.h b/src/lib/acl/ip_check.h index 794b943106..9d70b0a9b9 100644 --- a/src/lib/acl/ip_check.h +++ b/src/lib/acl/ip_check.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IP_CHECK_H -#define __IP_CHECK_H +#ifndef IP_CHECK_H +#define IP_CHECK_H #include @@ -410,7 +410,7 @@ const size_t IPCheck::IPV4_SIZE; } // namespace acl } // namespace isc -#endif // __IP_CHECK_H +#endif // IP_CHECK_H // Local Variables: // mode: c++ diff --git a/src/lib/acl/tests/sockaddr.h b/src/lib/acl/tests/sockaddr.h index bd304516ad..2a4457ab9d 100644 --- a/src/lib/acl/tests/sockaddr.h +++ b/src/lib/acl/tests/sockaddr.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ACL_TEST_SOCKADDR_H -#define __ACL_TEST_SOCKADDR_H 1 +#ifndef ACL_TEST_SOCKADDR_H +#define ACL_TEST_SOCKADDR_H 1 #include #include @@ -62,7 +62,7 @@ getSockAddr(const char* const addr) { } // end of namespace "acl" } // end of namespace "isc" -#endif // __ACL_TEST_SOCKADDR_H +#endif // ACL_TEST_SOCKADDR_H // Local Variables: // mode: c++ diff --git a/src/lib/asiodns/asiodns.h b/src/lib/asiodns/asiodns.h index 8791a72f1a..3032ebdb1a 100644 --- a/src/lib/asiodns/asiodns.h +++ b/src/lib/asiodns/asiodns.h @@ -12,12 +12,12 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIODNS_H -#define __ASIODNS_H 1 +#ifndef ASIODNS_H +#define ASIODNS_H 1 #include #include #include #include -#endif // __ASIODNS_H +#endif // ASIODNS_H diff --git a/src/lib/asiodns/dns_answer.h b/src/lib/asiodns/dns_answer.h index 36543697b7..4b4576bbbf 100644 --- a/src/lib/asiodns/dns_answer.h +++ b/src/lib/asiodns/dns_answer.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_DNS_ANSWER_H -#define __ASIOLINK_DNS_ANSWER_H 1 +#ifndef ASIOLINK_DNS_ANSWER_H +#define ASIOLINK_DNS_ANSWER_H 1 #include #include @@ -74,4 +74,4 @@ public: } // namespace asiodns } // namespace isc -#endif // __ASIOLINK_DNS_ANSWER_H +#endif // ASIOLINK_DNS_ANSWER_H diff --git a/src/lib/asiodns/dns_lookup.h b/src/lib/asiodns/dns_lookup.h index 5dc84ac34e..309755cd82 100644 --- a/src/lib/asiodns/dns_lookup.h +++ b/src/lib/asiodns/dns_lookup.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_DNS_LOOKUP_H -#define __ASIOLINK_DNS_LOOKUP_H 1 +#ifndef ASIOLINK_DNS_LOOKUP_H +#define ASIOLINK_DNS_LOOKUP_H 1 #include #include @@ -84,4 +84,4 @@ private: } // namespace asiodns } // namespace isc -#endif // __ASIOLINK_DNS_LOOKUP_H +#endif // ASIOLINK_DNS_LOOKUP_H diff --git a/src/lib/asiodns/dns_server.h b/src/lib/asiodns/dns_server.h index bc398056c4..3b8758fe49 100644 --- a/src/lib/asiodns/dns_server.h +++ b/src/lib/asiodns/dns_server.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_DNS_SERVER_H -#define __ASIOLINK_DNS_SERVER_H 1 +#ifndef ASIOLINK_DNS_SERVER_H +#define ASIOLINK_DNS_SERVER_H 1 #include @@ -152,4 +152,4 @@ private: } // namespace asiodns } // namespace isc -#endif // __ASIOLINK_DNS_SERVER_H +#endif // ASIOLINK_DNS_SERVER_H diff --git a/src/lib/asiodns/dns_service.h b/src/lib/asiodns/dns_service.h index 0b578fbd9b..01b6310d70 100644 --- a/src/lib/asiodns/dns_service.h +++ b/src/lib/asiodns/dns_service.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_DNS_SERVICE_H -#define __ASIOLINK_DNS_SERVICE_H 1 +#ifndef ASIOLINK_DNS_SERVICE_H +#define ASIOLINK_DNS_SERVICE_H 1 #include @@ -210,7 +210,7 @@ private: } // namespace asiodns } // namespace isc -#endif // __ASIOLINK_DNS_SERVICE_H +#endif // ASIOLINK_DNS_SERVICE_H // Local Variables: // mode: c++ diff --git a/src/lib/asiodns/io_fetch.h b/src/lib/asiodns/io_fetch.h index 78c2da9c61..c31ee092c9 100644 --- a/src/lib/asiodns/io_fetch.h +++ b/src/lib/asiodns/io_fetch.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IO_FETCH_H -#define __IO_FETCH_H 1 +#ifndef IO_FETCH_H +#define IO_FETCH_H 1 #include @@ -229,4 +229,4 @@ private: } // namespace asiodns } // namespace isc -#endif // __IO_FETCH_H +#endif // IO_FETCH_H diff --git a/src/lib/asiodns/sync_udp_server.h b/src/lib/asiodns/sync_udp_server.h index 97184226f4..ddac1f9340 100644 --- a/src/lib/asiodns/sync_udp_server.h +++ b/src/lib/asiodns/sync_udp_server.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __SYNC_UDP_SERVER_H -#define __SYNC_UDP_SERVER_H 1 +#ifndef SYNC_UDP_SERVER_H +#define SYNC_UDP_SERVER_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" @@ -145,4 +145,4 @@ private: } // namespace asiodns } // namespace isc -#endif // __SYNC_UDP_SERVER_H +#endif // SYNC_UDP_SERVER_H diff --git a/src/lib/asiodns/tcp_server.h b/src/lib/asiodns/tcp_server.h index 7675daf332..46f484bf81 100644 --- a/src/lib/asiodns/tcp_server.h +++ b/src/lib/asiodns/tcp_server.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __TCP_SERVER_H -#define __TCP_SERVER_H 1 +#ifndef TCP_SERVER_H +#define TCP_SERVER_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" @@ -147,4 +147,4 @@ private: } // namespace asiodns } // namespace isc -#endif // __TCP_SERVER_H +#endif // TCP_SERVER_H diff --git a/src/lib/asiodns/udp_server.h b/src/lib/asiodns/udp_server.h index b32c06c9d0..c2b1b96d64 100644 --- a/src/lib/asiodns/udp_server.h +++ b/src/lib/asiodns/udp_server.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __UDP_SERVER_H -#define __UDP_SERVER_H 1 +#ifndef UDP_SERVER_H +#define UDP_SERVER_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" @@ -96,4 +96,4 @@ private: } // namespace asiodns } // namespace isc -#endif // __UDP_SERVER_H +#endif // UDP_SERVER_H diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h index 51d3a141c1..708f368abb 100644 --- a/src/lib/asiolink/asiolink.h +++ b/src/lib/asiolink/asiolink.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_H -#define __ASIOLINK_H 1 +#ifndef ASIOLINK_H +#define ASIOLINK_H 1 // IMPORTANT NOTE: only very few ASIO headers files can be included in // this file. In particular, asio.hpp should never be included here. @@ -74,4 +74,4 @@ /// the placeholder of callback handlers: /// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html -#endif // __ASIOLINK_H +#endif // ASIOLINK_H diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h index bcaefe9faa..c4644c5048 100644 --- a/src/lib/asiolink/dummy_io_cb.h +++ b/src/lib/asiolink/dummy_io_cb.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DUMMY_IO_CB_H -#define __DUMMY_IO_CB_H +#ifndef DUMMY_IO_CB_H +#define DUMMY_IO_CB_H #include @@ -59,4 +59,4 @@ public: } // namespace asiolink } // namespace isc -#endif // __DUMMY_IO_CB_H +#endif // DUMMY_IO_CB_H diff --git a/src/lib/asiolink/interval_timer.h b/src/lib/asiolink/interval_timer.h index 57ec1c327e..620abfa962 100644 --- a/src/lib/asiolink/interval_timer.h +++ b/src/lib/asiolink/interval_timer.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_INTERVAL_TIMER_H -#define __ASIOLINK_INTERVAL_TIMER_H 1 +#ifndef ASIOLINK_INTERVAL_TIMER_H +#define ASIOLINK_INTERVAL_TIMER_H 1 #include #include @@ -130,4 +130,4 @@ private: } // namespace asiolink } // namespace isc -#endif // __ASIOLINK_INTERVAL_TIMER_H +#endif // ASIOLINK_INTERVAL_TIMER_H diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc index 832452cfb7..90bdf57d29 100644 --- a/src/lib/asiolink/io_address.cc +++ b/src/lib/asiolink/io_address.cc @@ -61,7 +61,7 @@ IOAddress::toText() const { } IOAddress -IOAddress::from_bytes(short family, const uint8_t* data) { +IOAddress::fromBytes(short family, const uint8_t* data) { if (data == NULL) { isc_throw(BadValue, "NULL pointer received."); } else @@ -76,6 +76,21 @@ IOAddress::from_bytes(short family, const uint8_t* data) { return IOAddress(string(addr_str)); } +std::vector +IOAddress::toBytes() const { + if (asio_address_.is_v4()) { + const asio::ip::address_v4::bytes_type bytes4 = + asio_address_.to_v4().to_bytes(); + return (std::vector(bytes4.begin(), bytes4.end())); + } + + // Not V4 address, so must be a V6 address (else we could never construct + // this object). + const asio::ip::address_v6::bytes_type bytes6 = + asio_address_.to_v6().to_bytes(); + return (std::vector(bytes6.begin(), bytes6.end())); +} + short IOAddress::getFamily() const { if (asio_address_.is_v4()) { diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h index a3bb61ac29..5b11b8784c 100644 --- a/src/lib/asiolink/io_address.h +++ b/src/lib/asiolink/io_address.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IO_ADDRESS_H -#define __IO_ADDRESS_H 1 +#ifndef IO_ADDRESS_H +#define IO_ADDRESS_H 1 // IMPORTANT NOTE: only very few ASIO headers files can be included in // this file. In particular, asio.hpp should never be included here. @@ -24,6 +24,7 @@ #include #include +#include #include @@ -103,6 +104,19 @@ public: /// \return AF_INET for IPv4 or AF_INET6 for IPv6. short getFamily() const; + /// \brief Convenience function to check for an IPv4 address + /// + /// \return true if the address is a V4 address + bool isV4() const { + return (asio_address_.is_v4()); + } + + /// \brief Convenience function to check for an IPv6 address + /// + /// \return true if the address is a V6 address + bool isV6() const { + return (asio_address_.is_v6()); + } /// \brief Creates an address from over wire data. /// @@ -110,8 +124,13 @@ public: /// \param data pointer to first char of data /// /// \return Created IOAddress object - static IOAddress - from_bytes(short family, const uint8_t* data); + static IOAddress fromBytes(short family, const uint8_t* data); + + /// \brief Return address as set of bytes + /// + /// \return Contents of the address as a set of bytes in network-byte + /// order. + std::vector toBytes() const; /// \brief Compare addresses for equality /// @@ -215,4 +234,4 @@ private: } // namespace asiolink } // namespace isc -#endif // __IO_ADDRESS_H +#endif // IO_ADDRESS_H diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h index aeac63d3af..f6d64a0026 100644 --- a/src/lib/asiolink/io_asio_socket.h +++ b/src/lib/asiolink/io_asio_socket.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IO_ASIO_SOCKET_H -#define __IO_ASIO_SOCKET_H 1 +#ifndef IO_ASIO_SOCKET_H +#define IO_ASIO_SOCKET_H 1 // IMPORTANT NOTE: only very few ASIO headers files can be included in // this file. In particular, asio.hpp should never be included here. @@ -385,4 +385,4 @@ private: } // namespace asiolink } // namespace isc -#endif // __IO_ASIO_SOCKET_H +#endif // IO_ASIO_SOCKET_H diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h index 973fc8b4dd..89bc247398 100644 --- a/src/lib/asiolink/io_endpoint.h +++ b/src/lib/asiolink/io_endpoint.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IO_ENDPOINT_H -#define __IO_ENDPOINT_H 1 +#ifndef IO_ENDPOINT_H +#define IO_ENDPOINT_H 1 // IMPORTANT NOTE: only very few ASIO headers files can be included in // this file. In particular, asio.hpp should never be included here. @@ -184,7 +184,7 @@ public: std::ostream& operator<<(std::ostream& os, const IOEndpoint& endpoint); } // namespace asiolink } // namespace isc -#endif // __IO_ENDPOINT_H +#endif // IO_ENDPOINT_H // Local Variables: // mode: c++ diff --git a/src/lib/asiolink/io_error.h b/src/lib/asiolink/io_error.h index c19d91c693..2fb2486e82 100644 --- a/src/lib/asiolink/io_error.h +++ b/src/lib/asiolink/io_error.h @@ -13,8 +13,8 @@ // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IO_ERROR_H -#define __IO_ERROR_H +#ifndef IO_ERROR_H +#define IO_ERROR_H #include @@ -34,4 +34,4 @@ public: } // namespace asiolink } // namespace isc -#endif // __IO_ERROR_H +#endif // IO_ERROR_H diff --git a/src/lib/asiolink/io_message.h b/src/lib/asiolink/io_message.h index 81f6da1cf0..7607c72ed5 100644 --- a/src/lib/asiolink/io_message.h +++ b/src/lib/asiolink/io_message.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IO_MESSAGE_H -#define __IO_MESSAGE_H 1 +#ifndef IO_MESSAGE_H +#define IO_MESSAGE_H 1 // IMPORTANT NOTE: only very few ASIO headers files can be included in // this file. In particular, asio.hpp should never be included here. @@ -99,4 +99,4 @@ private: } // namespace asiolink } // namespace isc -#endif // __IO_MESSAGE_H +#endif // IO_MESSAGE_H diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h index 75aaee67e8..e0198ddf73 100644 --- a/src/lib/asiolink/io_service.h +++ b/src/lib/asiolink/io_service.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_IO_SERVICE_H -#define __ASIOLINK_IO_SERVICE_H 1 +#ifndef ASIOLINK_IO_SERVICE_H +#define ASIOLINK_IO_SERVICE_H 1 namespace asio { class io_service; @@ -76,4 +76,4 @@ private: } // namespace asiolink } // namespace isc -#endif // __ASIOLINK_IO_SERVICE_H +#endif // ASIOLINK_IO_SERVICE_H diff --git a/src/lib/asiolink/io_socket.h b/src/lib/asiolink/io_socket.h index ab6479c62a..6581faf0f5 100644 --- a/src/lib/asiolink/io_socket.h +++ b/src/lib/asiolink/io_socket.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __IO_SOCKET_H -#define __IO_SOCKET_H 1 +#ifndef IO_SOCKET_H +#define IO_SOCKET_H 1 // IMPORTANT NOTE: only very few ASIO headers files can be included in // this file. In particular, asio.hpp should never be included here. @@ -123,4 +123,4 @@ public: } // namespace asiolink } // namespace isc -#endif // __IO_SOCKET_H +#endif // IO_SOCKET_H diff --git a/src/lib/asiolink/simple_callback.h b/src/lib/asiolink/simple_callback.h index a297a1d621..4301bd1d48 100644 --- a/src/lib/asiolink/simple_callback.h +++ b/src/lib/asiolink/simple_callback.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ASIOLINK_SIMPLE_CALLBACK_H -#define __ASIOLINK_SIMPLE_CALLBACK_H 1 +#ifndef ASIOLINK_SIMPLE_CALLBACK_H +#define ASIOLINK_SIMPLE_CALLBACK_H 1 #include @@ -72,4 +72,4 @@ private: } // namespace asiolink } // namespace isc -#endif // __ASIOLINK_SIMPLE_CALLBACK_H +#endif // ASIOLINK_SIMPLE_CALLBACK_H diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h index a54f6b290f..3d6a87a62e 100644 --- a/src/lib/asiolink/tcp_endpoint.h +++ b/src/lib/asiolink/tcp_endpoint.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __TCP_ENDPOINT_H -#define __TCP_ENDPOINT_H 1 +#ifndef TCP_ENDPOINT_H +#define TCP_ENDPOINT_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" @@ -116,7 +116,7 @@ private: } // namespace asiolink } // namespace isc -#endif // __TCP_ENDPOINT_H +#endif // TCP_ENDPOINT_H // Local Variables: // mode: c++ diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index 2505d7b019..6b0a43cd49 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __TCP_SOCKET_H -#define __TCP_SOCKET_H 1 +#ifndef TCP_SOCKET_H +#define TCP_SOCKET_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" @@ -415,4 +415,4 @@ TCPSocket::close() { } // namespace asiolink } // namespace isc -#endif // __TCP_SOCKET_H +#endif // TCP_SOCKET_H diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc index a1a90760eb..4bd7626df3 100644 --- a/src/lib/asiolink/tests/io_address_unittest.cc +++ b/src/lib/asiolink/tests/io_address_unittest.cc @@ -18,7 +18,9 @@ #include #include +#include #include +#include using namespace isc::asiolink; @@ -64,7 +66,7 @@ TEST(IOAddressTest, Family) { EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily()); } -TEST(IOAddressTest, from_bytes) { +TEST(IOAddressTest, fromBytes) { // 2001:db8:1::dead:beef uint8_t v6[] = { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0, @@ -74,16 +76,55 @@ TEST(IOAddressTest, from_bytes) { IOAddress addr("::"); EXPECT_NO_THROW({ - addr = IOAddress::from_bytes(AF_INET6, v6); + addr = IOAddress::fromBytes(AF_INET6, v6); }); EXPECT_EQ("2001:db8:1::dead:beef", addr.toText()); EXPECT_NO_THROW({ - addr = IOAddress::from_bytes(AF_INET, v4); + addr = IOAddress::fromBytes(AF_INET, v4); }); EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText()); } +TEST(IOAddressTest, toBytesV4) { + // Address and network byte-order representation of the address. + const char* V4STRING = "192.0.2.1"; + uint8_t V4[] = {0xc0, 0x00, 0x02, 0x01}; + + std::vector actual = IOAddress(V4STRING).toBytes(); + ASSERT_EQ(sizeof(V4), actual.size()); + EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V4)); +} + +TEST(IOAddressTest, toBytesV6) { + // Address and network byte-order representation of the address. + const char* V6STRING = "2001:db8:1::dead:beef"; + uint8_t V6[] = { + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef + }; + + std::vector actual = IOAddress(V6STRING).toBytes(); + ASSERT_EQ(sizeof(V6), actual.size()); + EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V6)); +} + +TEST(IOAddressTest, isV4) { + const IOAddress address4("192.0.2.1"); + const IOAddress address6("2001:db8:1::dead:beef"); + + EXPECT_TRUE(address4.isV4()); + EXPECT_FALSE(address6.isV4()); +} + +TEST(IOAddressTest, isV6) { + const IOAddress address4("192.0.2.1"); + const IOAddress address6("2001:db8:1::dead:beef"); + + EXPECT_FALSE(address4.isV6()); + EXPECT_TRUE(address6.isV6()); +} + TEST(IOAddressTest, uint32) { IOAddress addr1("192.0.2.5"); diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h index c5ba3bd93d..34701b984f 100644 --- a/src/lib/asiolink/udp_endpoint.h +++ b/src/lib/asiolink/udp_endpoint.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __UDP_ENDPOINT_H -#define __UDP_ENDPOINT_H 1 +#ifndef UDP_ENDPOINT_H +#define UDP_ENDPOINT_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" @@ -116,7 +116,7 @@ private: } // namespace asiolink } // namespace isc -#endif // __UDP_ENDPOINT_H +#endif // UDP_ENDPOINT_H // Local Variables: // mode: c++ diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index c061fba965..5712957c23 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __UDP_SOCKET_H -#define __UDP_SOCKET_H 1 +#ifndef UDP_SOCKET_H +#define UDP_SOCKET_H 1 #ifndef ASIO_HPP #error "asio.hpp must be included before including this, see asiolink.h as to why" @@ -321,4 +321,4 @@ UDPSocket::close() { } // namespace asiolink } // namespace isc -#endif // __UDP_SOCKET_H +#endif // UDP_SOCKET_H diff --git a/src/lib/bench/benchmark.h b/src/lib/bench/benchmark.h index a5c6fd4535..3e380dc367 100644 --- a/src/lib/bench/benchmark.h +++ b/src/lib/bench/benchmark.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __BENCHMARK_H -#define __BENCHMARK_H 1 +#ifndef BENCHMARK_H +#define BENCHMARK_H 1 #include @@ -402,7 +402,7 @@ private: } } -#endif // __BENCHMARK_H +#endif // BENCHMARK_H // Local Variables: // mode: c++ diff --git a/src/lib/bench/benchmark_util.h b/src/lib/bench/benchmark_util.h index 3a373f2fa2..2cb6acc6e3 100644 --- a/src/lib/bench/benchmark_util.h +++ b/src/lib/bench/benchmark_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 __BENCHMARK_UTIL_H -#define __BENCHMARK_UTIL_H 1 +#ifndef BENCHMARK_UTIL_H +#define BENCHMARK_UTIL_H 1 /// \file /// Utilities to help write benchmark cases. @@ -140,7 +140,7 @@ void loadQueryData(std::istream& input, BenchQueries& queries, const isc::dns::RRClass& qclass, const bool strict = false); } } -#endif // __BENCHMARK_UTIL_H +#endif // BENCHMARK_UTIL_H // Local Variables: // mode: c++ diff --git a/src/lib/bench/example/search_bench.cc b/src/lib/bench/example/search_bench.cc index 84f95d953b..d022d55283 100644 --- a/src/lib/bench/example/search_bench.cc +++ b/src/lib/bench/example/search_bench.cc @@ -116,7 +116,6 @@ main(int argc, char* argv[]) { } } argc -= optind; - argv += optind; if (argc != 0) { usage(); } diff --git a/src/lib/cache/cache_entry_key.h b/src/lib/cache/cache_entry_key.h index 674deb00f7..308a55614e 100644 --- a/src/lib/cache/cache_entry_key.h +++ b/src/lib/cache/cache_entry_key.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __CACHE_ENTRY_KEY_H -#define __CACHE_ENTRY_KEY_H +#ifndef CACHE_ENTRY_KEY_H +#define CACHE_ENTRY_KEY_H #include #include @@ -50,5 +50,5 @@ genCacheEntryName(const std::string& namestr, const uint16_t type); } // namespace cache } // namespace isc -#endif // __CACHE_ENTRY_KEY_H +#endif // CACHE_ENTRY_KEY_H diff --git a/src/lib/cache/local_zone_data.h b/src/lib/cache/local_zone_data.h index df77f40165..4bfdb94271 100644 --- a/src/lib/cache/local_zone_data.h +++ b/src/lib/cache/local_zone_data.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef _LOCAL_ZONE_DATA -#define _LOCAL_ZONE_DATA +#ifndef LOCAL_ZONE_DATA +#define LOCAL_ZONE_DATA #include #include @@ -60,5 +60,5 @@ typedef boost::shared_ptr ConstLocalZoneDataPtr; } // namespace cache } // namespace isc -#endif // _LOCAL_ZONE_DATA +#endif // LOCAL_ZONE_DATA diff --git a/src/lib/cache/logger.h b/src/lib/cache/logger.h index 52c9743cc7..3cadb54e25 100644 --- a/src/lib/cache/logger.h +++ b/src/lib/cache/logger.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATASRC_LOGGER_H -#define __DATASRC_LOGGER_H +#ifndef DATASRC_LOGGER_H +#define DATASRC_LOGGER_H #include #include diff --git a/src/lib/cache/message_cache.h b/src/lib/cache/message_cache.h index b418f23953..0c19139088 100644 --- a/src/lib/cache/message_cache.h +++ b/src/lib/cache/message_cache.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __MESSAGE_CACHE_H -#define __MESSAGE_CACHE_H +#ifndef MESSAGE_CACHE_H +#define MESSAGE_CACHE_H #include #include @@ -90,5 +90,5 @@ typedef boost::shared_ptr MessageCachePtr; } // namespace cache } // namespace isc -#endif // __MESSAGE_CACHE_H +#endif // MESSAGE_CACHE_H diff --git a/src/lib/cache/message_entry.h b/src/lib/cache/message_entry.h index 6da27cc539..206e601c1b 100644 --- a/src/lib/cache/message_entry.h +++ b/src/lib/cache/message_entry.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __MESSAGE_ENTRY_H -#define __MESSAGE_ENTRY_H +#ifndef MESSAGE_ENTRY_H +#define MESSAGE_ENTRY_H #include #include @@ -199,5 +199,5 @@ typedef boost::shared_ptr MessageEntryPtr; } // namespace cache } // namespace isc -#endif // __MESSAGE_ENTRY_H +#endif // MESSAGE_ENTRY_H diff --git a/src/lib/cache/message_utility.h b/src/lib/cache/message_utility.h index a77af078c4..1a79480812 100644 --- a/src/lib/cache/message_utility.h +++ b/src/lib/cache/message_utility.h @@ -14,8 +14,8 @@ // $Id$ -#ifndef __MESSAGE_UTILITY_H -#define __MESSAGE_UTILITY_H +#ifndef MESSAGE_UTILITY_H +#define MESSAGE_UTILITY_H #include @@ -63,4 +63,4 @@ bool canMessageBeCached(const isc::dns::Message& msg); } // namespace isc -#endif//__MESSAGE_UTILITY_H +#endif//MESSAGE_UTILITY_H diff --git a/src/lib/cache/resolver_cache.h b/src/lib/cache/resolver_cache.h index 5630bd77d4..9813cc29e6 100644 --- a/src/lib/cache/resolver_cache.h +++ b/src/lib/cache/resolver_cache.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __RESOLVER_CACHE_H -#define __RESOLVER_CACHE_H +#ifndef RESOLVER_CACHE_H +#define RESOLVER_CACHE_H #include #include @@ -319,5 +319,5 @@ private: } // namespace cache } // namespace isc -#endif // __RESOLVER_CACHE_H +#endif // RESOLVER_CACHE_H diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h index a4ea54ef6e..304c6e8907 100644 --- a/src/lib/cache/rrset_cache.h +++ b/src/lib/cache/rrset_cache.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __RRSET_CACHE_H -#define __RRSET_CACHE_H +#ifndef RRSET_CACHE_H +#define RRSET_CACHE_H #include #include @@ -89,5 +89,5 @@ typedef boost::shared_ptr ConstRRsetCachePtr; } // namespace cache } // namespace isc -#endif // __RRSET_CACHE_H +#endif // RRSET_CACHE_H diff --git a/src/lib/cache/rrset_copy.h b/src/lib/cache/rrset_copy.h index e1dc489f0e..6b74bc0b27 100644 --- a/src/lib/cache/rrset_copy.h +++ b/src/lib/cache/rrset_copy.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __RRSET_COPY_ -#define __RRSET_COPY_ +#ifndef RRSET_COPY +#define RRSET_COPY #include @@ -38,5 +38,5 @@ rrsetCopy(const isc::dns::AbstractRRset& src, isc::dns::AbstractRRset& dst); } // namespace cache } // namespace isc -#endif // __RRSET_COPY_ +#endif // RRSET_COPY diff --git a/src/lib/cache/rrset_entry.h b/src/lib/cache/rrset_entry.h index 129300df54..0efda3660f 100644 --- a/src/lib/cache/rrset_entry.h +++ b/src/lib/cache/rrset_entry.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __RRSET_ENTRY_H -#define __RRSET_ENTRY_H +#ifndef RRSET_ENTRY_H +#define RRSET_ENTRY_H #include #include @@ -132,5 +132,5 @@ typedef boost::shared_ptr RRsetEntryPtr; } // namespace cache } // namespace isc -#endif // __RRSET_ENTRY_H +#endif // RRSET_ENTRY_H diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc index 47b1eb1fa4..8d9b87dd05 100644 --- a/src/lib/cc/data.cc +++ b/src/lib/cc/data.cc @@ -57,32 +57,32 @@ Element::toWire(std::ostream& ss) const { } bool -Element::getValue(long int&) { +Element::getValue(long int&) const { return (false); } bool -Element::getValue(double&) { +Element::getValue(double&) const { return (false); } bool -Element::getValue(bool&) { +Element::getValue(bool&) const { return (false); } bool -Element::getValue(std::string&) { +Element::getValue(std::string&) const { return (false); } bool -Element::getValue(std::vector&) { +Element::getValue(std::vector&) const { return (false); } bool -Element::getValue(std::map&) { +Element::getValue(std::map&) const { return (false); } @@ -167,7 +167,7 @@ Element::find(const std::string&) const { } bool -Element::find(const std::string&, ConstElementPtr) const { +Element::find(const std::string&, ConstElementPtr&) const { return (false); } @@ -812,7 +812,7 @@ MapElement::set(const std::string& key, ConstElementPtr value) { } bool -MapElement::find(const std::string& id, ConstElementPtr t) const { +MapElement::find(const std::string& id, ConstElementPtr& t) const { try { ConstElementPtr p = find(id); if (p) { diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h index 5c731e6b54..bb84ae2e30 100644 --- a/src/lib/cc/data.h +++ b/src/lib/cc/data.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef _ISC_DATA_H -#define _ISC_DATA_H 1 +#ifndef ISC_DATA_H +#define ISC_DATA_H 1 #include #include @@ -71,7 +71,7 @@ public: /// the type in question. /// class Element { - + private: // technically the type could be omitted; is it useful? // should we remove it or replace it with a pure virtual @@ -112,7 +112,7 @@ public: /// \returns true if the other ElementPtr has the same type and /// value virtual bool equals(const Element& other) const = 0; - + /// Converts the Element to JSON format and appends it to /// the given stringstream. virtual void toJSON(std::ostream& ss) const = 0; @@ -152,12 +152,12 @@ public: /// data to the given reference and returning true /// //@{ - virtual bool getValue(long int& t); - virtual bool getValue(double& t); - virtual bool getValue(bool& t); - virtual bool getValue(std::string& t); - virtual bool getValue(std::vector& t); - virtual bool getValue(std::map& t); + virtual bool getValue(long int& t) const; + virtual bool getValue(double& t) const; + virtual bool getValue(bool& t) const; + virtual bool getValue(std::string& t) const; + virtual bool getValue(std::vector& t) const; + virtual bool getValue(std::map& t) const; //@} /// @@ -209,7 +209,7 @@ public: virtual size_t size() const; //@} - + /// \name MapElement functions /// /// \brief If the Element on which these functions are called are not @@ -253,12 +253,12 @@ public: /// \param identifier The identifier of the element to find /// \param t Reference to store the resulting ElementPtr, if found. /// \return true if the element was found, false if not. - virtual bool find(const std::string& identifier, ConstElementPtr t) const; + virtual bool find(const std::string& identifier, ConstElementPtr& t) const; //@} /// \name Factory functions - + // TODO: should we move all factory functions to a different class // so as not to burden the Element base with too many functions? // and/or perhaps even to a separate header? @@ -349,7 +349,7 @@ public: /// These function pparse the wireformat at the given stringstream /// (of the given length). If there is a parse error an exception /// of the type isc::cc::DecodeError is raised. - + //@{ /// Creates an Element from the wire format in the given /// stringstream of the given length. @@ -378,7 +378,7 @@ public: IntElement(long int v) : Element(integer), i(v) { } long int intValue() const { return (i); } using Element::getValue; - bool getValue(long int& t) { t = i; return (true); } + bool getValue(long int& t) const { t = i; return (true); } using Element::setValue; bool setValue(const long int v) { i = v; return (true); } void toJSON(std::ostream& ss) const; @@ -392,7 +392,7 @@ public: DoubleElement(double v) : Element(real), d(v) {}; double doubleValue() const { return (d); } using Element::getValue; - bool getValue(double& t) { t = d; return (true); } + bool getValue(double& t) const { t = d; return (true); } using Element::setValue; bool setValue(const double v) { d = v; return (true); } void toJSON(std::ostream& ss) const; @@ -406,7 +406,7 @@ public: BoolElement(const bool v) : Element(boolean), b(v) {}; bool boolValue() const { return (b); } using Element::getValue; - bool getValue(bool& t) { t = b; return (true); } + bool getValue(bool& t) const { t = b; return (true); } using Element::setValue; bool setValue(const bool v) { b = v; return (true); } void toJSON(std::ostream& ss) const; @@ -427,7 +427,7 @@ public: StringElement(std::string v) : Element(string), s(v) {}; std::string stringValue() const { return (s); } using Element::getValue; - bool getValue(std::string& t) { t = s; return (true); } + bool getValue(std::string& t) const { t = s; return (true); } using Element::setValue; bool setValue(const std::string& v) { s = v; return (true); } void toJSON(std::ostream& ss) const; @@ -441,7 +441,7 @@ public: ListElement() : Element(list) {} const std::vector& listValue() const { return (l); } using Element::getValue; - bool getValue(std::vector& t) { + bool getValue(std::vector& t) const { t = l; return (true); } @@ -474,7 +474,7 @@ public: return (m); } using Element::getValue; - bool getValue(std::map& t) { + bool getValue(std::map& t) const { t = m; return (true); } @@ -495,7 +495,7 @@ public: return (m.find(s) != m.end()); } void toJSON(std::ostream& ss) const; - + // we should name the two finds better... // find the element at id; raises TypeError if one of the // elements at path except the one we're looking for is not a @@ -507,7 +507,7 @@ public: // returns true if found, or false if not found (either because // it doesnt exist or one of the elements in the path is not // a MapElement) - bool find(const std::string& id, ConstElementPtr t) const; + bool find(const std::string& id, ConstElementPtr& t) const; bool equals(const Element& other) const; }; @@ -567,8 +567,8 @@ std::ostream& operator<<(std::ostream& out, const Element& e); bool operator==(const Element& a, const Element& b); bool operator!=(const Element& a, const Element& b); } } -#endif // _ISC_DATA_H +#endif // ISC_DATA_H -// Local Variables: +// Local Variables: // mode: c++ -// End: +// End: diff --git a/src/lib/cc/session.h b/src/lib/cc/session.h index 9b082322d3..a81829154b 100644 --- a/src/lib/cc/session.h +++ b/src/lib/cc/session.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef _ISC_SESSION_H -#define _ISC_SESSION_H 1 +#ifndef ISC_SESSION_H +#define ISC_SESSION_H 1 #include @@ -161,7 +161,7 @@ namespace isc { } // namespace cc } // namespace isc -#endif // _ISC_SESSION_H +#endif // ISC_SESSION_H // Local Variables: // mode: c++ diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc index 87d92f64ab..1565418352 100644 --- a/src/lib/cc/tests/data_unittests.cc +++ b/src/lib/cc/tests/data_unittests.cc @@ -112,7 +112,7 @@ TEST(Element, from_and_to_json) { std::string s = std::string(pe.what()); EXPECT_EQ("String expected in :1:3", s); } - + sv.clear(); sv.push_back("{1}"); //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err"); @@ -172,14 +172,14 @@ TEST(Element, from_and_to_json) { } -TEST(Element, create_and_value_throws) { - // this test checks whether elements throw exceptions if the - // incorrect type is requested - ElementPtr el; +template +void +testGetValueInt() { + T el; long int i; double d; bool b; - std::string s("asdf"); + std::string s; std::vector v; std::map m; @@ -196,7 +196,161 @@ TEST(Element, create_and_value_throws) { EXPECT_FALSE(el->getValue(s)); EXPECT_FALSE(el->getValue(v)); EXPECT_FALSE(el->getValue(m)); - EXPECT_EQ(i, 1); + EXPECT_EQ(1, i); +} + +template +void +testGetValueDouble() { + T el; + long int i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::create(1.1); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_NO_THROW(el->doubleValue()); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_TRUE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ(1.1, d); +} + +template +void +testGetValueBool() { + T el; + long int i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::create(true); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_NO_THROW(el->boolValue()); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_TRUE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ(true, b); +} + +template +void +testGetValueString() { + T el; + long int i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::create("foo"); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_NO_THROW(el->stringValue()); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_TRUE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ("foo", s); +} + +template +void +testGetValueList() { + T el; + long int i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::createList(); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_NO_THROW(el->listValue()); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_TRUE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ("[ ]", el->str()); +} + +template +void +testGetValueMap() { + T el; + long int i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::createMap(); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_NO_THROW(el->mapValue()); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_TRUE(el->getValue(m)); + EXPECT_EQ("{ }", el->str()); +} + +TEST(Element, create_and_value_throws) { + // this test checks whether elements throw exceptions if the + // incorrect type is requested + ElementPtr el; + ConstElementPtr cel; + long int i = 0; + double d = 0.0; + bool b = false; + std::string s("asdf"); + std::vector v; + std::map m; + ConstElementPtr tmp; + + testGetValueInt(); + testGetValueInt(); + + el = Element::create(1); i = 2; EXPECT_TRUE(el->setValue(i)); EXPECT_EQ(2, el->intValue()); @@ -214,24 +368,12 @@ TEST(Element, create_and_value_throws) { EXPECT_THROW(el->set("foo", el), TypeError); EXPECT_THROW(el->remove("foo"), TypeError); EXPECT_THROW(el->contains("foo"), TypeError); - ConstElementPtr tmp; EXPECT_FALSE(el->find("foo", tmp)); - + + testGetValueDouble(); + testGetValueDouble(); el = Element::create(1.1); - EXPECT_THROW(el->intValue(), TypeError); - EXPECT_NO_THROW(el->doubleValue()); - EXPECT_THROW(el->boolValue(), TypeError); - EXPECT_THROW(el->stringValue(), TypeError); - EXPECT_THROW(el->listValue(), TypeError); - EXPECT_THROW(el->mapValue(), TypeError); - EXPECT_FALSE(el->getValue(i)); - EXPECT_TRUE(el->getValue(d)); - EXPECT_FALSE(el->getValue(b)); - EXPECT_FALSE(el->getValue(s)); - EXPECT_FALSE(el->getValue(v)); - EXPECT_FALSE(el->getValue(m)); - EXPECT_EQ(d, 1.1); d = 2.2; EXPECT_TRUE(el->setValue(d)); EXPECT_EQ(2.2, el->doubleValue()); @@ -240,75 +382,77 @@ TEST(Element, create_and_value_throws) { EXPECT_FALSE(el->setValue(s)); EXPECT_FALSE(el->setValue(v)); EXPECT_FALSE(el->setValue(m)); + EXPECT_THROW(el->get(1), TypeError); + EXPECT_THROW(el->set(1, el), TypeError); + EXPECT_THROW(el->add(el), TypeError); + EXPECT_THROW(el->remove(1), TypeError); + EXPECT_THROW(el->size(), TypeError); + EXPECT_THROW(el->get("foo"), TypeError); + EXPECT_THROW(el->set("foo", el), TypeError); + EXPECT_THROW(el->remove("foo"), TypeError); + EXPECT_THROW(el->contains("foo"), TypeError); + EXPECT_FALSE(el->find("foo", tmp)); + + testGetValueBool(); + testGetValueBool(); el = Element::create(true); - EXPECT_THROW(el->intValue(), TypeError); - EXPECT_THROW(el->doubleValue(), TypeError); - EXPECT_NO_THROW(el->boolValue()); - EXPECT_THROW(el->stringValue(), TypeError); - EXPECT_THROW(el->listValue(), TypeError); - EXPECT_THROW(el->mapValue(), TypeError); - EXPECT_FALSE(el->getValue(i)); - EXPECT_FALSE(el->getValue(d)); - EXPECT_TRUE(el->getValue(b)); - EXPECT_FALSE(el->getValue(s)); - EXPECT_FALSE(el->getValue(v)); - EXPECT_FALSE(el->getValue(m)); - EXPECT_EQ(b, true); b = false; EXPECT_TRUE(el->setValue(b)); EXPECT_FALSE(el->boolValue()); + EXPECT_FALSE(el->setValue(i)); + EXPECT_FALSE(el->setValue(d)); + EXPECT_FALSE(el->setValue(s)); + EXPECT_FALSE(el->setValue(v)); + EXPECT_FALSE(el->setValue(m)); + EXPECT_THROW(el->get(1), TypeError); + EXPECT_THROW(el->set(1, el), TypeError); + EXPECT_THROW(el->add(el), TypeError); + EXPECT_THROW(el->remove(1), TypeError); + EXPECT_THROW(el->size(), TypeError); + EXPECT_THROW(el->get("foo"), TypeError); + EXPECT_THROW(el->set("foo", el), TypeError); + EXPECT_THROW(el->remove("foo"), TypeError); + EXPECT_THROW(el->contains("foo"), TypeError); + EXPECT_FALSE(el->find("foo", tmp)); + + testGetValueString(); + testGetValueString(); el = Element::create("foo"); - EXPECT_THROW(el->intValue(), TypeError); - EXPECT_THROW(el->doubleValue(), TypeError); - EXPECT_THROW(el->boolValue(), TypeError); - EXPECT_NO_THROW(el->stringValue()); - EXPECT_THROW(el->listValue(), TypeError); - EXPECT_THROW(el->mapValue(), TypeError); - EXPECT_FALSE(el->getValue(i)); - EXPECT_FALSE(el->getValue(d)); - EXPECT_FALSE(el->getValue(b)); - EXPECT_TRUE(el->getValue(s)); - EXPECT_FALSE(el->getValue(v)); - EXPECT_FALSE(el->getValue(m)); - EXPECT_EQ(s, "foo"); s = "bar"; EXPECT_TRUE(el->setValue(s)); EXPECT_EQ("bar", el->stringValue()); + EXPECT_FALSE(el->setValue(i)); + EXPECT_FALSE(el->setValue(b)); + EXPECT_FALSE(el->setValue(d)); + EXPECT_FALSE(el->setValue(v)); + EXPECT_FALSE(el->setValue(m)); + EXPECT_THROW(el->get(1), TypeError); + EXPECT_THROW(el->set(1, el), TypeError); + EXPECT_THROW(el->add(el), TypeError); + EXPECT_THROW(el->remove(1), TypeError); + EXPECT_THROW(el->size(), TypeError); + EXPECT_THROW(el->get("foo"), TypeError); + EXPECT_THROW(el->set("foo", el), TypeError); + EXPECT_THROW(el->remove("foo"), TypeError); + EXPECT_THROW(el->contains("foo"), TypeError); + EXPECT_FALSE(el->find("foo", tmp)); + + testGetValueList(); + testGetValueList(); el = Element::createList(); - EXPECT_THROW(el->intValue(), TypeError); - EXPECT_THROW(el->doubleValue(), TypeError); - EXPECT_THROW(el->boolValue(), TypeError); - EXPECT_THROW(el->stringValue(), TypeError); - EXPECT_NO_THROW(el->listValue()); - EXPECT_THROW(el->mapValue(), TypeError); - EXPECT_FALSE(el->getValue(i)); - EXPECT_FALSE(el->getValue(d)); - EXPECT_FALSE(el->getValue(b)); - EXPECT_FALSE(el->getValue(s)); - EXPECT_TRUE(el->getValue(v)); - EXPECT_FALSE(el->getValue(m)); - EXPECT_EQ("[ ]", el->str()); v.push_back(Element::create(1)); EXPECT_TRUE(el->setValue(v)); EXPECT_EQ("[ 1 ]", el->str()); - el = Element::createMap(); - EXPECT_THROW(el->intValue(), TypeError); - EXPECT_THROW(el->doubleValue(), TypeError); - EXPECT_THROW(el->boolValue(), TypeError); - EXPECT_THROW(el->stringValue(), TypeError); - EXPECT_THROW(el->listValue(), TypeError); - EXPECT_NO_THROW(el->mapValue()); - EXPECT_FALSE(el->getValue(i)); - EXPECT_FALSE(el->getValue(d)); - EXPECT_FALSE(el->getValue(b)); - EXPECT_FALSE(el->getValue(s)); - EXPECT_FALSE(el->getValue(v)); - EXPECT_TRUE(el->getValue(m)); + testGetValueMap(); + testGetValueMap(); + el = Element::createMap(); + EXPECT_NO_THROW(el->set("foo", Element::create("bar"))); + EXPECT_EQ("{ \"foo\": \"bar\" }", el->str()); } // Helper for escape check; it puts the given string in a StringElement, @@ -382,7 +526,7 @@ TEST(Element, MapElement) { // this function checks the specific functions for ListElements ElementPtr el = Element::fromJSON("{ \"name\": \"foo\", \"value1\": \"bar\", \"value2\": { \"number\": 42 } }"); ConstElementPtr el2; - + EXPECT_EQ(el->get("name")->stringValue(), "foo"); EXPECT_EQ(el->get("value2")->getType(), Element::map); @@ -396,11 +540,12 @@ TEST(Element, MapElement) { EXPECT_EQ(el->find("value2/number")->intValue(), 42); EXPECT_TRUE(isNull(el->find("value2/nothing/"))); - + EXPECT_EQ(el->find("value1")->stringValue(), "bar"); EXPECT_EQ(el->find("value1/")->stringValue(), "bar"); - + EXPECT_TRUE(el->find("value1", el2)); + EXPECT_EQ("bar", el2->stringValue()); EXPECT_FALSE(el->find("name/error", el2)); // A map element whose (only) element has the maximum length of tag. @@ -410,7 +555,7 @@ TEST(Element, MapElement) { "9123456789abcdefa123456789abcdefb123456789abcdef" "c123456789abcdefd123456789abcdefe123456789abcdef" "f123456789abcde"); - + EXPECT_EQ(255, long_maptag.length()); // check prerequisite el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}"); EXPECT_EQ("bar", el->find(long_maptag)->stringValue()); @@ -689,7 +834,7 @@ TEST(Element, merge) { c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); merge(b, a); EXPECT_EQ(*b, *c); - + // And some tests with multiple values a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }"); b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }"); diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h index 4b99a44bd8..b4a44d0afd 100644 --- a/src/lib/config/ccsession.h +++ b/src/lib/config/ccsession.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __CCSESSION_H -#define __CCSESSION_H 1 +#ifndef CCSESSION_H +#define CCSESSION_H 1 #include #include @@ -576,7 +576,7 @@ getRelatedLoggers(isc::data::ConstElementPtr loggers); } // namespace config } // namespace isc -#endif // __CCSESSION_H +#endif // CCSESSION_H // Local Variables: // mode: c++ diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h index 3fdbc25ce2..0bb1bfdb7c 100644 --- a/src/lib/config/config_data.h +++ b/src/lib/config/config_data.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __CONFIG_DATA_H -#define __CONFIG_DATA_H 1 +#ifndef CONFIG_DATA_H +#define CONFIG_DATA_H 1 #include #include diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h index 21709fdee0..14f681e616 100644 --- a/src/lib/config/config_log.h +++ b/src/lib/config/config_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 __CONFIG_LOG__H -#define __CONFIG_LOG__H +#ifndef CONFIG_LOG_H +#define CONFIG_LOG_H #include #include "config_messages.h" @@ -38,4 +38,4 @@ const int DBG_CONFIG_PROCESS = DBGLVL_TRACE_BASIC; } // namespace config } // namespace isc -#endif // __CONFIG_LOG__H +#endif // CONFIG_LOG_H diff --git a/src/lib/config/module_spec.h b/src/lib/config/module_spec.h index 27dcfe38b8..d755125d22 100644 --- a/src/lib/config/module_spec.h +++ b/src/lib/config/module_spec.h @@ -13,8 +13,8 @@ // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION // WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -#ifndef _MODULE_SPEC_H -#define _MODULE_SPEC_H 1 +#ifndef MODULE_SPEC_H +#define MODULE_SPEC_H 1 #include diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h index c91b5199e3..7d3cfdee2d 100644 --- a/src/lib/config/tests/fake_session.h +++ b/src/lib/config/tests/fake_session.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef _ISC_FAKESESSION_H -#define _ISC_FAKESESSION_H 1 +#ifndef ISC_FAKESESSION_H +#define ISC_FAKESESSION_H 1 #include @@ -111,7 +111,7 @@ private: } // namespace cc } // namespace isc -#endif // _ISC_FAKESESSION_H +#endif // ISC_FAKESESSION_H // Local Variables: // mode: c++ diff --git a/src/lib/cryptolink/crypto_hmac.h b/src/lib/cryptolink/crypto_hmac.h index 2eb0d0ec8f..ac827857b7 100644 --- a/src/lib/cryptolink/crypto_hmac.h +++ b/src/lib/cryptolink/crypto_hmac.h @@ -18,8 +18,8 @@ #include -#ifndef _ISC_CRYPTO_HMAC_H -#define _ISC_CRYPTO_HMAC_H +#ifndef ISC_CRYPTO_HMAC_H +#define ISC_CRYPTO_HMAC_H namespace isc { namespace cryptolink { @@ -205,5 +205,5 @@ void deleteHMAC(HMAC* hmac); } // namespace cryptolink } // namespace isc -#endif // __ISC_CRYPTO_HMAC +#endif // ISC_CRYPTO_HMAC_H diff --git a/src/lib/cryptolink/cryptolink.h b/src/lib/cryptolink/cryptolink.h index d0f7d38933..859065bfd2 100644 --- a/src/lib/cryptolink/cryptolink.h +++ b/src/lib/cryptolink/cryptolink.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef _ISC_CRYPTO_H -#define _ISC_CRYPTO_H +#ifndef ISC_CRYPTO_H +#define ISC_CRYPTO_H #include #include @@ -205,4 +205,4 @@ private: } // namespace cryptolink } // namespace isc -#endif // _ISC_CRYPTO_H +#endif // ISC_CRYPTO_H diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am index eccc147a77..f4e768aafc 100644 --- a/src/lib/datasrc/Makefile.am +++ b/src/lib/datasrc/Makefile.am @@ -26,6 +26,7 @@ lib_LTLIBRARIES = libb10-datasrc.la libb10_datasrc_la_SOURCES = data_source.h libb10_datasrc_la_SOURCES += rbnode_rrset.h libb10_datasrc_la_SOURCES += rbtree.h +libb10_datasrc_la_SOURCES += exceptions.h libb10_datasrc_la_SOURCES += zonetable.h zonetable.cc libb10_datasrc_la_SOURCES += zone.h zone_finder.cc zone_finder_context.cc libb10_datasrc_la_SOURCES += result.h @@ -35,6 +36,8 @@ libb10_datasrc_la_SOURCES += database.h database.cc libb10_datasrc_la_SOURCES += factory.h factory.cc libb10_datasrc_la_SOURCES += client_list.h client_list.cc libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc +libb10_datasrc_la_SOURCES += master_loader_callbacks.h +libb10_datasrc_la_SOURCES += master_loader_callbacks.cc nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1 diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h index dab081f38d..3756a6858a 100644 --- a/src/lib/datasrc/client.h +++ b/src/lib/datasrc/client.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATA_SOURCE_CLIENT_H -#define __DATA_SOURCE_CLIENT_H 1 +#ifndef DATA_SOURCE_CLIENT_H +#define DATA_SOURCE_CLIENT_H 1 #include diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc index 865a1ce4b9..0750fb6e1c 100644 --- a/src/lib/datasrc/client_list.cc +++ b/src/lib/datasrc/client_list.cc @@ -12,17 +12,23 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include #include "client_list.h" +#include "exceptions.h" #include "client.h" #include "factory.h" #include "memory/memory_client.h" +#include "memory/zone_table_segment.h" +#include "memory/zone_writer.h" +#include "memory/zone_data_loader.h" +#include "memory/zone_data_updater.h" #include "logger.h" #include +#include #include #include +#include using namespace isc::data; using namespace isc::dns; @@ -32,6 +38,8 @@ using boost::lexical_cast; using boost::shared_ptr; using boost::dynamic_pointer_cast; using isc::datasrc::memory::InMemoryClient; +using isc::datasrc::memory::ZoneTableSegment; +using isc::datasrc::memory::ZoneDataUpdater; namespace isc { namespace datasrc { @@ -39,21 +47,24 @@ namespace datasrc { ConfigurableClientList::DataSourceInfo::DataSourceInfo( DataSourceClient* data_src_client, const DataSourceClientContainerPtr& container, bool has_cache, - const RRClass& rrclass, MemorySegment& mem_sgmt) : + const RRClass& rrclass, const shared_ptr& segment) : data_src_client_(data_src_client), container_(container) { if (has_cache) { - cache_.reset(new InMemoryClient(mem_sgmt, rrclass)); + cache_.reset(new InMemoryClient(segment, rrclass)); + ztable_segment_ = segment; } } ConfigurableClientList::DataSourceInfo::DataSourceInfo( - const RRClass& rrclass, MemorySegment& mem_sgmt, bool has_cache) : + const RRClass& rrclass, const shared_ptr& segment, + bool has_cache) : data_src_client_(NULL) { if (has_cache) { - cache_.reset(new InMemoryClient(mem_sgmt, rrclass)); + cache_.reset(new InMemoryClient(segment, rrclass)); + ztable_segment_ = segment; } } @@ -64,21 +75,10 @@ ConfigurableClientList::DataSourceInfo::getCacheClient() const { ConfigurableClientList::ConfigurableClientList(const RRClass& rrclass) : rrclass_(rrclass), - mem_sgmt_(new util::MemorySegmentLocal), configuration_(new isc::data::ListElement), allow_cache_(false) {} -ConfigurableClientList::~ConfigurableClientList() { - // Explicitly clear the contained data source clients, and check memory - // leak. assert() (with abort on failure) may be too harsh, but - // it's probably better to find more leaks initially. Once it's stabilized - // we should probably revisit it. - - data_sources_.clear(); - assert(mem_sgmt_->allMemoryDeallocated()); -} - void ConfigurableClientList::configure(const ConstElementPtr& config, bool allow_cache) @@ -90,6 +90,8 @@ ConfigurableClientList::configure(const ConstElementPtr& config, size_t i(0); // Outside of the try to be able to access it in the catch try { vector new_data_sources; + shared_ptr ztable_segment( + ZoneTableSegment::create(*config, rrclass_)); for (; i < config->size(); ++i) { // Extract the parameters const ConstElementPtr dconf(config->get(i)); @@ -126,7 +128,8 @@ ConfigurableClientList::configure(const ConstElementPtr& config, isc_throw(ConfigurationError, "The cache must be enabled " "for the MasterFiles type"); } - new_data_sources.push_back(DataSourceInfo(rrclass_, *mem_sgmt_, + new_data_sources.push_back(DataSourceInfo(rrclass_, + ztable_segment, true)); } else { // Ask the factory to create the data source for us @@ -135,7 +138,7 @@ ConfigurableClientList::configure(const ConstElementPtr& config, // And put it into the vector new_data_sources.push_back(DataSourceInfo(ds.first, ds.second, want_cache, rrclass_, - *mem_sgmt_)); + ztable_segment)); } if (want_cache) { @@ -175,9 +178,9 @@ ConfigurableClientList::configure(const ConstElementPtr& config, try { cache->load(origin, paramConf->get(*it)->stringValue()); - } catch (const isc::dns::MasterLoadError& mle) { - LOG_ERROR(logger, DATASRC_MASTERLOAD_ERROR) - .arg(mle.what()); + } catch (const ZoneLoaderException& e) { + LOG_ERROR(logger, DATASRC_LOAD_FROM_FILE_ERROR) + .arg(origin).arg(e.what()); } } else { ZoneIteratorPtr iterator; @@ -192,7 +195,12 @@ ConfigurableClientList::configure(const ConstElementPtr& config, isc_throw(isc::Unexpected, "Got NULL iterator " "for zone " << origin); } - cache->load(origin, *iterator); + try { + cache->load(origin, *iterator); + } catch (const ZoneLoaderException& e) { + LOG_ERROR(logger, DATASRC_LOAD_FROM_ITERATOR_ERROR) + .arg(origin).arg(e.what()); + } } } } @@ -337,33 +345,93 @@ ConfigurableClientList::findInternal(MutableResult& candidate, // and the need_updater parameter is true, get the zone there. } +// We still provide this method for backward compatibility. But to not have +// duplicate code, it is a thin wrapper around getCachedZoneWriter only. ConfigurableClientList::ReloadResult ConfigurableClientList::reload(const Name& name) { + const ZoneWriterPair result(getCachedZoneWriter(name)); + if (result.first != ZONE_SUCCESS) { + return (result.first); + } + + assert(result.second); + result.second->load(); + result.second->install(); + result.second->cleanup(); + + return (ZONE_SUCCESS); +} + +namespace { + +// We would like to use boost::bind for this. However, the loadZoneData takes +// a reference, while we have a shared pointer to the iterator -- and we need +// to keep it alive as long as the ZoneWriter is alive. Therefore we can't +// really just dereference it and pass it, since it would get destroyed once +// the getCachedZoneWriter would end. This class holds the shared pointer +// alive, otherwise is mostly simple. +// +// It might be doable with nested boost::bind, but it would probably look +// more awkward and complicated than this. +class IteratorLoader { +public: + IteratorLoader(const RRClass& rrclass, const Name& name, + const ZoneIteratorPtr& iterator) : + rrclass_(rrclass), + name_(name), + iterator_(iterator) + {} + memory::ZoneData* operator()(util::MemorySegment& segment) { + return (memory::loadZoneData(segment, rrclass_, name_, *iterator_)); + } +private: + const RRClass rrclass_; + const Name name_; + ZoneIteratorPtr iterator_; +}; + +// We can't use the loadZoneData function directly in boost::bind, since +// it is overloaded and the compiler can't choose the correct version +// reliably and fails. So we simply wrap it into an unique name. +memory::ZoneData* +loadZoneDataFromFile(util::MemorySegment& segment, const RRClass& rrclass, + const Name& name, const string& filename) +{ + return (memory::loadZoneData(segment, rrclass, name, filename)); +} + +} + +ConfigurableClientList::ZoneWriterPair +ConfigurableClientList::getCachedZoneWriter(const Name& name) { if (!allow_cache_) { - return (CACHE_DISABLED); + return (ZoneWriterPair(CACHE_DISABLED, ZoneWriterPtr())); } // Try to find the correct zone. MutableResult result; findInternal(result, name, true, true); if (!result.finder) { - return (ZONE_NOT_FOUND); + return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr())); } - // Try to convert the finder to in-memory one. If it is the cache, - // it should work. - // It is of a different type or there's no cache. + // Try to get the in-memory cache for the zone. If there's none, + // we can't provide the result. if (!result.info->cache_) { - return (ZONE_NOT_CACHED); + return (ZoneWriterPair(ZONE_NOT_CACHED, ZoneWriterPtr())); } + memory::LoadAction load_action; DataSourceClient* client(result.info->data_src_client_); - if (client) { - // Now do the final reload. If it does not exist in client, + if (client != NULL) { + // Now finally provide the writer. + // If it does not exist in client, // DataSourceError is thrown, which is exactly the result what we // want, so no need to handle it. ZoneIteratorPtr iterator(client->getIterator(name)); if (!iterator) { isc_throw(isc::Unexpected, "Null iterator from " << name); } - result.info->cache_->load(name, *iterator); + // And wrap the iterator into the correct functor (which + // keeps it alive as long as it is needed). + load_action = IteratorLoader(rrclass_, name, iterator); } else { // The MasterFiles special case const string filename(result.info->cache_->getFileName(name)); @@ -371,9 +439,14 @@ ConfigurableClientList::reload(const Name& name) { isc_throw(isc::Unexpected, "Confused about missing both filename " "and data source"); } - result.info->cache_->load(name, filename); + // boost::bind is enough here. + load_action = boost::bind(loadZoneDataFromFile, _1, rrclass_, name, + filename); } - return (ZONE_RELOADED); + return (ZoneWriterPair(ZONE_SUCCESS, + ZoneWriterPtr( + result.info->ztable_segment_-> + getZoneWriter(load_action, name, rrclass_)))); } // NOTE: This function is not tested, it would be complicated. However, the diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h index 61544efb4a..d1a35b5481 100644 --- a/src/lib/datasrc/client_list.h +++ b/src/lib/datasrc/client_list.h @@ -21,6 +21,7 @@ #include #include #include +#include "memory/zone_table_segment.h" #include #include @@ -37,12 +38,12 @@ typedef boost::shared_ptr DataSourceClientPtr; class DataSourceClientContainer; typedef boost::shared_ptr DataSourceClientContainerPtr; - // XXX: it's better to even hide the existence of the "memory" namespace. // We should probably consider pimpl for details of ConfigurableClientList // and hide real definitions except for itself and tests. namespace memory { class InMemoryClient; +class ZoneWriter; } /// \brief The list of data source clients. @@ -220,9 +221,6 @@ public: /// \param rrclass For which class the list should work. ConfigurableClientList(const isc::dns::RRClass& rrclass); - /// \brief Destructor - virtual ~ConfigurableClientList(); - /// \brief Exception thrown when there's an error in configuration. class ConfigurationError : public Exception { public: @@ -272,7 +270,8 @@ public: CACHE_DISABLED, ///< The cache is not enabled in this list. ZONE_NOT_CACHED, ///< Zone is served directly, not from cache. ZONE_NOT_FOUND, ///< Zone does not exist or not cached. - ZONE_RELOADED ///< The zone was successfully reloaded. + ZONE_SUCCESS ///< The zone was successfully reloaded or + /// the writer provided. }; /// \brief Reloads a cached zone. @@ -289,6 +288,36 @@ public: /// the original data source no longer contains the cached zone. ReloadResult reload(const dns::Name& zone); +private: + /// \brief Convenience type shortcut + typedef boost::shared_ptr ZoneWriterPtr; +public: + + /// \brief Return value of getCachedZoneWriter() + /// + /// A pair containing status and the zone writer, for the + /// getCachedZoneWriter() method. + typedef std::pair ZoneWriterPair; + + /// \brief Return a zone writer that can be used to reload a zone. + /// + /// This looks up a cached copy of zone and returns the ZoneWriter + /// that can be used to reload the content of the zone. This can + /// be used instead of reload() -- reload() works synchronously, which + /// is not what is needed every time. + /// + /// \param zone The origin of the zone to reload. + /// \return The result has two parts. The first one is a status describing + /// if it worked or not (and in case it didn't, also why). If the + /// status is ZONE_SUCCESS, the second part contains a shared pointer + /// to the writer. If the status is anything else, the second part is + /// NULL. + /// \throw DataSourceError or anything else that the data source + /// containing the zone might throw is propagated. + /// \throw DataSourceError if something unexpected happens, like when + /// the original data source no longer contains the cached zone. + ZoneWriterPair getCachedZoneWriter(const dns::Name& zone); + /// \brief Implementation of the ClientList::find. virtual FindResult find(const dns::Name& zone, bool want_exact_match = false, @@ -300,12 +329,16 @@ public: struct DataSourceInfo { // Plays a role of default constructor too (for vector) DataSourceInfo(const dns::RRClass& rrclass, - util::MemorySegment& mem_sgmt, + const boost::shared_ptr + & + ztable_segment, bool has_cache = false); DataSourceInfo(DataSourceClient* data_src_client, const DataSourceClientContainerPtr& container, bool has_cache, const dns::RRClass& rrclass, - util::MemorySegment& mem_sgmt); + const boost::shared_ptr + & + ztable_segment); DataSourceClient* data_src_client_; DataSourceClientContainerPtr container_; @@ -316,6 +349,7 @@ public: // No other applications or tests may use it. const DataSourceClient* getCacheClient() const; boost::shared_ptr cache_; + boost::shared_ptr ztable_segment_; }; /// \brief The collection of data sources. @@ -370,12 +404,6 @@ private: bool want_exact_match, bool want_finder) const; const isc::dns::RRClass rrclass_; - /// \brief Memory segment for in-memory cache. - /// - /// Note that this must be placed before data_sources_ so it won't be - /// destroyed before the built objects in the destructor. - boost::scoped_ptr mem_sgmt_; - /// \brief Currently active configuration. isc::data::ConstElementPtr configuration_; @@ -391,6 +419,11 @@ protected: DataSources data_sources_; }; +/// \brief Shortcut typedef for maps of client_lists. +typedef boost::shared_ptr > > + ClientListMapPtr; + } // namespace datasrc } // namespace isc diff --git a/src/lib/datasrc/data_source.h b/src/lib/datasrc/data_source.h index 37c536e5fe..bf5a7d73f2 100644 --- a/src/lib/datasrc/data_source.h +++ b/src/lib/datasrc/data_source.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATA_SOURCE_H -#define __DATA_SOURCE_H +#ifndef DATA_SOURCE_H +#define DATA_SOURCE_H #include diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h index 68955096de..320f3272a6 100644 --- a/src/lib/datasrc/database.h +++ b/src/lib/datasrc/database.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATABASE_DATASRC_H -#define __DATABASE_DATASRC_H +#ifndef DATABASE_DATASRC_H +#define DATABASE_DATASRC_H #include @@ -1425,7 +1425,7 @@ private: } } -#endif // __DATABASE_DATASRC_H +#endif // DATABASE_DATASRC_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/datasrc_config.h.pre.in b/src/lib/datasrc/datasrc_config.h.pre.in index 9074df6420..221cab0130 100644 --- a/src/lib/datasrc/datasrc_config.h.pre.in +++ b/src/lib/datasrc/datasrc_config.h.pre.in @@ -11,8 +11,9 @@ // 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_CONFIG_H -#define __DATASRC_CONFIG_H 1 + +#ifndef DATASRC_CONFIG_H +#define DATASRC_CONFIG_H 1 namespace isc { namespace datasrc { @@ -28,4 +29,4 @@ const char* const BACKEND_LIBRARY_PATH = "@@PKGLIBDIR@@/"; } // end namespace datasrc } // end namespace isc -#endif // __DATASRC_CONFIG_H +#endif // DATASRC_CONFIG_H diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes index 94b4d4249e..e7cb9d3f9d 100644 --- a/src/lib/datasrc/datasrc_messages.mes +++ b/src/lib/datasrc/datasrc_messages.mes @@ -305,10 +305,27 @@ Therefore, the zone will not be available for this process. If this is a problem, you should move the zone to some database backend (sqlite3, for example) and use it from there. -% DATASRC_MASTERLOAD_ERROR %1 -An error was found in the zone data for a MasterFiles zone. The zone -is not loaded. The specific error is shown in the message, and should -be addressed. +% DATASRC_LOAD_FROM_FILE_ERROR Error loading zone %1: %2 +An error was found in the zone data when it was being loaded from a +file. The zone was not loaded. The specific error is shown in the +message, and should be addressed. + +% DATASRC_LOAD_FROM_ITERATOR_ERROR Error loading zone %1: %2 +An error was found in the zone data when it was being loaded from +another data source. The zone was not loaded. The specific error is +shown in the message, and should be addressed. + +% DATASRC_MASTER_LOAD_ERROR %1:%2: Zone '%3/%4' contains error: %5 +There's an error in the given master file. The zone won't be loaded for +this reason. Parsing might follow, so you might get further errors and +warnings to fix everything at once. But in case the error is serious enough, +the parser might just give up or get confused and generate false errors +afterwards. + +% DATASRC_MASTER_LOAD_WARN %1:%2: Zone '%3/%4' has a potential problem: %5 +There's something suspicious in the master file. This is a warning only. +It may be a problem or it may be harmless, but it should be checked. +This problem does not stop the zone from being loaded. % DATASRC_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3' Debug information. An RRset is being added to the in-memory data source. diff --git a/src/lib/datasrc/exceptions.h b/src/lib/datasrc/exceptions.h new file mode 100644 index 0000000000..749b9559f3 --- /dev/null +++ b/src/lib/datasrc/exceptions.h @@ -0,0 +1,47 @@ +// 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_EXCEPTIONS_H +#define DATASRC_EXCEPTIONS_H 1 + +#include + +namespace isc { +namespace datasrc { + +/// Base class for a number of exceptions that are thrown while working +/// with zones. +struct ZoneException : public Exception { + ZoneException(const char* file, size_t line, const char* what) : + Exception(file, line, what) + {} +}; + +/// Base class for a number of exceptions that are thrown when zones are +/// being loaded. This is a recoverable exception. It should be possible +/// to skip the bad zone and continue loading/serving other zones. +struct ZoneLoaderException : public ZoneException { + ZoneLoaderException(const char* file, size_t line, const char* what) : + ZoneException(file, line, what) + {} +}; + +} // namespace datasrc +} // namespace isc + +#endif // DATASRC_EXCEPTIONS + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h index 2731f58775..45e4f9b5c8 100644 --- a/src/lib/datasrc/factory.h +++ b/src/lib/datasrc/factory.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATA_SOURCE_FACTORY_H -#define __DATA_SOURCE_FACTORY_H 1 +#ifndef DATA_SOURCE_FACTORY_H +#define DATA_SOURCE_FACTORY_H 1 #include #include diff --git a/src/lib/datasrc/iterator.h b/src/lib/datasrc/iterator.h index 99d33313c0..e1c69294da 100644 --- a/src/lib/datasrc/iterator.h +++ b/src/lib/datasrc/iterator.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATASRC_ZONE_ITERATOR_H -#define __DATASRC_ZONE_ITERATOR_H 1 +#ifndef DATASRC_ZONE_ITERATOR_H +#define DATASRC_ZONE_ITERATOR_H 1 #include @@ -98,7 +98,7 @@ public: } } -#endif // __DATASRC_ZONE_ITERATOR_H +#endif // DATASRC_ZONE_ITERATOR_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/logger.h b/src/lib/datasrc/logger.h index db4e5cbabc..a270082add 100644 --- a/src/lib/datasrc/logger.h +++ b/src/lib/datasrc/logger.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATASRC_LOGGER_H -#define __DATASRC_LOGGER_H +#ifndef DATASRC_LOGGER_H +#define DATASRC_LOGGER_H #include #include diff --git a/src/lib/datasrc/master_loader_callbacks.cc b/src/lib/datasrc/master_loader_callbacks.cc new file mode 100644 index 0000000000..54e4d0e744 --- /dev/null +++ b/src/lib/datasrc/master_loader_callbacks.cc @@ -0,0 +1,73 @@ +// 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 + +namespace isc { +namespace datasrc { + +namespace { + +void +logError(const isc::dns::Name& name, const isc::dns::RRClass& rrclass, + bool* ok, const std::string& source, size_t line, + const std::string& reason) +{ + LOG_ERROR(logger, DATASRC_MASTER_LOAD_ERROR).arg(source).arg(line). + arg(name).arg(rrclass).arg(reason); + if (ok != NULL) { + *ok = false; + } +} + +void +logWarning(const isc::dns::Name& name, const isc::dns::RRClass& rrclass, + const std::string& source, size_t line, const std::string& reason) +{ + LOG_WARN(logger, DATASRC_MASTER_LOAD_WARN).arg(source).arg(line). + arg(name).arg(rrclass).arg(reason); +} + +} + +isc::dns::MasterLoaderCallbacks +createMasterLoaderCallbacks(const isc::dns::Name& name, + const isc::dns::RRClass& rrclass, bool* ok) +{ + return (isc::dns::MasterLoaderCallbacks(boost::bind(&logError, name, + rrclass, ok, _1, _2, + _3), + boost::bind(&logWarning, name, + rrclass, _1, _2, _3))); +} + +isc::dns::AddRRsetCallback +createMasterLoaderAddCallback(ZoneUpdater& updater) { + return (boost::bind(&ZoneUpdater::addRRset, &updater, + // The callback provides a shared pointer, we + // need the object. This bind unpacks the object. + boost::bind(&isc::dns::RRsetPtr::operator*, _1))); +} + +} +} diff --git a/src/lib/datasrc/master_loader_callbacks.h b/src/lib/datasrc/master_loader_callbacks.h new file mode 100644 index 0000000000..c2583037b5 --- /dev/null +++ b/src/lib/datasrc/master_loader_callbacks.h @@ -0,0 +1,67 @@ +// 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_MASTER_LOADER_CALLBACKS_H +#define DATASRC_MASTER_LOADER_CALLBACKS_H + +#include + +namespace isc { +namespace dns { +class Name; +class RRClass; +} +namespace datasrc { + +class ZoneUpdater; + +/// \brief Create issue callbacks for MasterLoader +/// +/// This will create set of callbacks for the MasterLoader that +/// will be used to report any issues found in the zone data. +/// +/// \param name Name of the zone. Used in logging. +/// \param rrclass The class of the zone. Used in logging. +/// \param ok If this is non-NULL and there are any errors during +/// the loading, it is set to false. Otherwise, it is untouched. +/// \return Set of callbacks to be passed to the master loader. +/// \throw std::bad_alloc when allocation fails. +isc::dns::MasterLoaderCallbacks +createMasterLoaderCallbacks(const isc::dns::Name& name, + const isc::dns::RRClass& rrclass, bool* ok); + +/// \brief Create a callback for MasterLoader to add RRsets. +/// +/// This creates a callback that can be used by the MasterLoader to add +/// loaded RRsets into a zone updater. +/// +/// The zone updater should be opened in the replace mode no changes should +/// have been done to it yet (but it is not checked). It is not commited +/// automatically and it is up to the caller to commit the changes (or not). +/// It must not be destroyed for the whole time of loading. +/// +/// The function is mostly straightforward packing of the updater.addRRset +/// into a boost::function, it is defined explicitly due to small technical +/// annoyences around boost::bind application, so it can be reused. +/// +/// \param updater The zone updater to use. +/// \return The callback to be passed to MasterLoader. +/// \throw std::bad_alloc when allocation fails. +isc::dns::AddRRsetCallback +createMasterLoaderAddCallback(ZoneUpdater& updater); + +} +} + +#endif diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am index 743caa22f5..72b3273299 100644 --- a/src/lib/datasrc/memory/Makefile.am +++ b/src/lib/datasrc/memory/Makefile.am @@ -16,12 +16,19 @@ libdatasrc_memory_la_SOURCES += treenode_rrset.h treenode_rrset.cc libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc libdatasrc_memory_la_SOURCES += segment_object_holder.h -libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc libdatasrc_memory_la_SOURCES += logger.h logger.cc libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc libdatasrc_memory_la_SOURCES += zone_table_segment.h zone_table_segment.cc libdatasrc_memory_la_SOURCES += zone_table_segment_local.h zone_table_segment_local.cc +libdatasrc_memory_la_SOURCES += zone_data_updater.h zone_data_updater.cc +libdatasrc_memory_la_SOURCES += zone_data_loader.h zone_data_loader.cc +libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc +libdatasrc_memory_la_SOURCES += zone_writer.h +libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc +libdatasrc_memory_la_SOURCES += load_action.h +libdatasrc_memory_la_SOURCES += util_internal.h + nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc EXTRA_DIST = rdata_serialization_priv.cc diff --git a/src/lib/datasrc/memory/benchmarks/rdata_reader_bench.cc b/src/lib/datasrc/memory/benchmarks/rdata_reader_bench.cc index 4e6af56142..7bb919fe64 100644 --- a/src/lib/datasrc/memory/benchmarks/rdata_reader_bench.cc +++ b/src/lib/datasrc/memory/benchmarks/rdata_reader_bench.cc @@ -183,7 +183,6 @@ main(int argc, char* argv[]) { } } argc -= optind; - argv += optind; if (argc != 0) { usage(); } diff --git a/src/lib/datasrc/memory/benchmarks/rrset_render_bench.cc b/src/lib/datasrc/memory/benchmarks/rrset_render_bench.cc index bc418e85c9..266f4f592d 100644 --- a/src/lib/datasrc/memory/benchmarks/rrset_render_bench.cc +++ b/src/lib/datasrc/memory/benchmarks/rrset_render_bench.cc @@ -179,7 +179,6 @@ main(int argc, char* argv[]) { } } argc -= optind; - argv += optind; if (argc != 0) { usage(); } diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h index 272245d05f..48164525a9 100644 --- a/src/lib/datasrc/memory/domaintree.h +++ b/src/lib/datasrc/memory/domaintree.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef _DOMAINTREE_H -#define _DOMAINTREE_H 1 +#ifndef DOMAINTREE_H +#define DOMAINTREE_H 1 //! \file datasrc/memory/domaintree.h /// @@ -2126,7 +2126,7 @@ DomainTree::dumpDotHelper(std::ostream& os, } // namespace datasrc } // namespace isc -#endif // _DOMAINTREE_H +#endif // DOMAINTREE_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/memory/load_action.h b/src/lib/datasrc/memory/load_action.h new file mode 100644 index 0000000000..f9625af289 --- /dev/null +++ b/src/lib/datasrc/memory/load_action.h @@ -0,0 +1,47 @@ +// 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 LOAD_ACTION_H +#define LOAD_ACTION_H + +#include + +namespace isc { +// Forward declarations +namespace util{ +class MemorySegment; +} +namespace datasrc { +namespace memory { +class ZoneData; + +/// \brief Callback to load data into the memory +/// +/// This is called from the ZoneWriter whenever there's need to load the +/// zone data. The callback should allocate new ZoneData and fill it with +/// the zone content. It is up to the callback to know where or how to +/// load the data, or even the origin and class of the zone (it is assumed +/// the callback will be some kind of functor). +/// +/// All data should be allocated from the passed MemorySegment. The ownership +/// is passed onto the caller. +/// +/// It must not return NULL. +typedef boost::function LoadAction; + +} +} +} + +#endif diff --git a/src/lib/datasrc/memory/memory_client.cc b/src/lib/datasrc/memory/memory_client.cc index f4213470fe..65e1e3b555 100644 --- a/src/lib/datasrc/memory/memory_client.cc +++ b/src/lib/datasrc/memory/memory_client.cc @@ -17,12 +17,12 @@ #include #include #include -#include #include -#include #include #include #include +#include +#include #include @@ -31,20 +31,10 @@ #include #include -#include #include #include -#include - -#include -#include -#include -#include -#include -#include #include -#include #include #include #include @@ -53,560 +43,53 @@ using namespace std; using namespace isc::dns; using namespace isc::dns::rdata; using namespace isc::datasrc::memory; -using boost::scoped_ptr; +using namespace isc::util; namespace isc { namespace datasrc { namespace memory { using detail::SegmentObjectHolder; +using boost::shared_ptr; -namespace { -// Some type aliases -typedef DomainTree FileNameTree; -typedef DomainTreeNode FileNameNode; - -// A functor type used for loading. -typedef boost::function LoadCallback; - -} // end of anonymous namespace - -/// Implementation details for \c InMemoryClient hidden from the public -/// interface. -/// -/// For now, \c InMemoryClient only contains a \c ZoneTable object, which -/// consists of (pointers to) \c InMemoryZoneFinder objects, we may add more -/// member variables later for new features. -class InMemoryClient::InMemoryClientImpl { -private: - // The deleter for the filenames stored in the tree. - struct FileNameDeleter { - FileNameDeleter() {} - void operator()(std::string* filename) const { - delete filename; - } - }; +namespace { // unnamed namespace +// A helper internal class used by the memory client, used for deleting +// filenames stored in an internal tree. +class FileNameDeleter { public: - InMemoryClientImpl(util::MemorySegment& mem_sgmt, RRClass rrclass) : - mem_sgmt_(mem_sgmt), - rrclass_(rrclass), - zone_count_(0), - zone_table_(ZoneTable::create(mem_sgmt_, rrclass)), - file_name_tree_(FileNameTree::create(mem_sgmt_, false)) - {} - ~InMemoryClientImpl() { - FileNameDeleter deleter; - FileNameTree::destroy(mem_sgmt_, file_name_tree_, deleter); + FileNameDeleter() {} - ZoneTable::destroy(mem_sgmt_, zone_table_, rrclass_); - } - - util::MemorySegment& mem_sgmt_; - const RRClass rrclass_; - unsigned int zone_count_; - ZoneTable* zone_table_; - FileNameTree* file_name_tree_; - - // Common process for zone load. - // rrset_installer is a functor that takes another functor as an argument, - // and expected to call the latter for each RRset of the zone. How the - // sequence of the RRsets is generated depends on the internal - // details of the loader: either from a textual master file or from - // another data source. - // filename is the file name of the master file or empty if the zone is - // loaded from another data source. - result::Result load(const Name& zone_name, const string& filename, - boost::function rrset_installer); - - // Add the necessary magic for any wildcard contained in 'name' - // (including itself) to be found in the zone. - // - // In order for wildcard matching to work correctly in the zone finder, - // we must ensure that a node for the wildcarding level exists in the - // backend ZoneTree. - // E.g. if the wildcard name is "*.sub.example." then we must ensure - // that "sub.example." exists and is marked as a wildcard level. - // Note: the "wildcarding level" is for the parent name of the wildcard - // name (such as "sub.example."). - // - // We also perform the same trick for empty wild card names possibly - // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example'). - void addWildcards(const Name& zone_name, ZoneData& zone_data, - const Name& name) - { - Name wname(name); - const unsigned int labels(wname.getLabelCount()); - const unsigned int origin_labels(zone_name.getLabelCount()); - for (unsigned int l = labels; - l > origin_labels; - --l, wname = wname.split(1)) { - if (wname.isWildcard()) { - LOG_DEBUG(logger, DBG_TRACE_DATA, - DATASRC_MEMORY_MEM_ADD_WILDCARD).arg(name); - - // Ensure a separate level exists for the "wildcarding" name, - // and mark the node as "wild". - ZoneNode* node; - zone_data.insertName(mem_sgmt_, wname.split(1), &node); - node->setFlag(ZoneData::WILDCARD_NODE); - - // Ensure a separate level exists for the wildcard name. - // Note: for 'name' itself we do this later anyway, but the - // overhead should be marginal because wildcard names should - // be rare. - zone_data.insertName(mem_sgmt_, wname, &node); - } - } - } - - /* - * Does some checks in context of the data that are already in the zone. - * Currently checks for forbidden combinations of RRsets in the same - * domain (CNAME+anything, DNAME+NS). - * - * If such condition is found, it throws AddError. - */ - void contextCheck(const Name& zone_name, const AbstractRRset& rrset, - const RdataSet* set) const - { - // Ensure CNAME and other type of RR don't coexist for the same - // owner name except with NSEC, which is the only RR that can coexist - // with CNAME (and also RRSIG, which is handled separately) - if (rrset.getType() == RRType::CNAME()) { - for (const RdataSet* sp = set; sp != NULL; sp = sp->getNext()) { - if (sp->type != RRType::NSEC()) { - LOG_ERROR(logger, DATASRC_MEMORY_MEM_CNAME_TO_NONEMPTY). - arg(rrset.getName()); - isc_throw(AddError, "CNAME can't be added with " - << sp->type << " RRType for " - << rrset.getName()); - } - } - } else if ((rrset.getType() != RRType::NSEC()) && - (RdataSet::find(set, RRType::CNAME()) != NULL)) { - LOG_ERROR(logger, - DATASRC_MEMORY_MEM_CNAME_COEXIST).arg(rrset.getName()); - isc_throw(AddError, "CNAME and " << rrset.getType() << - " can't coexist for " << rrset.getName()); - } - - /* - * Similar with DNAME, but it must not coexist only with NS and only in - * non-apex domains. - * RFC 2672 section 3 mentions that it is implied from it and RFC 2181 - */ - if (rrset.getName() != zone_name && - // Adding DNAME, NS already there - ((rrset.getType() == RRType::DNAME() && - RdataSet::find(set, RRType::NS()) != NULL) || - // Adding NS, DNAME already there - (rrset.getType() == RRType::NS() && - RdataSet::find(set, RRType::DNAME()) != NULL))) - { - LOG_ERROR(logger, DATASRC_MEMORY_MEM_DNAME_NS).arg(rrset.getName()); - isc_throw(AddError, "DNAME can't coexist with NS in non-apex " - "domain " << rrset.getName()); - } - } - - // Validate rrset before adding it to the zone. If something is wrong - // it throws an exception. It doesn't modify the zone, and provides - // the strong exception guarantee. - void addValidation(const Name& zone_name, const ConstRRsetPtr rrset) { - if (!rrset) { - isc_throw(NullRRset, "The rrset provided is NULL"); - } - if (rrset->getRdataCount() == 0) { - isc_throw(AddError, "The rrset provided is empty: " << - rrset->getName() << "/" << rrset->getType()); - } - // Check for singleton RRs. It should probably handled at a different - // layer in future. - if ((rrset->getType() == RRType::CNAME() || - rrset->getType() == RRType::DNAME()) && - rrset->getRdataCount() > 1) - { - // XXX: this is not only for CNAME or DNAME. We should generalize - // this code for all other "singleton RR types" (such as SOA) in a - // separate task. - LOG_ERROR(logger, - DATASRC_MEMORY_MEM_SINGLETON).arg(rrset->getName()). - arg(rrset->getType()); - isc_throw(AddError, "multiple RRs of singleton type for " - << rrset->getName()); - } - // NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this - // implementation requests it be so at the moment. - if ((rrset->getType() == RRType::NSEC3() || - rrset->getType() == RRType::NSEC3PARAM()) && - rrset->getRdataCount() > 1) { - isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for " - << rrset->getName() << " which isn't supported"); - } - - // For RRSIGs, check consistency of the type covered. - // We know the RRset isn't empty, so the following check is safe. - if (rrset->getType() == RRType::RRSIG()) { - RdataIteratorPtr rit = rrset->getRdataIterator(); - const RRType covered = dynamic_cast( - rit->getCurrent()).typeCovered(); - for (rit->next(); !rit->isLast(); rit->next()) { - if (dynamic_cast( - rit->getCurrent()).typeCovered() != covered) { - isc_throw(AddError, "RRSIG contains mixed covered types: " - << rrset->toText()); - } - } - } - - const NameComparisonResult compare = - zone_name.compare(rrset->getName()); - if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN && - compare.getRelation() != NameComparisonResult::EQUAL) - { - LOG_ERROR(logger, - DATASRC_MEMORY_MEM_OUT_OF_ZONE).arg(rrset->getName()). - arg(zone_name); - isc_throw(OutOfZone, "The name " << rrset->getName() << - " is not contained in zone " << zone_name); - } - - // Some RR types do not really work well with a wildcard. - // Even though the protocol specifically doesn't completely ban such - // usage, we refuse to load a zone containing such RR in order to - // keep the lookup logic simpler and more predictable. - // See RFC4592 and (for DNAME) RFC6672 for more technical background. - // Note also that BIND 9 refuses NS at a wildcard, so in that sense - // we simply provide compatible behavior. - if (rrset->getName().isWildcard()) { - if (rrset->getType() == RRType::NS()) { - LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_NS). - arg(rrset->getName()); - isc_throw(AddError, "Invalid NS owner name (wildcard): " << - rrset->getName()); - } - if (rrset->getType() == RRType::DNAME()) { - LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_DNAME). - arg(rrset->getName()); - isc_throw(AddError, "Invalid DNAME owner name (wildcard): " << - rrset->getName()); - } - } - - // Owner names of NSEC3 have special format as defined in RFC5155, - // and cannot be a wildcard name or must be one label longer than - // the zone origin. While the RFC doesn't prohibit other forms of - // names, no sane zone would have such names for NSEC3. - // BIND 9 also refuses NSEC3 at wildcard. - if (rrset->getType() == RRType::NSEC3() && - (rrset->getName().isWildcard() || - rrset->getName().getLabelCount() != - zone_name.getLabelCount() + 1)) { - LOG_ERROR(logger, DATASRC_MEMORY_BAD_NSEC3_NAME). - arg(rrset->getName()); - isc_throw(AddError, "Invalid NSEC3 owner name: " << - rrset->getName()); - } - } - - void addNSEC3(const ConstRRsetPtr rrset, - const ConstRRsetPtr rrsig, - ZoneData& zone_data) - { - // We know rrset has exactly one RDATA - const generic::NSEC3& nsec3_rdata = - dynamic_cast( - rrset->getRdataIterator()->getCurrent()); - - NSEC3Data* nsec3_data = zone_data.getNSEC3Data(); - if (nsec3_data == NULL) { - nsec3_data = NSEC3Data::create(mem_sgmt_, nsec3_rdata); - zone_data.setNSEC3Data(nsec3_data); - zone_data.setSigned(true); - } else { - size_t salt_len = nsec3_data->getSaltLen(); - const uint8_t* salt_data = nsec3_data->getSaltData(); - const vector& salt_data_2 = nsec3_rdata.getSalt(); - - if ((nsec3_rdata.getHashalg() != nsec3_data->hashalg) || - (nsec3_rdata.getIterations() != nsec3_data->iterations) || - (salt_data_2.size() != salt_len)) { - isc_throw(AddError, - "NSEC3 with inconsistent parameters: " << - rrset->toText()); - } - if ((salt_len > 0) && - (std::memcmp(&salt_data_2[0], salt_data, salt_len) != 0)) { - isc_throw(AddError, - "NSEC3 with inconsistent parameters: " << - rrset->toText()); - } - } - - ZoneNode* node; - nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node); - - RdataEncoder encoder; - RdataSet* set = RdataSet::create(mem_sgmt_, encoder, rrset, rrsig); - RdataSet* old_set = node->setData(set); - if (old_set != NULL) { - RdataSet::destroy(mem_sgmt_, rrclass_, old_set); - } - } - - void addRdataSet(const Name& zone_name, ZoneData& zone_data, - const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig) - { - if (rrset->getType() == RRType::NSEC3()) { - addNSEC3(rrset, rrsig, zone_data); - } else { - ZoneNode* node; - zone_data.insertName(mem_sgmt_, rrset->getName(), &node); - - RdataSet* rdataset_head = node->getData(); - - // Checks related to the surrounding data. - // Note: when the check fails and the exception is thrown, - // it may break strong exception guarantee. At the moment - // we prefer code simplicity and don't bother to introduce - // complicated recovery code. - contextCheck(zone_name, *rrset, rdataset_head); - - if (RdataSet::find(rdataset_head, rrset->getType()) != NULL) { - isc_throw(AddError, - "RRset of the type already exists: " - << rrset->getName() << " (type: " - << rrset->getType() << ")"); - } - - RdataEncoder encoder; - RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder, rrset, - rrsig); - rdataset->next = rdataset_head; - node->setData(rdataset); - - // Ok, we just put it in - - // If this RRset creates a zone cut at this node, mark the - // node indicating the need for callback in find(). - if (rrset->getType() == RRType::NS() && - rrset->getName() != zone_name) { - node->setFlag(ZoneNode::FLAG_CALLBACK); - // If it is DNAME, we have a callback as well here - } else if (rrset->getType() == RRType::DNAME()) { - node->setFlag(ZoneNode::FLAG_CALLBACK); - } - - // If we've added NSEC3PARAM at zone origin, set up NSEC3 - // specific data or check consistency with already set up - // parameters. - if (rrset->getType() == RRType::NSEC3PARAM() && - rrset->getName() == zone_name) { - // We know rrset has exactly one RDATA - const generic::NSEC3PARAM& param = - dynamic_cast - (rrset->getRdataIterator()->getCurrent()); - - NSEC3Data* nsec3_data = zone_data.getNSEC3Data(); - if (nsec3_data == NULL) { - nsec3_data = NSEC3Data::create(mem_sgmt_, param); - zone_data.setNSEC3Data(nsec3_data); - zone_data.setSigned(true); - } else { - size_t salt_len = nsec3_data->getSaltLen(); - const uint8_t* salt_data = nsec3_data->getSaltData(); - const vector& salt_data_2 = param.getSalt(); - - if ((param.getHashalg() != nsec3_data->hashalg) || - (param.getIterations() != nsec3_data->iterations) || - (salt_data_2.size() != salt_len)) { - isc_throw(AddError, - "NSEC3PARAM with inconsistent parameters: " - << rrset->toText()); - } - - if ((salt_len > 0) && - (std::memcmp(&salt_data_2[0], - salt_data, salt_len) != 0)) { - isc_throw(AddError, - "NSEC3PARAM with inconsistent parameters: " - << rrset->toText()); - } - } - } else if (rrset->getType() == RRType::NSEC()) { - // If it is NSEC signed zone, we mark the zone as signed - // (conceptually "signed" is a broader notion but our current - // zone finder implementation regards "signed" as "NSEC - // signed") - zone_data.setSigned(true); - } - } - } - - // Implementation of InMemoryClient::add() - void add(const ConstRRsetPtr& rrset, const ConstRRsetPtr& sig_rrset, - const Name& zone_name, ZoneData& zone_data) - { - // Sanitize input. This will cause an exception to be thrown - // if the input RRset is empty. - addValidation(zone_name, rrset); - if (sig_rrset) { - addValidation(zone_name, sig_rrset); - } - - // OK, can add the RRset. - LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET). - arg(rrset->getName()).arg(rrset->getType()).arg(zone_name); - - // Add wildcards possibly contained in the owner name to the domain - // tree. This can only happen for the normal (non-NSEC3) tree. - // Note: this can throw an exception, breaking strong exception - // guarantee. (see also the note for the call to contextCheck() - // above). - if (rrset->getType() != RRType::NSEC3()) { - addWildcards(zone_name, zone_data, rrset->getName()); - } - - addRdataSet(zone_name, zone_data, rrset, sig_rrset); + void operator()(std::string* filename) const { + delete filename; } }; -// A helper internal class for load(). make it non-copyable to avoid -// accidental copy. -// -// The current internal implementation expects that both a normal -// (non RRSIG) RRset and (when signed) its RRSIG are added at once. -// Also in the current implementation, the input sequence of RRsets -// are grouped with their owner name (so once a new owner name is encountered, -// no subsequent RRset has the previous owner name), but the ordering -// in the same group is not fixed. So we hold all RRsets of the same -// owner name in node_rrsets_ and node_rrsigsets_, and add the matching -// pairs of RRsets to the zone when we see a new owner name. -// -// The caller is responsible for adding the RRsets of the last group -// in the input sequence by explicitly calling flushNodeRRsets() at the -// end. It's cleaner and more robust if we let the destructor of this class -// do it, but since we cannot guarantee the adding operation is exception free, -// we don't choose that option to maintain the common expectation for -// destructors. -class InMemoryClient::Loader : boost::noncopyable { - typedef std::map NodeRRsets; - typedef NodeRRsets::value_type NodeRRsetsVal; -public: - Loader(InMemoryClientImpl* client_impl, const Name& zone_name, - ZoneData& zone_data) : - client_impl_(client_impl), zone_name_(zone_name), zone_data_(zone_data) - {} - void addFromLoad(const ConstRRsetPtr& rrset) { - // If we see a new name, flush the temporary holders, adding the - // pairs of RRsets and RRSIGs of the previous name to the zone. - if ((!node_rrsets_.empty() || !node_rrsigsets_.empty()) && - getCurrentName() != rrset->getName()) { - flushNodeRRsets(); - } +} // end of unnamed namespace - // Store this RRset until it can be added to the zone. The current - // implementation requires RRs of the same RRset should be added at - // once, so we check the "duplicate" here. - const bool is_rrsig = rrset->getType() == RRType::RRSIG(); - NodeRRsets& node_rrsets = is_rrsig ? node_rrsigsets_ : node_rrsets_; - const RRType& rrtype = is_rrsig ? - getCoveredType(rrset) : rrset->getType(); - if (!node_rrsets.insert(NodeRRsetsVal(rrtype, rrset)).second) { - isc_throw(AddError, - "Duplicate add of the same type of" - << (is_rrsig ? " RRSIG" : "") << " RRset: " - << rrset->getName() << "/" << rrtype); - } - } - void flushNodeRRsets() { - BOOST_FOREACH(NodeRRsetsVal val, node_rrsets_) { - // Identify the corresponding RRSIG for the RRset, if any. - // If found add both the RRset and its RRSIG at once. - ConstRRsetPtr sig_rrset; - NodeRRsets::iterator sig_it = - node_rrsigsets_.find(val.first); - if (sig_it != node_rrsigsets_.end()) { - sig_rrset = sig_it->second; - node_rrsigsets_.erase(sig_it); - } - client_impl_->add(val.second, sig_rrset, zone_name_, zone_data_); - } +InMemoryClient::InMemoryClient(shared_ptr ztable_segment, + RRClass rrclass) : + ztable_segment_(ztable_segment), + rrclass_(rrclass), + zone_count_(0), + file_name_tree_(FileNameTree::create( + ztable_segment_->getMemorySegment(), false)) +{} - // Right now, we don't accept RRSIG without covered RRsets (this - // should eventually allowed, but to do so we'll need to update the - // finder). - if (!node_rrsigsets_.empty()) { - isc_throw(AddError, "RRSIG is added without covered RRset for " - << getCurrentName()); - } - - node_rrsets_.clear(); - node_rrsigsets_.clear(); - } -private: - // A helper to identify the covered type of an RRSIG. - static RRType getCoveredType(const ConstRRsetPtr& sig_rrset) { - RdataIteratorPtr it = sig_rrset->getRdataIterator(); - // Empty RRSIG shouldn't be passed either via a master file or another - // data source iterator, but it could still happen if the iterator - // has a bug. We catch and reject such cases. - if (it->isLast()) { - isc_throw(isc::Unexpected, - "Empty RRset is passed in-memory loader, name: " - << sig_rrset->getName()); - } - return (dynamic_cast(it->getCurrent()). - typeCovered()); - } - const Name& getCurrentName() const { - if (!node_rrsets_.empty()) { - return (node_rrsets_.begin()->second->getName()); - } - assert(!node_rrsigsets_.empty()); - return (node_rrsigsets_.begin()->second->getName()); - } - -private: - InMemoryClientImpl* client_impl_; - const Name& zone_name_; - ZoneData& zone_data_; - NodeRRsets node_rrsets_; - NodeRRsets node_rrsigsets_; -}; +InMemoryClient::~InMemoryClient() { + MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment(); + FileNameDeleter deleter; + FileNameTree::destroy(mem_sgmt, file_name_tree_, deleter); +} result::Result -InMemoryClient::InMemoryClientImpl::load( - const Name& zone_name, - const string& filename, - boost::function rrset_installer) +InMemoryClient::loadInternal(const isc::dns::Name& zone_name, + const std::string& filename, + ZoneData* zone_data) { + MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment(); SegmentObjectHolder holder( - mem_sgmt_, ZoneData::create(mem_sgmt_, zone_name), rrclass_); - - Loader loader(this, zone_name, *holder.get()); - rrset_installer(boost::bind(&Loader::addFromLoad, &loader, _1)); - // Add any last RRsets that were left - loader.flushNodeRRsets(); - - const ZoneNode* origin_node = holder.get()->getOriginNode(); - const RdataSet* set = origin_node->getData(); - // If the zone is NSEC3-signed, check if it has NSEC3PARAM - if (holder.get()->isNSEC3Signed()) { - if (RdataSet::find(set, RRType::NSEC3PARAM()) == NULL) { - LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM). - arg(zone_name).arg(rrclass_); - } - } - - // When an empty zone file is loaded, the origin doesn't even have - // an SOA RR. This condition should be avoided, and hence load() - // should throw when an empty zone is loaded. - if (RdataSet::find(set, RRType::SOA()) == NULL) { - isc_throw(EmptyZone, - "Won't create an empty zone for: " << zone_name); - } + mem_sgmt, zone_data, rrclass_); LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE). arg(zone_name).arg(rrclass_); @@ -614,7 +97,7 @@ InMemoryClient::InMemoryClientImpl::load( // Set the filename in file_name_tree_ now, so that getFileName() // can use it (during zone reloading). FileNameNode* node(NULL); - switch (file_name_tree_->insert(mem_sgmt_, zone_name, &node)) { + switch (file_name_tree_->insert(mem_sgmt, zone_name, &node)) { case FileNameTree::SUCCESS: case FileNameTree::ALREADYEXISTS: // These are OK @@ -629,9 +112,10 @@ InMemoryClient::InMemoryClientImpl::load( const std::string* tstr = node->setData(new std::string(filename)); delete tstr; - const ZoneTable::AddResult result(zone_table_->addZone(mem_sgmt_, rrclass_, - zone_name, - holder.release())); + ZoneTable* zone_table = ztable_segment_->getHeader().getTable(); + const ZoneTable::AddResult result(zone_table->addZone(mem_sgmt, rrclass_, + zone_name, + holder.release())); if (result.code == result::SUCCESS) { // Only increment the zone count if the zone doesn't already // exist. @@ -639,52 +123,20 @@ InMemoryClient::InMemoryClientImpl::load( } // Destroy the old instance of the zone if there was any if (result.zone_data != NULL) { - ZoneData::destroy(mem_sgmt_, result.zone_data, rrclass_); + ZoneData::destroy(mem_sgmt, result.zone_data, rrclass_); } return (result.code); } -namespace { -// A wrapper for dns::masterLoad used by load() below. Essentially it -// converts the two callback types. Note the mostly redundant wrapper of -// boost::bind. It converts function to -// function (masterLoad() expects the latter). SunStudio -// doesn't seem to do this conversion if we just pass 'callback'. -void -masterLoadWrapper(const char* const filename, const Name& origin, - const RRClass& zone_class, LoadCallback callback) -{ - masterLoad(filename, origin, zone_class, boost::bind(callback, _1)); -} - -// The installer called from Impl::load() for the iterator version of load(). -void -generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) { - ConstRRsetPtr rrset; - while ((rrset = iterator->getNextRRset()) != NULL) { - callback(rrset); - } -} -} - -InMemoryClient::InMemoryClient(util::MemorySegment& mem_sgmt, - RRClass rrclass) : - impl_(new InMemoryClientImpl(mem_sgmt, rrclass)) -{} - -InMemoryClient::~InMemoryClient() { - delete impl_; -} - RRClass InMemoryClient::getClass() const { - return (impl_->rrclass_); + return (rrclass_); } unsigned int InMemoryClient::getZoneCount() const { - return (impl_->zone_count_); + return (zone_count_); } isc::datasrc::DataSourceClient::FindResult @@ -692,7 +144,8 @@ InMemoryClient::findZone(const isc::dns::Name& zone_name) const { LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_FIND_ZONE).arg(zone_name); - ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name)); + const ZoneTable* zone_table = ztable_segment_->getHeader().getTable(); + const ZoneTable::FindResult result(zone_table->findZone(zone_name)); ZoneFinderPtr finder; if (result.code != result::NOTFOUND) { @@ -704,34 +157,37 @@ InMemoryClient::findZone(const isc::dns::Name& zone_name) const { const ZoneData* InMemoryClient::findZoneData(const isc::dns::Name& zone_name) { - ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name)); + const ZoneTable* zone_table = ztable_segment_->getHeader().getTable(); + const ZoneTable::FindResult result(zone_table->findZone(zone_name)); return (result.zone_data); } result::Result InMemoryClient::load(const isc::dns::Name& zone_name, - const std::string& filename) { + const std::string& filename) +{ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD).arg(zone_name). arg(filename); - return (impl_->load(zone_name, filename, - boost::bind(masterLoadWrapper, filename.c_str(), - zone_name, getClass(), _1))); + MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment(); + ZoneData* zone_data = loadZoneData(mem_sgmt, rrclass_, zone_name, + filename); + return (loadInternal(zone_name, filename, zone_data)); } result::Result -InMemoryClient::load(const isc::dns::Name& zone_name, - ZoneIterator& iterator) { - return (impl_->load(zone_name, string(), - boost::bind(generateRRsetFromIterator, - &iterator, _1))); +InMemoryClient::load(const isc::dns::Name& zone_name, ZoneIterator& iterator) { + MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment(); + ZoneData* zone_data = loadZoneData(mem_sgmt, rrclass_, zone_name, + iterator); + return (loadInternal(zone_name, string(), zone_data)); } const std::string InMemoryClient::getFileName(const isc::dns::Name& zone_name) const { const FileNameNode* node(NULL); - const FileNameTree::Result result = impl_->file_name_tree_->find(zone_name, - &node); + const FileNameTree::Result result = file_name_tree_->find(zone_name, + &node); if (result == FileNameTree::EXACTMATCH) { return (*node->getData()); } else { @@ -754,7 +210,7 @@ private: bool separate_rrs_; bool ready_; public: - MemoryIterator(const RRClass rrclass, + MemoryIterator(const RRClass& rrclass, const ZoneTree& tree, const Name& origin, bool separate_rrs) : rrclass_(rrclass), @@ -853,7 +309,8 @@ public: ZoneIteratorPtr InMemoryClient::getIterator(const Name& name, bool separate_rrs) const { - ZoneTable::FindResult result(impl_->zone_table_->findZone(name)); + const ZoneTable* zone_table = ztable_segment_->getHeader().getTable(); + const ZoneTable::FindResult result(zone_table->findZone(name)); if (result.code != result::SUCCESS) { isc_throw(DataSourceError, "No such zone: " + name.toText()); } diff --git a/src/lib/datasrc/memory/memory_client.h b/src/lib/datasrc/memory/memory_client.h index c37ad53c0b..169421f7f9 100644 --- a/src/lib/datasrc/memory/memory_client.h +++ b/src/lib/datasrc/memory/memory_client.h @@ -22,6 +22,8 @@ #include #include +#include + #include namespace isc { @@ -34,6 +36,8 @@ class RRsetList; namespace datasrc { namespace memory { +class ZoneTableSegment; + /// \brief A data source client that holds all necessary data in memory. /// /// The \c InMemoryClient class provides an access to a conceptual data @@ -60,7 +64,7 @@ public: /// This constructor internally involves resource allocation, and if /// it fails, a corresponding standard exception will be thrown. /// It never throws an exception otherwise. - InMemoryClient(util::MemorySegment& mem_sgmt, + InMemoryClient(boost::shared_ptr ztable_segment, isc::dns::RRClass rrclass); /// The destructor. @@ -83,11 +87,11 @@ public: /// current content. The masterfile parsing ability is kind of limited, /// see isc::dns::masterLoad. /// - /// This throws isc::dns::MasterLoadError if there is problem with loading - /// (missing file, malformed, it contains different zone, etc - see - /// isc::dns::masterLoad for details). + /// This throws isc::dns::MasterLoadError or AddError if there are + /// problems with loading (missing file, malformed data, unexpected + /// zone, etc. - see isc::dns::masterLoad for details). /// - /// In case of internal problems, OutOfZone, NullRRset or AssertError could + /// In case of internal problems, NullRRset or AssertError could /// be thrown, but they should not be expected. Exceptions caused by /// allocation may be thrown as well. /// @@ -139,40 +143,6 @@ public: /// zone from a file before. const std::string getFileName(const isc::dns::Name& zone_name) const; - /// \brief RRset is NULL exception. - /// - /// This is thrown if the provided RRset parameter is NULL. - struct NullRRset : public InvalidParameter { - NullRRset(const char* file, size_t line, const char* what) : - InvalidParameter(file, line, what) - { } - }; - - /// \brief Zone is empty exception. - /// - /// This is thrown if we have an empty zone created as a result of - /// load(). - struct EmptyZone : public InvalidParameter { - EmptyZone(const char* file, size_t line, const char* what) : - InvalidParameter(file, line, what) - { } - }; - - /// \brief General failure exception for \c add(). - /// - /// This is thrown against general error cases in adding an RRset - /// to the zone. - /// - /// Note: this exception would cover cases for \c OutOfZone or - /// \c NullRRset. We'll need to clarify and unify the granularity - /// of exceptions eventually. For now, exceptions are added as - /// developers see the need for it. - struct AddError : public InvalidParameter { - AddError(const char* file, size_t line, const char* what) : - InvalidParameter(file, line, what) - { } - }; - /// Returns a \c ZoneFinder result that best matches the given name. /// /// This derived version of the method never throws an exception. @@ -210,14 +180,20 @@ public: uint32_t end_serial) const; private: - // TODO: Do we still need the PImpl if nobody should manipulate this class - // directly any more (it should be handled through DataSourceClient)? - class InMemoryClientImpl; - InMemoryClientImpl* impl_; + // Some type aliases + typedef DomainTree FileNameTree; + typedef DomainTreeNode FileNameNode; - // A helper internal class used by load(). It maintains some intermediate - // states while loading RRs of the zone. - class Loader; + // Common process for zone load. Registers filename internally and + // adds the ZoneData to the ZoneTable. + result::Result loadInternal(const isc::dns::Name& zone_name, + const std::string& filename, + ZoneData* zone_data); + + boost::shared_ptr ztable_segment_; + const isc::dns::RRClass rrclass_; + unsigned int zone_count_; + FileNameTree* file_name_tree_; }; } // namespace memory diff --git a/src/lib/datasrc/memory/rdataset.cc b/src/lib/datasrc/memory/rdataset.cc index aae64f3ca4..e7a070f88f 100644 --- a/src/lib/datasrc/memory/rdataset.cc +++ b/src/lib/datasrc/memory/rdataset.cc @@ -122,8 +122,8 @@ RdataSet::create(util::MemorySegment& mem_sgmt, RdataEncoder& encoder, } void -RdataSet::destroy(util::MemorySegment& mem_sgmt, RRClass rrclass, - RdataSet* rdataset) +RdataSet::destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset, + RRClass rrclass) { const size_t data_len = RdataReader(rrclass, rdataset->type, diff --git a/src/lib/datasrc/memory/rdataset.h b/src/lib/datasrc/memory/rdataset.h index b0b3b487ba..ffa5075439 100644 --- a/src/lib/datasrc/memory/rdataset.h +++ b/src/lib/datasrc/memory/rdataset.h @@ -187,12 +187,12 @@ public: /// /// \param mem_sgmt The \c MemorySegment that allocated memory for /// \c node. - /// \param rrclass The RR class of the \c RdataSet to be destroyed. /// \param rdataset A non NULL pointer to a valid \c RdataSet object + /// \param rrclass The RR class of the \c RdataSet to be destroyed. /// that was originally created by the \c create() method (the behavior /// is undefined if this condition isn't met). - static void destroy(util::MemorySegment& mem_sgmt, dns::RRClass rrclass, - RdataSet* rdataset); + static void destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset, + dns::RRClass rrclass); /// \brief Find \c RdataSet of given RR type from a list (const version). /// @@ -205,6 +205,11 @@ public: /// if not found in the entire list, it returns NULL. The head pointer /// can be NULL, in which case this function will simply return NULL. /// + /// By default, this method ignores an RdataSet that only contains an + /// RRSIG (i.e., missing the covered RdataSet); if the optional + /// sigonly_ok parameter is explicitly set to true, it matches such + /// RdataSet and returns it if found. + /// /// \note This function is defined as a (static) class method to /// clarify its an operation for \c RdataSet objects and to make the /// name shorter. But its implementation does not depend on private @@ -215,10 +220,14 @@ public: /// \param rdata_head A pointer to \c RdataSet from which the search /// starts. It can be NULL. /// \param type The RRType of \c RdataSet to find. + /// \param sigonly_ok Whether it should find an RdataSet that only has + /// RRSIG /// \return A pointer to the found \c RdataSet or NULL if none found. static const RdataSet* - find(const RdataSet* rdataset_head, const dns::RRType& type) { - return (find(rdataset_head, type)); + find(const RdataSet* rdataset_head, const dns::RRType& type, + bool sigonly_ok = false) + { + return (find(rdataset_head, type, sigonly_ok)); } /// \brief Find \c RdataSet of given RR type from a list (non const @@ -227,8 +236,10 @@ public: /// This is similar to the const version, except it takes and returns non /// const pointers. static RdataSet* - find(RdataSet* rdataset_head, const dns::RRType& type) { - return (find(rdataset_head, type)); + find(RdataSet* rdataset_head, const dns::RRType& type, + bool sigonly_ok = false) + { + return (find(rdataset_head, type, sigonly_ok)); } typedef boost::interprocess::offset_ptr RdataSetPtr; @@ -347,12 +358,14 @@ private: // Shared by both mutable and immutable versions of find() template static RdataSetType* - find(RdataSetType* rdataset_head, const dns::RRType& type) { + find(RdataSetType* rdataset_head, const dns::RRType& type, bool sigonly_ok) + { for (RdataSetType* rdataset = rdataset_head; rdataset != NULL; rdataset = rdataset->getNext()) // use getNext() for efficiency { - if (rdataset->type == type) { + if (rdataset->type == type && + (rdataset->getRdataCount() > 0 || sigonly_ok)) { return (rdataset); } } diff --git a/src/lib/datasrc/memory/treenode_rrset.cc b/src/lib/datasrc/memory/treenode_rrset.cc index f51b2f95ac..e7ed20cbdb 100644 --- a/src/lib/datasrc/memory/treenode_rrset.cc +++ b/src/lib/datasrc/memory/treenode_rrset.cc @@ -342,7 +342,7 @@ TreeNodeRRset::isSameKind(const AbstractRRset& abs_other) const { // Same for the owner name. Comparing the nodes also detect // the case where RR classes are different (see the method description // of the header for details). - if (node_ != other->node_ ) { + if (node_ != other->node_) { return (false); } // If one is constructed with a "real name" and the other isn't diff --git a/src/lib/datasrc/memory/util_internal.h b/src/lib/datasrc/memory/util_internal.h new file mode 100644 index 0000000000..05aaa29a9c --- /dev/null +++ b/src/lib/datasrc/memory/util_internal.h @@ -0,0 +1,57 @@ +// 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_MEMORY_UTIL_INTERNAL_H +#define DATASRC_MEMORY_UTIL_INTERNAL_H 1 + +#include +#include +#include + +namespace isc { +namespace datasrc { +namespace memory { +namespace detail { + +/// \brief Return the covered RR type of an RRSIG RRset. +/// +/// This is a commonly used helper to extract the type covered field of an +/// RRSIG RRset and return it in the form of an RRType object. +/// +/// Normally, an empty RRSIG shouldn't be passed to this function, whether +/// it comes from a master file or another data source iterator, but it could +/// still happen in some buggy situations. This function catches and rejects +/// such cases. +inline dns::RRType +getCoveredType(const dns::ConstRRsetPtr& sig_rrset) { + dns::RdataIteratorPtr it = sig_rrset->getRdataIterator(); + if (it->isLast()) { + isc_throw(isc::Unexpected, + "Empty RRset is passed in-memory loader, name: " + << sig_rrset->getName()); + } + return (dynamic_cast(it->getCurrent()). + typeCovered()); +} + +} // namespace detail +} // namespace memory +} // namespace datasrc +} // namespace isc + +#endif // DATASRC_MEMORY_UTIL_INTERNAL_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/datasrc/memory/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc index e2cbdef141..cc314196ff 100644 --- a/src/lib/datasrc/memory/zone_data.cc +++ b/src/lib/datasrc/memory/zone_data.cc @@ -49,7 +49,7 @@ rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt, rdataset = rdataset_next) { rdataset_next = rdataset->getNext(); - RdataSet::destroy(*mem_sgmt, rrclass, rdataset); + RdataSet::destroy(*mem_sgmt, rdataset, rrclass); } } @@ -61,21 +61,28 @@ nullDeleter(RdataSet* rdataset_head) { NSEC3Data* NSEC3Data::create(util::MemorySegment& mem_sgmt, + const Name& zone_origin, const generic::NSEC3PARAM& rdata) { - return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(), + return (NSEC3Data::create(mem_sgmt, zone_origin, + rdata.getHashalg(), rdata.getFlags(), rdata.getIterations(), rdata.getSalt())); } NSEC3Data* -NSEC3Data::create(util::MemorySegment& mem_sgmt, const generic::NSEC3& rdata) { - return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(), +NSEC3Data::create(util::MemorySegment& mem_sgmt, + const Name& zone_origin, + const generic::NSEC3& rdata) +{ + return (NSEC3Data::create(mem_sgmt, zone_origin, + rdata.getHashalg(), rdata.getFlags(), rdata.getIterations(), rdata.getSalt())); } NSEC3Data* -NSEC3Data::create(util::MemorySegment& mem_sgmt, uint8_t hashalg, - uint8_t flags, uint16_t iterations, +NSEC3Data::create(util::MemorySegment& mem_sgmt, + const Name& zone_origin, + uint8_t hashalg, uint8_t flags, uint16_t iterations, const std::vector& salt) { // NSEC3Data allocation can throw. To avoid leaking the tree, we manage @@ -87,6 +94,11 @@ NSEC3Data::create(util::MemorySegment& mem_sgmt, uint8_t hashalg, mem_sgmt, ZoneTree::create(mem_sgmt, true), boost::bind(nullDeleter, _1)); + ZoneTree* tree = holder.get(); + const ZoneTree::Result result = + tree->insert(mem_sgmt, zone_origin, NULL); + assert(result == ZoneTree::SUCCESS); + const size_t salt_len = salt.size(); void* p = mem_sgmt.allocate(sizeof(NSEC3Data) + 1 + salt_len); diff --git a/src/lib/datasrc/memory/zone_data.h b/src/lib/datasrc/memory/zone_data.h index 09306d791d..974ce241d1 100644 --- a/src/lib/datasrc/memory/zone_data.h +++ b/src/lib/datasrc/memory/zone_data.h @@ -90,9 +90,11 @@ public: /// /// \param mem_sgmt A \c MemorySegment from which memory for the new /// \c NSEC3Data is allocated. + /// \param zone_origin The zone origin. /// \param rdata An NSEC3PARAM RDATA that specifies the NSEC3 parameters /// to be stored. static NSEC3Data* create(util::MemorySegment& mem_sgmt, + const dns::Name& zone_origin, const dns::rdata::generic::NSEC3PARAM& rdata); /// \brief Allocate and construct \c NSEC3Data from NSEC3 Rdata. @@ -104,9 +106,11 @@ public: /// /// \param mem_sgmt A \c MemorySegment from which memory for the new /// \c NSEC3Data is allocated. + /// \param zone_origin The zone origin. /// \param rdata An NSEC3 RDATA that specifies the NSEC3 parameters /// to be stored. static NSEC3Data* create(util::MemorySegment& mem_sgmt, + const dns::Name& zone_origin, const dns::rdata::generic::NSEC3& rdata); /// \brief Destruct and deallocate \c NSEC3Data. @@ -193,8 +197,10 @@ public: private: // Common subroutine for the public versions of create(). - static NSEC3Data* create(util::MemorySegment& mem_sgmt, uint8_t hashalg, - uint8_t flags, uint16_t iterations, + static NSEC3Data* create(util::MemorySegment& mem_sgmt, + const dns::Name& zone_origin, + uint8_t hashalg, uint8_t flags, + uint16_t iterations, const std::vector& salt); /// \brief The constructor. @@ -366,9 +372,9 @@ public: /// /// \param mem_sgmt A \c MemorySegment from which memory for the new /// \c ZoneData is allocated. - /// \param name The zone name. + /// \param zone_origin The zone origin. static ZoneData* create(util::MemorySegment& mem_sgmt, - const dns::Name& zone_name); + const dns::Name& zone_origin); /// \brief Destruct and deallocate \c ZoneData. /// diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc new file mode 100644 index 0000000000..051acc30d5 --- /dev/null +++ b/src/lib/datasrc/memory/zone_data_loader.cc @@ -0,0 +1,237 @@ +// 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 +#include + +#include + +using namespace isc::dns; +using namespace isc::dns::rdata; + +namespace isc { +namespace datasrc { +namespace memory { + +using detail::SegmentObjectHolder; +using detail::getCoveredType; + +namespace { // unnamed namespace + +// A functor type used for loading. +typedef boost::function LoadCallback; + +// A helper internal class for \c loadZoneData(). make it non-copyable +// to avoid accidental copy. +// +// The current internal implementation expects that both a normal +// (non RRSIG) RRset and (when signed) its RRSIG are added at once. +// Also in the current implementation, the input sequence of RRsets +// are grouped with their owner name (so once a new owner name is encountered, +// no subsequent RRset has the previous owner name), but the ordering +// in the same group is not fixed. So we hold all RRsets of the same +// owner name in node_rrsets_ and node_rrsigsets_, and add the matching +// pairs of RRsets to the zone when we see a new owner name. +// +// The caller is responsible for adding the RRsets of the last group +// in the input sequence by explicitly calling flushNodeRRsets() at the +// end. It's cleaner and more robust if we let the destructor of this class +// do it, but since we cannot guarantee the adding operation is exception free, +// we don't choose that option to maintain the common expectation for +// destructors. +class ZoneDataLoader : boost::noncopyable { +public: + ZoneDataLoader(util::MemorySegment& mem_sgmt, + const isc::dns::RRClass rrclass, + const isc::dns::Name& zone_name, ZoneData& zone_data) : + updater_(mem_sgmt, rrclass, zone_name, zone_data) + {} + + void addFromLoad(const isc::dns::ConstRRsetPtr& rrset); + void flushNodeRRsets(); + +private: + typedef std::map NodeRRsets; + typedef NodeRRsets::value_type NodeRRsetsVal; + + // A helper to identify the covered type of an RRSIG. + const isc::dns::Name& getCurrentName() const; + +private: + NodeRRsets node_rrsets_; + NodeRRsets node_rrsigsets_; + ZoneDataUpdater updater_; +}; + +void +ZoneDataLoader::addFromLoad(const ConstRRsetPtr& rrset) { + // If we see a new name, flush the temporary holders, adding the + // pairs of RRsets and RRSIGs of the previous name to the zone. + if ((!node_rrsets_.empty() || !node_rrsigsets_.empty()) && + (getCurrentName() != rrset->getName())) { + flushNodeRRsets(); + } + + // Store this RRset until it can be added to the zone. The current + // implementation requires RRs of the same RRset should be added at + // once, so we check the "duplicate" here. + const bool is_rrsig = rrset->getType() == RRType::RRSIG(); + NodeRRsets& node_rrsets = is_rrsig ? node_rrsigsets_ : node_rrsets_; + const RRType& rrtype = is_rrsig ? getCoveredType(rrset) : rrset->getType(); + if (!node_rrsets.insert(NodeRRsetsVal(rrtype, rrset)).second) { + isc_throw(ZoneDataUpdater::AddError, + "Duplicate add of the same type of" + << (is_rrsig ? " RRSIG" : "") << " RRset: " + << rrset->getName() << "/" << rrtype); + } + + if (rrset->getRRsig()) { + addFromLoad(rrset->getRRsig()); + } +} + +void +ZoneDataLoader::flushNodeRRsets() { + BOOST_FOREACH(NodeRRsetsVal val, node_rrsets_) { + // Identify the corresponding RRSIG for the RRset, if any. If + // found add both the RRset and its RRSIG at once. + ConstRRsetPtr sig_rrset; + NodeRRsets::iterator sig_it = node_rrsigsets_.find(val.first); + if (sig_it != node_rrsigsets_.end()) { + sig_rrset = sig_it->second; + node_rrsigsets_.erase(sig_it); + } + updater_.add(val.second, sig_rrset); + } + + // Normally rrsigsets map should be empty at this point, but it's still + // possible that an RRSIG that don't has covered RRset is added; they + // still remain in the map. We add them to the zone separately. + BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) { + updater_.add(ConstRRsetPtr(), val.second); + } + + node_rrsets_.clear(); + node_rrsigsets_.clear(); +} + +const Name& +ZoneDataLoader::getCurrentName() const { + if (!node_rrsets_.empty()) { + return (node_rrsets_.begin()->second->getName()); + } + assert(!node_rrsigsets_.empty()); + return (node_rrsigsets_.begin()->second->getName()); +} + +ZoneData* +loadZoneDataInternal(util::MemorySegment& mem_sgmt, + const isc::dns::RRClass& rrclass, + const Name& zone_name, + boost::function rrset_installer) +{ + SegmentObjectHolder holder( + mem_sgmt, ZoneData::create(mem_sgmt, zone_name), rrclass); + + ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get()); + rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader, _1)); + // Add any last RRsets that were left + loader.flushNodeRRsets(); + + const ZoneNode* origin_node = holder.get()->getOriginNode(); + const RdataSet* rdataset = origin_node->getData(); + // If the zone is NSEC3-signed, check if it has NSEC3PARAM + if (holder.get()->isNSEC3Signed()) { + if (RdataSet::find(rdataset, RRType::NSEC3PARAM()) == NULL) { + LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM). + arg(zone_name).arg(rrclass); + } + } + + // When an empty zone file is loaded, the origin doesn't even have + // an SOA RR. This condition should be avoided, and hence load() + // should throw when an empty zone is loaded. + if (RdataSet::find(rdataset, RRType::SOA()) == NULL) { + isc_throw(EmptyZone, + "Won't create an empty zone for: " << zone_name); + } + + return (holder.release()); +} + +// A wrapper for dns::masterLoad used by loadZoneData() below. Essentially it +// converts the two callback types. Note the mostly redundant wrapper of +// boost::bind. It converts function to +// function (masterLoad() expects the latter). SunStudio +// doesn't seem to do this conversion if we just pass 'callback'. +void +masterLoadWrapper(const char* const filename, const Name& origin, + const RRClass& zone_class, LoadCallback callback) +{ + try { + masterLoad(filename, origin, zone_class, boost::bind(callback, _1)); + } catch (MasterLoadError& e) { + isc_throw(ZoneLoaderException, e.what()); + } +} + +// The installer called from the iterator version of loadZoneData(). +void +generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) { + ConstRRsetPtr rrset; + while ((rrset = iterator->getNextRRset()) != NULL) { + callback(rrset); + } +} + +} // end of unnamed namespace + +ZoneData* +loadZoneData(util::MemorySegment& mem_sgmt, + const isc::dns::RRClass& rrclass, + const isc::dns::Name& zone_name, + const std::string& zone_file) +{ + return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name, + boost::bind(masterLoadWrapper, + zone_file.c_str(), + zone_name, rrclass, + _1))); +} + +ZoneData* +loadZoneData(util::MemorySegment& mem_sgmt, + const isc::dns::RRClass& rrclass, + const isc::dns::Name& zone_name, + ZoneIterator& iterator) +{ + return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name, + boost::bind(generateRRsetFromIterator, + &iterator, _1))); +} + +} // namespace memory +} // namespace datasrc +} // namespace isc diff --git a/src/lib/datasrc/memory/zone_data_loader.h b/src/lib/datasrc/memory/zone_data_loader.h new file mode 100644 index 0000000000..6b409ec186 --- /dev/null +++ b/src/lib/datasrc/memory/zone_data_loader.h @@ -0,0 +1,81 @@ +// 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_ZONE_DATA_LOADER_H +#define DATASRC_ZONE_DATA_LOADER_H 1 + +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace datasrc { +namespace memory { + +/// \brief Zone is empty exception. +/// +/// This is thrown if an empty zone would be created during +/// \c loadZoneData(). +struct EmptyZone : public ZoneLoaderException { + EmptyZone(const char* file, size_t line, const char* what) : + ZoneLoaderException(file, line, what) + {} +}; + +/// \brief Create and return a ZoneData instance populated from the +/// \c zone_file. +/// +/// Throws \c ZoneDataUpdater::AddError if invalid or inconsistent data +/// is present in the \c zone_file. Throws \c isc::Unexpected if empty +/// RRsets are passed by the master loader. Throws \c EmptyZone if an +/// empty zone would be created due to the \c loadZoneData(). +/// +/// \param mem_sgmt The memory segment. +/// \param rrclass The RRClass. +/// \param zone_name The name of the zone that is being loaded. +/// \param zone_file Filename which contains the zone data for \c zone_name. +ZoneData* loadZoneData(util::MemorySegment& mem_sgmt, + const isc::dns::RRClass& rrclass, + const isc::dns::Name& zone_name, + const std::string& zone_file); + +/// \brief Create and return a ZoneData instance populated from the +/// \c iterator. +/// +/// Throws \c ZoneDataUpdater::AddError if invalid or inconsistent data +/// is present in the \c zone_file. Throws \c isc::Unexpected if empty +/// RRsets are passed by the zone iterator. Throws \c EmptyZone if an +/// empty zone would be created due to the \c loadZoneData(). +/// +/// \param mem_sgmt The memory segment. +/// \param rrclass The RRClass. +/// \param zone_name The name of the zone that is being loaded. +/// \param iterator Iterator that returns RRsets to load into the zone. +ZoneData* loadZoneData(util::MemorySegment& mem_sgmt, + const isc::dns::RRClass& rrclass, + const isc::dns::Name& zone_name, + ZoneIterator& iterator); + +} // namespace memory +} // namespace datasrc +} // namespace isc + +#endif // DATASRC_ZONE_DATA_LOADER_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc new file mode 100644 index 0000000000..51ec03ce3a --- /dev/null +++ b/src/lib/datasrc/memory/zone_data_updater.cc @@ -0,0 +1,381 @@ +// 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 + +using namespace isc::dns; +using namespace isc::dns::rdata; + +namespace isc { +namespace datasrc { +namespace memory { + +using detail::getCoveredType; + +void +ZoneDataUpdater::addWildcards(const Name& name) { + Name wname(name); + const unsigned int labels(wname.getLabelCount()); + const unsigned int origin_labels(zone_name_.getLabelCount()); + for (unsigned int l = labels; + l > origin_labels; + --l, wname = wname.split(1)) + { + if (wname.isWildcard()) { + LOG_DEBUG(logger, DBG_TRACE_DATA, + DATASRC_MEMORY_MEM_ADD_WILDCARD).arg(name); + + // Ensure a separate level exists for the "wildcarding" + // name, and mark the node as "wild". + ZoneNode* node; + zone_data_.insertName(mem_sgmt_, wname.split(1), &node); + node->setFlag(ZoneData::WILDCARD_NODE); + + // Ensure a separate level exists for the wildcard name. + // Note: for 'name' itself we do this later anyway, but the + // overhead should be marginal because wildcard names should + // be rare. + zone_data_.insertName(mem_sgmt_, wname, &node); + } + } +} + +void +ZoneDataUpdater::contextCheck(const AbstractRRset& rrset, + const RdataSet* rdataset) const +{ + // Ensure CNAME and other type of RR don't coexist for the same + // owner name except with NSEC, which is the only RR that can + // coexist with CNAME (and also RRSIG, which is handled separately) + if (rrset.getType() == RRType::CNAME()) { + for (const RdataSet* sp = rdataset; sp != NULL; sp = sp->getNext()) { + if (sp->type != RRType::NSEC()) { + LOG_ERROR(logger, DATASRC_MEMORY_MEM_CNAME_TO_NONEMPTY). + arg(rrset.getName()); + isc_throw(AddError, + "CNAME can't be added with " << sp->type + << " RRType for " << rrset.getName()); + } + } + } else if ((rrset.getType() != RRType::NSEC()) && + (RdataSet::find(rdataset, RRType::CNAME()) != NULL)) + { + LOG_ERROR(logger, + DATASRC_MEMORY_MEM_CNAME_COEXIST).arg(rrset.getName()); + isc_throw(AddError, + "CNAME and " << rrset.getType() << + " can't coexist for " << rrset.getName()); + } + + // Similar with DNAME, but it must not coexist only with NS and only + // in non-apex domains. RFC 2672 section 3 mentions that it is + // implied from it and RFC 2181. + if (rrset.getName() != zone_name_ && + // Adding DNAME, NS already there + ((rrset.getType() == RRType::DNAME() && + RdataSet::find(rdataset, RRType::NS()) != NULL) || + // Adding NS, DNAME already there + (rrset.getType() == RRType::NS() && + RdataSet::find(rdataset, RRType::DNAME()) != NULL))) + { + LOG_ERROR(logger, DATASRC_MEMORY_MEM_DNAME_NS).arg(rrset.getName()); + isc_throw(AddError, "DNAME can't coexist with NS in non-apex domain: " + << rrset.getName()); + } +} + +void +ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const { + assert(rrset); + + if (rrset->getRdataCount() == 0) { + isc_throw(AddError, + "The rrset provided is empty: " + << rrset->getName() << "/" << rrset->getType()); + } + + // Check for singleton RRs. It should probably handled at a different + // layer in future. + if ((rrset->getType() == RRType::CNAME() || + rrset->getType() == RRType::DNAME()) && + rrset->getRdataCount() > 1) + { + // XXX: this is not only for CNAME or DNAME. We should + // generalize this code for all other "singleton RR types" (such + // as SOA) in a separate task. + LOG_ERROR(logger, + DATASRC_MEMORY_MEM_SINGLETON).arg(rrset->getName()). + arg(rrset->getType()); + isc_throw(AddError, "multiple RRs of singleton type for " + << rrset->getName()); + } + + // NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this + // implementation requests it be so at the moment. + if ((rrset->getType() == RRType::NSEC3() || + rrset->getType() == RRType::NSEC3PARAM()) && + (rrset->getRdataCount() > 1)) + { + isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for " + << rrset->getName() << " which isn't supported"); + } + + // For RRSIGs, check consistency of the type covered. We know the + // RRset isn't empty, so the following check is safe. + if (rrset->getType() == RRType::RRSIG()) { + RdataIteratorPtr rit = rrset->getRdataIterator(); + const RRType covered = dynamic_cast( + rit->getCurrent()).typeCovered(); + for (rit->next(); !rit->isLast(); rit->next()) { + if (dynamic_cast( + rit->getCurrent()).typeCovered() != covered) + { + isc_throw(AddError, "RRSIG contains mixed covered types: " + << rrset->toText()); + } + } + } + + const NameComparisonResult compare = zone_name_.compare(rrset->getName()); + if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN && + compare.getRelation() != NameComparisonResult::EQUAL) + { + LOG_ERROR(logger, + DATASRC_MEMORY_MEM_OUT_OF_ZONE).arg(rrset->getName()). + arg(zone_name_); + isc_throw(AddError, + "The name " << rrset->getName() << + " is not contained in zone " << zone_name_); + } + + // Some RR types do not really work well with a wildcard. Even + // though the protocol specifically doesn't completely ban such + // usage, we refuse to load a zone containing such RR in order to + // keep the lookup logic simpler and more predictable. See RFC4592 + // and (for DNAME) RFC6672 for more technical background. Note also + // that BIND 9 refuses NS at a wildcard, so in that sense we simply + // provide compatible behavior. + if (rrset->getName().isWildcard()) { + if (rrset->getType() == RRType::NS()) { + LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_NS). + arg(rrset->getName()); + isc_throw(AddError, "Invalid NS owner name (wildcard): " + << rrset->getName()); + } + + if (rrset->getType() == RRType::DNAME()) { + LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_DNAME). + arg(rrset->getName()); + isc_throw(AddError, "Invalid DNAME owner name (wildcard): " + << rrset->getName()); + } + } + + // Owner names of NSEC3 have special format as defined in RFC5155, + // and cannot be a wildcard name or must be one label longer than + // the zone origin. While the RFC doesn't prohibit other forms of + // names, no sane zone would have such names for NSEC3. BIND 9 also + // refuses NSEC3 at wildcard. + if (rrset->getType() == RRType::NSEC3() && + (rrset->getName().isWildcard() || + rrset->getName().getLabelCount() != zone_name_.getLabelCount() + 1)) + { + LOG_ERROR(logger, DATASRC_MEMORY_BAD_NSEC3_NAME).arg(rrset->getName()); + isc_throw(AddError, "Invalid NSEC3 owner name: " << + rrset->getName() << "; zone: " << zone_name_); + } +} + +const NSEC3Hash* +ZoneDataUpdater::getNSEC3Hash() { + if (hash_ == NULL) { + NSEC3Data* nsec3_data = zone_data_.getNSEC3Data(); + // This should never be NULL in this codepath. + assert(nsec3_data != NULL); + + hash_ = NSEC3Hash::create(nsec3_data->hashalg, + nsec3_data->iterations, + nsec3_data->getSaltData(), + nsec3_data->getSaltLen()); + } + + return (hash_); +} + +template +void +ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) { + // We know rrset has exactly one RDATA + const T& nsec3_rdata = + dynamic_cast( + rrset->getRdataIterator()->getCurrent()); + + NSEC3Data* nsec3_data = zone_data_.getNSEC3Data(); + if (nsec3_data == NULL) { + nsec3_data = NSEC3Data::create(mem_sgmt_, zone_name_, nsec3_rdata); + zone_data_.setNSEC3Data(nsec3_data); + zone_data_.setSigned(true); + } else { + const NSEC3Hash* hash = getNSEC3Hash(); + if (!hash->match(nsec3_rdata)) { + isc_throw(AddError, + rrset->getType() << " with inconsistent parameters: " + << rrset->toText()); + } + } +} + +void +ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset, + const ConstRRsetPtr rrsig) +{ + if (rrset) { + setupNSEC3(rrset); + } + + NSEC3Data* nsec3_data = zone_data_.getNSEC3Data(); + if (nsec3_data == NULL) { + // This is some tricky case: an RRSIG for NSEC3 is given without the + // covered NSEC3, and we don't even know any NSEC3 related data. + // This situation is not necessarily broken, but in our current + // implementation it's very difficult to deal with. So we reject it; + // hopefully this case shouldn't happen in practice, at least unless + // zone is really broken. + assert(!rrset); + isc_throw(NotImplemented, + "RRSIG for NSEC3 cannot be added - no known NSEC3 data"); + } + + ZoneNode* node; + nsec3_data->insertName(mem_sgmt_, name, &node); + + RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig); + RdataSet* old_rdataset = node->setData(rdataset); + if (old_rdataset != NULL) { + RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_); + } +} + +void +ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype, + const ConstRRsetPtr rrset, + const ConstRRsetPtr rrsig) +{ + if (rrtype == RRType::NSEC3()) { + addNSEC3(name, rrset, rrsig); + } else { + ZoneNode* node; + zone_data_.insertName(mem_sgmt_, name, &node); + + RdataSet* rdataset_head = node->getData(); + + // Checks related to the surrounding data. Note: when the check + // fails and the exception is thrown, it may break strong + // exception guarantee. At the moment we prefer code simplicity + // and don't bother to introduce complicated recovery code. + if (rrset) { // this check is only for covered RRset, not RRSIG + contextCheck(*rrset, rdataset_head); + } + + if (RdataSet::find(rdataset_head, rrtype, true) != NULL) { + isc_throw(AddError, + "RRset of the type already exists: " + << name << " (type: " << rrtype << ")"); + } + + RdataSet* rdataset_new = RdataSet::create(mem_sgmt_, encoder_, + rrset, rrsig); + rdataset_new->next = rdataset_head; + node->setData(rdataset_new); + + // Ok, we just put it in. + + // Convenient (and more efficient) shortcut to check RRsets at origin + const bool is_origin = (node == zone_data_.getOriginNode()); + + // If this RRset creates a zone cut at this node, mark the node + // indicating the need for callback in find(). Note that we do this + // only when non RRSIG RRset of that type is added. + if (rrset && rrtype == RRType::NS() && !is_origin) { + node->setFlag(ZoneNode::FLAG_CALLBACK); + // If it is DNAME, we have a callback as well here + } else if (rrset && rrtype == RRType::DNAME()) { + node->setFlag(ZoneNode::FLAG_CALLBACK); + } + + // If we've added NSEC3PARAM at zone origin, set up NSEC3 + // specific data or check consistency with already set up + // parameters. + if (rrset && rrtype == RRType::NSEC3PARAM() && is_origin) { + setupNSEC3(rrset); + } else if (rrset && rrtype == RRType::NSEC() && is_origin) { + // If it is NSEC signed zone, we mark the zone as signed + // (conceptually "signed" is a broader notion but our + // current zone finder implementation regards "signed" as + // "NSEC signed") + zone_data_.setSigned(true); + } + } +} + +void +ZoneDataUpdater::add(const ConstRRsetPtr& rrset, + const ConstRRsetPtr& sig_rrset) +{ + // Validate input. + if (!rrset && !sig_rrset) { + isc_throw(NullRRset, + "ZoneDataUpdater::add is given 2 NULL pointers"); + } + if (rrset) { + validate(rrset); + } + if (sig_rrset) { + validate(sig_rrset); + } + + const Name& name = rrset ? rrset->getName() : sig_rrset->getName(); + const RRType& rrtype = rrset ? rrset->getType() : + getCoveredType(sig_rrset); + + // OK, can add the RRset. + LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).arg(name). + arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")"). + arg(zone_name_); + + // Add wildcards possibly contained in the owner name to the domain + // tree. This can only happen for the normal (non-NSEC3) tree. + // Note: this can throw an exception, breaking strong exception + // guarantee. (see also the note for the call to contextCheck() + // above). + if (rrtype != RRType::NSEC3()) { + addWildcards(name); + } + + addRdataSet(name, rrtype, rrset, sig_rrset); +} + +} // namespace memory +} // namespace datasrc +} // namespace isc diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h new file mode 100644 index 0000000000..9d669a0e29 --- /dev/null +++ b/src/lib/datasrc/memory/zone_data_updater.h @@ -0,0 +1,201 @@ +// 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_ZONE_DATA_UPDATER_H +#define DATASRC_ZONE_DATA_UPDATER_H 1 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace isc { +namespace datasrc { +namespace memory { + +/// \brief A helper class to add records to a zone. +/// +/// This class provides an \c add() method that can be used to add +/// RRsets to a ZoneData instance. The RRsets are first validated for +/// correctness and consistency, and their data is made into RdataSets +/// which are added to the ZoneData for the zone. +/// +/// The way to use this is to make a ZoneDataUpdater instance, and call +/// add() on it as follows: +/// +/// \code +/// ZoneDataUpdater updater(mem_sgmt, rrclass, zone_origin_name, zone_data); +/// ConstRRsetPtr rrset; +/// updater.add(rrset, ConstRRsetPtr()); +/// \endcode +/// +/// We enforce that instances are non-copyable as it's pointless to make +/// copies. +class ZoneDataUpdater : boost::noncopyable { +public: + /// + /// \name Constructors and Destructor. + /// + //@{ + + /// The constructor. + /// + /// \throw none + /// + /// \param mem_sgmt The memory segment used for the zone data. + /// \param rrclass The RRclass of the zone data. + /// \param zone_name The Name of the zone under which records will be + /// added. + // \param zone_data The ZoneData object which is populated with + // record data. + ZoneDataUpdater(util::MemorySegment& mem_sgmt, + isc::dns::RRClass rrclass, + const isc::dns::Name& zone_name, + ZoneData& zone_data) : + mem_sgmt_(mem_sgmt), + rrclass_(rrclass), + zone_name_(zone_name), + zone_data_(zone_data), + hash_(NULL) + {} + + /// The destructor. + ~ZoneDataUpdater() { + delete hash_; + } + + //@} + + /// This is thrown if the provided RRset parameter passed to \c + /// add() is NULL. + struct NullRRset : public InvalidParameter { + NullRRset(const char* file, size_t line, const char* what) : + InvalidParameter(file, line, what) + {} + }; + + /// \brief General failure exception for \c add(). + /// + /// This is thrown against general error cases in adding an RRset + /// to the zone. + struct AddError : public ZoneLoaderException { + AddError(const char* file, size_t line, const char* what) : + ZoneLoaderException(file, line, what) + {} + }; + + /// \brief Add an RRset to the zone. + /// + /// This is the core method of this class. It is used to add an + /// RRset to the ZoneData associated with this object. The RRset is + /// first validated for correctness and consistency with the rest of + /// the records in the zone, and then an RdataSet is created and + /// populated with the record data and added to the ZoneData for the + /// name in the RRset. + /// + /// At least one of \c rrset or \c sig_rrset must be non NULL. + /// \c sig_rrset can be reasonably NULL when \c rrset is not signed in + /// the zone; it's unusual that \c rrset is NULL, but is still possible + /// if these RRsets are given separately to the loader, or if even the + /// zone is half broken and really contains an RRSIG that doesn't have + /// any covered RRset. This implementation supports these cases (but + /// see the note below). + /// + /// There is one tricky case: Due to a limitation of the current + /// implementation, it cannot accept an RRSIG for NSEC3 without the covered + /// NSEC3, unless at least one NSEC3 or NSEC3PARAM has been added. + /// In this case an isc::NotImplemented exception will be thrown. It + /// should be very rare in practice, and hopefully wouldn't be a real + /// issue. + /// + /// \note Due to limitations of the current implementation, if a + /// (non RRSIG) RRset and its RRSIG are added separately in different + /// calls to this method, the second attempt will be rejected due to + /// an \c AddError exception. This will be loosened in Trac + /// ticket #2441. + /// + /// \throw NullRRset Both \c rrset and sig_rrset is NULL + /// \throw AddError any of a variety of validation checks fail for the + /// \c rrset and its associated \c sig_rrset. + /// \throw NotImplemented RRSIG for NSEC3 cannot be added due to internal + /// restriction. + /// + /// \param rrset The RRset to be added. + /// \param sig_rrset An associated RRSIG RRset for the \c rrset. It + /// can be empty if there is no RRSIG for the \c rrset. + void add(const isc::dns::ConstRRsetPtr& rrset, + const isc::dns::ConstRRsetPtr& sig_rrset); + +private: + // Add the necessary magic for any wildcard contained in 'name' + // (including itself) to be found in the zone. + // + // In order for wildcard matching to work correctly in the zone finder, + // we must ensure that a node for the wildcarding level exists in the + // backend ZoneTree. + // E.g. if the wildcard name is "*.sub.example." then we must ensure + // that "sub.example." exists and is marked as a wildcard level. + // Note: the "wildcarding level" is for the parent name of the wildcard + // name (such as "sub.example."). + // + // We also perform the same trick for empty wild card names possibly + // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example'). + void addWildcards(const isc::dns::Name& name); + + // Does some checks in context of the data that are already in the + // zone. Currently checks for forbidden combinations of RRsets in + // the same domain (CNAME+anything, DNAME+NS). If such condition is + // found, it throws AddError. + void contextCheck(const isc::dns::AbstractRRset& rrset, + const RdataSet* set) const; + + // Validate rrset before adding it to the zone. If something is wrong + // it throws an exception. It doesn't modify the zone, and provides + // the strong exception guarantee. + void validate(const isc::dns::ConstRRsetPtr rrset) const; + + const isc::dns::NSEC3Hash* getNSEC3Hash(); + template + void setupNSEC3(const isc::dns::ConstRRsetPtr rrset); + void addNSEC3(const isc::dns::Name& name, + const isc::dns::ConstRRsetPtr rrset, + const isc::dns::ConstRRsetPtr rrsig); + void addRdataSet(const isc::dns::Name& name, + const isc::dns::RRType& rrtype, + const isc::dns::ConstRRsetPtr rrset, + const isc::dns::ConstRRsetPtr rrsig); + + util::MemorySegment& mem_sgmt_; + const isc::dns::RRClass rrclass_; + const isc::dns::Name& zone_name_; + ZoneData& zone_data_; + RdataEncoder encoder_; + const isc::dns::NSEC3Hash* hash_; +}; + +} // namespace memory +} // namespace datasrc +} // namespace isc + +#endif // DATASRC_ZONE_DATA_UPDATER_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc index 11188a038b..7f57d8ede2 100644 --- a/src/lib/datasrc/memory/zone_finder.cc +++ b/src/lib/datasrc/memory/zone_finder.cc @@ -216,6 +216,14 @@ createNSEC3RRset(const ZoneNode* node, const RRClass& rrclass) { assert(rdataset != NULL); assert(rdataset->type == RRType::NSEC3()); + // Check for the rare case of RRSIG-only record; in theory it could exist + // but we simply consider it broken for NSEC3. + if (rdataset->getRdataCount() == 0) { + uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH]; + isc_throw(DataSourceError, "Broken zone: RRSIG-only NSEC3 record at " + << node->getAbsoluteLabels(labels_buf) << "/" << rrclass); + } + // Create the RRset. Note the DNSSEC flag: NSEC3 implies DNSSEC. return (createTreeNodeRRset(node, rdataset, rrclass, ZoneFinder::FIND_DNSSEC)); @@ -297,8 +305,16 @@ getClosestNSEC(const ZoneData& zone_data, } const ZoneNode* prev_node; - while ((prev_node = zone_data.getZoneTree().previousNode(node_path)) - != NULL) { + if (node_path.getLastComparisonResult().getRelation() == + NameComparisonResult::SUBDOMAIN) { + // In case the search ended as a sub-domain, the previous node + // is already at the top of node_path. + prev_node = node_path.getLastComparedNode(); + } else { + prev_node = zone_data.getZoneTree().previousNode(node_path); + } + + while (prev_node != NULL) { if (!prev_node->isEmpty()) { const RdataSet* found = RdataSet::find(prev_node->getData(), RRType::NSEC()); @@ -306,6 +322,7 @@ getClosestNSEC(const ZoneData& zone_data, return (ConstNodeRRset(prev_node, found)); } } + prev_node = zone_data.getZoneTree().previousNode(node_path); } // This must be impossible and should be an internal bug. // See the description at the method declaration. @@ -627,7 +644,10 @@ private: // This can be a bit more optimized, but unless we have many // requested types the effect is probably marginal. For now we // keep it simple. - if (std::find(type_beg, type_end, rdset->type) != type_end) { + // Check for getRdataCount is necessary not to include RRSIG-only + // records accidentally (should be rare, but possible). + if (std::find(type_beg, type_end, rdset->type) != type_end && + rdset->getRdataCount() > 0) { result->push_back(createTreeNodeRRset(node, rdset, rrclass_, options, real_name)); } diff --git a/src/lib/datasrc/memory/zone_table.cc b/src/lib/datasrc/memory/zone_table.cc index 836b020088..c0237f5401 100644 --- a/src/lib/datasrc/memory/zone_table.cc +++ b/src/lib/datasrc/memory/zone_table.cc @@ -47,23 +47,22 @@ typedef boost::function ZoneDataDeleterType; } ZoneTable* -ZoneTable::create(util::MemorySegment& mem_sgmt, RRClass zone_class) { +ZoneTable::create(util::MemorySegment& mem_sgmt, const RRClass& zone_class) { SegmentObjectHolder holder( mem_sgmt, ZoneTableTree::create(mem_sgmt), boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class)); void* p = mem_sgmt.allocate(sizeof(ZoneTable)); - ZoneTable* zone_table = new(p) ZoneTable(holder.get()); + ZoneTable* zone_table = new(p) ZoneTable(zone_class, holder.get()); holder.release(); return (zone_table); } void -ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable, - RRClass zone_class) +ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable) { ZoneTableTree::destroy(mem_sgmt, ztable->zones_.get(), boost::bind(deleteZoneData, &mem_sgmt, _1, - zone_class)); + ztable->rrclass_)); mem_sgmt.deallocate(ztable, sizeof(ZoneTable)); } diff --git a/src/lib/datasrc/memory/zone_table.h b/src/lib/datasrc/memory/zone_table.h index 024558eb30..1b369b93dd 100644 --- a/src/lib/datasrc/memory/zone_table.h +++ b/src/lib/datasrc/memory/zone_table.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATASRC_MEMORY_ZONE_TABLE_H -#define __DATASRC_MEMORY_ZONE_TABLE_H 1 +#ifndef DATASRC_MEMORY_ZONE_TABLE_H +#define DATASRC_MEMORY_ZONE_TABLE_H 1 #include @@ -102,7 +102,9 @@ private: /// This constructor internally involves resource allocation, and if /// it fails, a corresponding standard exception will be thrown. /// It never throws an exception otherwise. - ZoneTable(ZoneTableTree* zones) : zones_(zones) + ZoneTable(const dns::RRClass& rrclass, ZoneTableTree* zones) : + rrclass_(rrclass), + zones_(zones) {} public: @@ -119,7 +121,7 @@ public: /// \param zone_class The RR class of the zone. It must be the RR class /// that is supposed to be associated to the zone table. static ZoneTable* create(util::MemorySegment& mem_sgmt, - dns::RRClass zone_class); + const dns::RRClass& zone_class); /// \brief Destruct and deallocate \c ZoneTable /// @@ -135,8 +137,7 @@ public: /// \param ztable A non NULL pointer to a valid \c ZoneTable object /// that was originally created by the \c create() method (the behavior /// is undefined if this condition isn't met). - static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable, - dns::RRClass zone_class); + static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable); /// Add a new zone to the \c ZoneTable. /// @@ -185,12 +186,13 @@ public: FindResult findZone(const isc::dns::Name& name) const; private: + const dns::RRClass rrclass_; boost::interprocess::offset_ptr zones_; }; } } } -#endif // __DATASRC_MEMORY_ZONE_TABLE_H +#endif // DATASRC_MEMORY_ZONE_TABLE_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/memory/zone_table_segment.cc b/src/lib/datasrc/memory/zone_table_segment.cc index 7a80e3c4c1..50587c41a5 100644 --- a/src/lib/datasrc/memory/zone_table_segment.cc +++ b/src/lib/datasrc/memory/zone_table_segment.cc @@ -15,17 +15,20 @@ #include #include +using namespace isc::dns; + namespace isc { namespace datasrc { namespace memory { ZoneTableSegment* -ZoneTableSegment::create(const isc::data::Element&) { +ZoneTableSegment::create(const isc::data::Element&, const RRClass& rrclass) { /// FIXME: For now, we always return ZoneTableSegmentLocal. This /// should be updated eventually to parse the passed Element /// argument and construct a corresponding ZoneTableSegment /// implementation. - return (new ZoneTableSegmentLocal); + + return (new ZoneTableSegmentLocal(rrclass)); } void diff --git a/src/lib/datasrc/memory/zone_table_segment.h b/src/lib/datasrc/memory/zone_table_segment.h index 7fd1310229..88e69f6ba4 100644 --- a/src/lib/datasrc/memory/zone_table_segment.h +++ b/src/lib/datasrc/memory/zone_table_segment.h @@ -12,10 +12,12 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ZONE_TABLE_SEGMENT_H__ -#define __ZONE_TABLE_SEGMENT_H__ +#ifndef ZONE_TABLE_SEGMENT_H +#define ZONE_TABLE_SEGMENT_H +#include #include +#include "load_action.h" #include #include @@ -24,8 +26,14 @@ #include namespace isc { +// Some forward declarations +namespace dns { +class Name; +class RRClass; +} namespace datasrc { namespace memory { +class ZoneWriter; /// \brief Memory-management independent entry point that contains a /// pointer to a zone table in memory. @@ -35,18 +43,21 @@ namespace memory { /// map from domain names to zone locators) in memory. struct ZoneTableHeader { public: + ZoneTableHeader(ZoneTable* zone_table) : + table_(zone_table) + {} + /// \brief Returns a pointer to the underlying zone table. ZoneTable* getTable() { - return (table.get()); + return (table_.get()); } /// \brief const version of \c getTable(). const ZoneTable* getTable() const { - return (table.get()); + return (table_.get()); } - private: - boost::interprocess::offset_ptr table; + boost::interprocess::offset_ptr table_; }; /// \brief Manages a ZoneTableHeader, an entry point into a table of @@ -64,7 +75,7 @@ protected: /// An instance implementing this interface is expected to be /// created by the factory method (\c create()), so this constructor /// is protected. - ZoneTableSegment() + ZoneTableSegment(isc::dns::RRClass) {} public: /// \brief Destructor @@ -92,7 +103,20 @@ public: /// \param config The configuration based on which a derived object /// is returned. /// \return Returns a ZoneTableSegment object - static ZoneTableSegment* create(const isc::data::Element& config); + static ZoneTableSegment* create(const isc::data::Element& config, + const isc::dns::RRClass& rrclass); + + /// \brief Temporary/Testing version of create. + /// + /// This exists as a temporary solution during the migration phase + /// towards using the ZoneTableSegment. It doesn't take a config, + /// but a memory segment instead. If you can, you should use the + /// other version, this one will be gone soon. + /// + /// \param segment The memory segment to use. + /// \return Returns a new ZoneTableSegment object. + /// \todo Remove this method. + static ZoneTableSegment* create(isc::util::MemorySegment& segment); /// \brief Destroy a ZoneTableSegment /// @@ -101,10 +125,25 @@ public: /// /// \param segment The segment to destroy. static void destroy(ZoneTableSegment* segment); + + /// \brief Create a zone write corresponding to this segment + /// + /// This creates a new write that can be used to update zones + /// inside this zone table segment. + /// + /// \param loadAction Callback to provide the actual data. + /// \param origin The origin of the zone to reload. + /// \param rrclass The class of the zone to reload. + /// \return New instance of a zone writer. The ownership is passed + /// onto the caller and the caller needs to \c delete it when + /// it's done with the writer. + virtual ZoneWriter* getZoneWriter(const LoadAction& load_action, + const dns::Name& origin, + const dns::RRClass& rrclass) = 0; }; } // namespace memory } // namespace datasrc } // namespace isc -#endif // __ZONE_TABLE_SEGMENT_H__ +#endif // ZONE_TABLE_SEGMENT_H diff --git a/src/lib/datasrc/memory/zone_table_segment_local.cc b/src/lib/datasrc/memory/zone_table_segment_local.cc index 589c9afb30..fdaf678e6f 100644 --- a/src/lib/datasrc/memory/zone_table_segment_local.cc +++ b/src/lib/datasrc/memory/zone_table_segment_local.cc @@ -13,13 +13,31 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include "zone_writer_local.h" +using namespace isc::dns; using namespace isc::util; namespace isc { namespace datasrc { namespace memory { +ZoneTableSegmentLocal::ZoneTableSegmentLocal(const RRClass& rrclass) : + ZoneTableSegment(rrclass), + header_(ZoneTable::create(mem_sgmt_, rrclass)) +{ +} + +ZoneTableSegmentLocal::~ZoneTableSegmentLocal() { + // Explicitly clear the contained data, and check memory + // leak. assert() (with abort on failure) may be too harsh, but + // it's probably better to find more leaks initially. Once it's stabilized + // we should probably revisit it. + + ZoneTable::destroy(mem_sgmt_, header_.getTable()); + assert(mem_sgmt_.allMemoryDeallocated()); +} + // After more methods' definitions are added here, it would be a good // idea to move getHeader() and getMemorySegment() definitions to the // header file. @@ -38,6 +56,14 @@ ZoneTableSegmentLocal::getMemorySegment() { return (mem_sgmt_); } +ZoneWriter* +ZoneTableSegmentLocal::getZoneWriter(const LoadAction& load_action, + const dns::Name& name, + const dns::RRClass& rrclass) +{ + return (new ZoneWriterLocal(this, load_action, name, rrclass)); +} + } // namespace memory } // namespace datasrc } // namespace isc diff --git a/src/lib/datasrc/memory/zone_table_segment_local.h b/src/lib/datasrc/memory/zone_table_segment_local.h index de776a9036..e08ca3978b 100644 --- a/src/lib/datasrc/memory/zone_table_segment_local.h +++ b/src/lib/datasrc/memory/zone_table_segment_local.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ZONE_TABLE_SEGMENT_LOCAL_H__ -#define __ZONE_TABLE_SEGMENT_LOCAL_H__ +#ifndef ZONE_TABLE_SEGMENT_LOCAL_H +#define ZONE_TABLE_SEGMENT_LOCAL_H #include #include @@ -37,11 +37,10 @@ protected: /// Instances are expected to be created by the factory method /// (\c ZoneTableSegment::create()), so this constructor is /// protected. - ZoneTableSegmentLocal() - {} + ZoneTableSegmentLocal(const isc::dns::RRClass& rrclass); public: /// \brief Destructor - virtual ~ZoneTableSegmentLocal() {} + virtual ~ZoneTableSegmentLocal(); /// \brief Return the ZoneTableHeader for the local zone table /// segment implementation. @@ -54,13 +53,17 @@ public: /// implementation (a MemorySegmentLocal instance). virtual isc::util::MemorySegment& getMemorySegment(); + /// \brief Concrete implementation of ZoneTableSegment::getZoneWriter + virtual ZoneWriter* getZoneWriter(const LoadAction& load_action, + const dns::Name& origin, + const dns::RRClass& rrclass); private: - ZoneTableHeader header_; isc::util::MemorySegmentLocal mem_sgmt_; + ZoneTableHeader header_; }; } // namespace memory } // namespace datasrc } // namespace isc -#endif // __ZONE_TABLE_SEGMENT_LOCAL_H__ +#endif // ZONE_TABLE_SEGMENT_LOCAL_H diff --git a/src/lib/datasrc/memory/zone_writer.h b/src/lib/datasrc/memory/zone_writer.h new file mode 100644 index 0000000000..0e8f285fff --- /dev/null +++ b/src/lib/datasrc/memory/zone_writer.h @@ -0,0 +1,92 @@ +// 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 MEM_ZONE_WRITER_H +#define MEM_ZONE_WRITER_H + +#include "load_action.h" + +namespace isc { +namespace datasrc { +namespace memory { + +/// \brief Does an update to a zone. +/// +/// This abstract base class represents the work of a reload of a zone. +/// The work is divided into three stages -- load(), install() and cleanup(). +/// They should be called in this order for the effect to take place. +/// +/// We divide them so the update of zone data can be done asynchronously, +/// in a different thread. The install() operation is the only one that needs +/// to be done in a critical section. +/// +/// Each derived class implementation must provide the strong exception +/// guarantee for each public method. That is, when any of the methods +/// throws, the entire state should stay the same as before the call +/// (how to achieve that may be implementation dependant). +class ZoneWriter { +public: + /// \brief Virtual destructor. + virtual ~ZoneWriter() {}; + + /// \brief Get the zone data into memory. + /// + /// This is the part that does the time-consuming loading into the memory. + /// This can be run in a separate thread, for example. It has no effect on + /// the data actually served, it only prepares them for future use. + /// + /// This is the first method you should call on the object. Never call it + /// multiple times. + /// + /// \note As this contains reading of files or other data sources, or with + /// some other source of the data to load, it may throw quite anything. + /// If it throws, do not call any other methods on the object and + /// discard it. + /// \note After successful load(), you have to call cleanup() some time + /// later. + /// \throw isc::InvalidOperation if called second time. + virtual void load() = 0; + + /// \brief Put the changes to effect. + /// + /// This replaces the old version of zone with the one previously prepared + /// by load(). It takes ownership of the old zone data, if any. + /// + /// You may call it only after successful load() and at most once. + /// + /// The operation is expected to be fast and is meant to be used inside + /// a critical section. + /// + /// This may throw in rare cases, depending on the concrete implementation. + /// If it throws, you still need to call cleanup(). + /// + /// \throw isc::InvalidOperation if called without previous load() or for + /// the second time or cleanup() was called already. + virtual void install() = 0; + + /// \brief Clean up resources. + /// + /// This releases all resources held by owned zone data. That means the + /// one loaded by load() in case install() was not called or was not + /// successful, or the one replaced in install(). + /// + /// Generally, this should never throw. + virtual void cleanup() = 0; +}; + +} +} +} + +#endif diff --git a/src/lib/datasrc/memory/zone_writer_local.cc b/src/lib/datasrc/memory/zone_writer_local.cc new file mode 100644 index 0000000000..0cd95876c5 --- /dev/null +++ b/src/lib/datasrc/memory/zone_writer_local.cc @@ -0,0 +1,93 @@ +// 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 "zone_writer_local.h" +#include "zone_data.h" +#include "zone_table_segment_local.h" + +#include + +using std::auto_ptr; + +namespace isc { +namespace datasrc { +namespace memory { + +ZoneWriterLocal::ZoneWriterLocal(ZoneTableSegmentLocal* segment, + const LoadAction& load_action, + const dns::Name& origin, + const dns::RRClass& rrclass) : + segment_(segment), + load_action_(load_action), + origin_(origin), + rrclass_(rrclass), + zone_data_(NULL), + state_(ZW_UNUSED) +{} + +ZoneWriterLocal::~ZoneWriterLocal() { + // Clean up everything there might be left if someone forgot, just + // in case. + cleanup(); +} + +void +ZoneWriterLocal::load() { + if (state_ != ZW_UNUSED) { + isc_throw(isc::InvalidOperation, "Trying to load twice"); + } + + zone_data_ = load_action_(segment_->getMemorySegment()); + + if (zone_data_ == NULL) { + // Bug inside load_action_. + isc_throw(isc::InvalidOperation, "No data returned from load action"); + } + + state_ = ZW_LOADED; +} + +void +ZoneWriterLocal::install() { + if (state_ != ZW_LOADED) { + isc_throw(isc::InvalidOperation, "No data to install"); + } + + + ZoneTable* table(segment_->getHeader().getTable()); + if (table == NULL) { + isc_throw(isc::Unexpected, "No zone table present"); + } + const ZoneTable::AddResult result(table->addZone( + segment_->getMemorySegment(), + rrclass_, origin_, zone_data_)); + + state_ = ZW_INSTALLED; + zone_data_ = result.zone_data; +} + +void +ZoneWriterLocal::cleanup() { + // We eat the data (if any) now. + + if (zone_data_ != NULL) { + ZoneData::destroy(segment_->getMemorySegment(), zone_data_, rrclass_); + zone_data_ = NULL; + state_ = ZW_CLEANED; + } +} + +} +} +} diff --git a/src/lib/datasrc/memory/zone_writer_local.h b/src/lib/datasrc/memory/zone_writer_local.h new file mode 100644 index 0000000000..7231a5738a --- /dev/null +++ b/src/lib/datasrc/memory/zone_writer_local.h @@ -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. + +#ifndef MEM_ZONE_WRITER_LOCAL_H +#define MEM_ZONE_WRITER_LOCAL_H + +#include "zone_writer.h" + +#include +#include + +namespace isc { +namespace datasrc { +namespace memory { + +class ZoneData; +class ZoneTableSegmentLocal; + +/// \brief Writer implementation which loads data locally. +/// +/// This implementation prepares a clean zone data and lets one callback +/// to fill it and another to install it somewhere. The class does mostly +/// nothing (and delegates the work to the callbacks), just stores little bit +/// of state between the calls. +class ZoneWriterLocal : public ZoneWriter { +public: + /// \brief Constructor + /// + /// \param segment The zone table segment to store the zone into. + /// \param load_action The callback used to load data. + /// \param install_action The callback used to install the loaded zone. + /// \param rrclass The class of the zone. + ZoneWriterLocal(ZoneTableSegmentLocal* segment, + const LoadAction& load_action, const dns::Name& name, + const dns::RRClass& rrclass); + + /// \brief Destructor + ~ZoneWriterLocal(); + + /// \brief Loads the data. + /// + /// This calls the load_action (passed to constructor) and stores the + /// data for future use. + /// + /// \throw isc::InvalidOperation if it is called the second time in + /// lifetime of the object. + /// \throw Whatever the load_action throws, it is propagated up. + virtual void load(); + + /// \brief Installs the zone. + /// + /// It modifies the zone table accessible through the segment (passed to + /// constructor). + /// + /// \throw isc::InvalidOperation if it is called the second time in + /// lifetime of the object or if load() was not called previously or if + /// cleanup() was already called. + virtual void install(); + + /// \brief Clean up memory. + /// + /// Cleans up the memory used by load()ed zone if not yet installed, or + /// the old zone replaced by install(). + virtual void cleanup(); +private: + ZoneTableSegmentLocal* segment_; + LoadAction load_action_; + dns::Name origin_; + dns::RRClass rrclass_; + ZoneData* zone_data_; + enum State { + ZW_UNUSED, + ZW_LOADED, + ZW_INSTALLED, + ZW_CLEANED + }; + State state_; +}; + +} +} +} + +#endif diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h index be545d42df..4e277e07b2 100644 --- a/src/lib/datasrc/memory_datasrc.h +++ b/src/lib/datasrc/memory_datasrc.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __MEMORY_DATA_SOURCE_H -#define __MEMORY_DATA_SOURCE_H 1 +#ifndef MEMORY_DATA_SOURCE_H +#define MEMORY_DATA_SOURCE_H 1 #include @@ -360,7 +360,7 @@ extern "C" void destroyInstance(DataSourceClient* instance); } } -#endif // __DATA_SOURCE_MEMORY_H +#endif // MEMORY_DATA_SOURCE_H // Local Variables: // mode: c++ // End: diff --git a/src/lib/datasrc/memory_datasrc_link.cc b/src/lib/datasrc/memory_datasrc_link.cc index cbbc6db4c1..857223f699 100644 --- a/src/lib/datasrc/memory_datasrc_link.cc +++ b/src/lib/datasrc/memory_datasrc_link.cc @@ -129,7 +129,7 @@ checkConfig(ConstElementPtr config, ElementPtr errors) { result = false; } else { try { - RRClass rrc(config->get("class")->stringValue()); + RRClass(config->get("class")->stringValue()); } catch (const isc::Exception& rrce) { addError(errors, "Error parsing class config for memory backend: " + diff --git a/src/lib/datasrc/rbnode_rrset.h b/src/lib/datasrc/rbnode_rrset.h index 1c23e05d67..cbb1b71398 100644 --- a/src/lib/datasrc/rbnode_rrset.h +++ b/src/lib/datasrc/rbnode_rrset.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __RBNODE_RRSET_H -#define __RBNODE_RRSET_H +#ifndef RBNODE_RRSET_H +#define RBNODE_RRSET_H #include #include @@ -227,4 +227,4 @@ private: } // namespace datasrc } // namespace isc -#endif // __RBNODE_RRSET_H +#endif // RBNODE_RRSET_H diff --git a/src/lib/datasrc/rbtree.h b/src/lib/datasrc/rbtree.h index eb971e80c5..d0efa0a4de 100644 --- a/src/lib/datasrc/rbtree.h +++ b/src/lib/datasrc/rbtree.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef _RBTREE_H -#define _RBTREE_H 1 +#ifndef RBTREE_H +#define RBTREE_H 1 //! \file datasrc/rbtree.h /// @@ -1986,7 +1986,7 @@ RBTree::dumpDotHelper(std::ostream& os, const RBNode* node, } } -#endif // _RBTREE_H +#endif // RBTREE_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/result.h b/src/lib/datasrc/result.h index f7ca363556..5a28d08099 100644 --- a/src/lib/datasrc/result.h +++ b/src/lib/datasrc/result.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATASRC_RESULT_H -#define __DATASRC_RESULT_H 1 +#ifndef DATASRC_RESULT_H +#define DATASRC_RESULT_H 1 namespace isc { namespace datasrc { diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h index 3e44d5b71c..a8112d40b1 100644 --- a/src/lib/datasrc/sqlite3_accessor.h +++ b/src/lib/datasrc/sqlite3_accessor.h @@ -13,8 +13,8 @@ // PERFORMANCE OF THIS SOFTWARE. -#ifndef __DATASRC_SQLITE3_ACCESSOR_H -#define __DATASRC_SQLITE3_ACCESSOR_H +#ifndef DATASRC_SQLITE3_ACCESSOR_H +#define DATASRC_SQLITE3_ACCESSOR_H #include #include @@ -291,7 +291,7 @@ extern "C" void destroyInstance(DataSourceClient* instance); } } -#endif // __DATASRC_SQLITE3_CONNECTION_H +#endif // DATASRC_SQLITE3_ACCESSOR_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am index d2049f14d4..d3ef3bfec3 100644 --- a/src/lib/datasrc/tests/Makefile.am +++ b/src/lib/datasrc/tests/Makefile.am @@ -59,6 +59,7 @@ run_unittests_SOURCES += zonetable_unittest.cc run_unittests_SOURCES += zone_finder_context_unittest.cc run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc run_unittests_SOURCES += client_list_unittest.cc +run_unittests_SOURCES += master_loader_callbacks_test.cc # We need the actual module implementation in the tests (they are not part # of libdatasrc) @@ -93,6 +94,7 @@ endif EXTRA_DIST = testdata/brokendb.sqlite3 EXTRA_DIST += testdata/contexttest.zone +EXTRA_DIST += testdata/contexttest-almost-obsolete.zone EXTRA_DIST += testdata/diffs.sqlite3 EXTRA_DIST += testdata/duplicate_rrset.zone EXTRA_DIST += testdata/example2.com diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc index d995d5c910..6769e9b537 100644 --- a/src/lib/datasrc/tests/client_list_unittest.cc +++ b/src/lib/datasrc/tests/client_list_unittest.cc @@ -12,14 +12,14 @@ // 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 #include @@ -27,15 +27,20 @@ #include +#include + #include #include using namespace isc::datasrc; using isc::datasrc::memory::InMemoryClient; +using isc::datasrc::memory::ZoneTableSegment; using isc::datasrc::memory::InMemoryZoneFinder; using namespace isc::data; using namespace isc::dns; -using namespace boost; +// don't import the entire boost namespace. It will unexpectedly hide uintXX_t +// for some systems. +using boost::shared_ptr; using namespace std; namespace { @@ -255,7 +260,9 @@ public: " \"type\": \"test_type\"," " \"params\": [\"example.org\", \"example.com\", " " \"noiter.org\", \"null.org\"]" - "}]")) + "}]")), + config_(Element::fromJSON("{}")), + ztable_segment_(ZoneTableSegment::create(*config_, rrclass_)) { for (size_t i(0); i < ds_count; ++ i) { shared_ptr @@ -263,7 +270,7 @@ public: ds_.push_back(ds); ds_info_.push_back(ConfigurableClientList::DataSourceInfo( ds.get(), DataSourceClientContainerPtr(), - false, rrclass_, mem_sgmt_)); + false, rrclass_, ztable_segment_)); } } @@ -283,13 +290,14 @@ public: // Create cache from the temporary data source, and push it to the // client list. - const shared_ptr cache(new InMemoryClient(mem_sgmt_, - rrclass_)); + const shared_ptr cache( + new InMemoryClient(ztable_segment_, rrclass_)); cache->load(zone, *mock_client.getIterator(zone, false)); ConfigurableClientList::DataSourceInfo& dsrc_info = list_->getDataSources()[index]; dsrc_info.cache_ = cache; + dsrc_info.ztable_segment_ = ztable_segment_; } // Check the positive result is as we expect it. void positiveResult(const ClientList::FindResult& result, @@ -309,7 +317,7 @@ public: result.life_keeper_); if (from_cache) { EXPECT_NE(shared_ptr(), - dynamic_pointer_cast( + boost::dynamic_pointer_cast( result.finder_)) << "Finder is not from cache"; EXPECT_TRUE(NULL != dynamic_cast(result.dsrc_client_)); @@ -362,12 +370,12 @@ public: shared_ptr()); } const RRClass rrclass_; - isc::util::MemorySegmentLocal mem_sgmt_; shared_ptr list_; const ClientList::FindResult negative_result_; vector > ds_; vector ds_info_; - const ConstElementPtr config_elem_, config_elem_zones_; + const ConstElementPtr config_elem_, config_elem_zones_, config_; + shared_ptr ztable_segment_; }; // Test the test itself @@ -819,141 +827,215 @@ TEST_F(ListTest, masterFiles) { } TEST_F(ListTest, BadMasterFile) { - // Configure two zone correctly, and one with the wrong origin - // (resulting in an out-of-zone data error) - // Configuration should succeed, and the correct zones should - // be loaded. Neither the 'bad' origin or the zone it used - // should be loaded + // Configuration should succeed, and the good zones in the list + // below should be loaded. No bad zones should be loaded. const ConstElementPtr elem(Element::fromJSON("[" "{" " \"type\": \"MasterFiles\"," " \"cache-enable\": true," " \"params\": {" + + // good zone " \"example.com.\": \"" TEST_DATA_DIR "/example.com.flattened\"," + + // bad zone (empty file) + " \"example.net.\": \"" TEST_DATA_DIR "/example.net-empty\"," + + // bad zone (data doesn't validate: see the file for details) + " \"example.edu.\": \"" TEST_DATA_DIR "/example.edu-broken\"," + + // bad zone (file doesn't exist) + " \"example.info.\": \"" TEST_DATA_DIR "/example.info-nonexist\"," + + // bad zone (data doesn't match the zone name) " \"foo.bar.\": \"" TEST_DATA_DIR "/example.org.nsec3-signed\"," + + // good zone " \".\": \"" TEST_DATA_DIR "/root.zone\"" + " }" "}]")); - list_->configure(elem, true); + + EXPECT_NO_THROW({ + // This should not throw even if there are any zone loading + // errors. + list_->configure(elem, true); + }); positiveResult(list_->find(Name("example.com."), true), ds_[0], Name("example.com."), true, "example.com", true); EXPECT_TRUE(negative_result_ == list_->find(Name("example.org."), true)); EXPECT_TRUE(negative_result_ == list_->find(Name("foo.bar"), true)); + EXPECT_TRUE(negative_result_ == list_->find(Name("example.net."), true)); + EXPECT_TRUE(negative_result_ == list_->find(Name("example.edu."), true)); + EXPECT_TRUE(negative_result_ == list_->find(Name("example.info."), true)); positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root", true); } +// This allows us to test two versions of the reloading code +// (One by calling reload(), one by obtaining a ZoneWriter and +// playing with that). Once we deprecate reload(), we should revert this +// change and not use typed tests any more. +template +class ReloadTest : public ListTest { +public: + ConfigurableClientList::ReloadResult doReload(const Name& origin); +}; + +// Version with calling reload() +class ReloadUpdateType {}; +template<> +ConfigurableClientList::ReloadResult +ReloadTest::doReload(const Name& origin) { + return (list_->reload(origin)); +}; + +// Version with the ZoneWriter +class WriterUpdateType {}; +template<> +ConfigurableClientList::ReloadResult +ReloadTest::doReload(const Name& origin) { + ConfigurableClientList::ZoneWriterPair + result(list_->getCachedZoneWriter(origin)); + if (result.first == ConfigurableClientList::ZONE_SUCCESS) { + // Can't use ASSERT_NE here, it would want to return(), which + // it can't in non-void function. + if (result.second) { + result.second->load(); + result.second->install(); + result.second->cleanup(); + } else { + ADD_FAILURE() << "getCachedZoneWriter returned ZONE_SUCCESS, " + "but the writer is NULL"; + } + } else { + EXPECT_EQ(static_cast(NULL), + result.second.get()); + } + return (result.first); +} + +// Typedefs for the GTEST guts to make it work +typedef ::testing::Types UpdateTypes; +TYPED_TEST_CASE(ReloadTest, UpdateTypes); + // Test we can reload a zone -TEST_F(ListTest, reloadSuccess) { - list_->configure(config_elem_zones_, true); +TYPED_TEST(ReloadTest, reloadSuccess) { + this->list_->configure(this->config_elem_zones_, true); const Name name("example.org"); - prepareCache(0, name); + this->prepareCache(0, name); // The cache currently contains a tweaked version of zone, which doesn't // have apex NS. So the lookup should result in NXRRSET. EXPECT_EQ(ZoneFinder::NXRRSET, - list_->find(name).finder_->find(name, RRType::NS())->code); + this->list_->find(name).finder_->find(name, RRType::NS())->code); // Now reload the full zone. It should be there now. - EXPECT_EQ(ConfigurableClientList::ZONE_RELOADED, list_->reload(name)); + EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, this->doReload(name)); EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(name).finder_->find(name, RRType::NS())->code); + this->list_->find(name).finder_->find(name, RRType::NS())->code); } // The cache is not enabled. The load should be rejected. -TEST_F(ListTest, reloadNotEnabled) { - list_->configure(config_elem_zones_, false); +TYPED_TEST(ReloadTest, reloadNotEnabled) { + this->list_->configure(this->config_elem_zones_, false); const Name name("example.org"); // We put the cache in even when not enabled. This won't confuse the thing. - prepareCache(0, name); + this->prepareCache(0, name); // See the reloadSuccess test. This should result in NXRRSET. EXPECT_EQ(ZoneFinder::NXRRSET, - list_->find(name).finder_->find(name, RRType::NS())->code); + this->list_->find(name).finder_->find(name, RRType::NS())->code); // Now reload. It should reject it. - EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, list_->reload(name)); + EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, this->doReload(name)); // Nothing changed here EXPECT_EQ(ZoneFinder::NXRRSET, - list_->find(name).finder_->find(name, RRType::NS())->code); + this->list_->find(name).finder_->find(name, RRType::NS())->code); } // Test several cases when the zone does not exist -TEST_F(ListTest, reloadNoSuchZone) { - list_->configure(config_elem_zones_, true); +TYPED_TEST(ReloadTest, reloadNoSuchZone) { + this->list_->configure(this->config_elem_zones_, true); const Name name("example.org"); // We put the cache in even when not enabled. This won't confuse the // reload method, as that one looks at the real state of things, not // at the configuration. - prepareCache(0, Name("example.com")); + this->prepareCache(0, Name("example.com")); // Not in the data sources EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, - list_->reload(Name("example.cz"))); + this->doReload(Name("exmaple.cz"))); // Not cached - EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, list_->reload(name)); + EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, this->doReload(name)); // Partial match EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, - list_->reload(Name("sub.example.com"))); + this->doReload(Name("sub.example.com"))); // Nothing changed here - these zones don't exist EXPECT_EQ(static_cast(NULL), - list_->find(name).dsrc_client_); + this->list_->find(name).dsrc_client_); EXPECT_EQ(static_cast(NULL), - list_->find(Name("example.cz")).dsrc_client_); + this->list_->find(Name("example.cz")).dsrc_client_); EXPECT_EQ(static_cast(NULL), - list_->find(Name("sub.example.com"), true).dsrc_client_); + this->list_->find(Name("sub.example.com"), true).dsrc_client_); // Not reloaded, so NS shouldn't be visible yet. EXPECT_EQ(ZoneFinder::NXRRSET, - list_->find(Name("example.com")).finder_-> + this->list_->find(Name("example.com")).finder_-> find(Name("example.com"), RRType::NS())->code); } // Check we gracefuly throw an exception when a zone disappeared in // the underlying data source when we want to reload it -TEST_F(ListTest, reloadZoneGone) { - list_->configure(config_elem_, true); +TYPED_TEST(ReloadTest, reloadZoneGone) { + this->list_->configure(this->config_elem_, true); const Name name("example.org"); // We put in a cache for non-existant zone. This emulates being loaded // and then the zone disappearing. We prefill the cache, so we can check // it. - prepareCache(0, name); + this->prepareCache(0, name); // The (cached) zone contains zone's SOA EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(name).finder_->find(name, RRType::SOA())->code); + this->list_->find(name).finder_->find(name, + RRType::SOA())->code); // The zone is not there, so abort the reload. - EXPECT_THROW(list_->reload(name), DataSourceError); + EXPECT_THROW(this->doReload(name), DataSourceError); // The (cached) zone is not hurt. EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(name).finder_->find(name, RRType::SOA())->code); + this->list_->find(name).finder_->find(name, + RRType::SOA())->code); } // The underlying data source throws. Check we don't modify the state. -TEST_F(ListTest, reloadZoneThrow) { - list_->configure(config_elem_zones_, true); +TYPED_TEST(ReloadTest, reloadZoneThrow) { + this->list_->configure(this->config_elem_zones_, true); const Name name("noiter.org"); - prepareCache(0, name); + this->prepareCache(0, name); // The zone contains stuff now EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(name).finder_->find(name, RRType::SOA())->code); + this->list_->find(name).finder_->find(name, + RRType::SOA())->code); // The iterator throws, so abort the reload. - EXPECT_THROW(list_->reload(name), isc::NotImplemented); + EXPECT_THROW(this->doReload(name), isc::NotImplemented); // The zone is not hurt. EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(name).finder_->find(name, RRType::SOA())->code); + this->list_->find(name).finder_->find(name, + RRType::SOA())->code); } -TEST_F(ListTest, reloadNullIterator) { - list_->configure(config_elem_zones_, true); +TYPED_TEST(ReloadTest, reloadNullIterator) { + this->list_->configure(this->config_elem_zones_, true); const Name name("null.org"); - prepareCache(0, name); + this->prepareCache(0, name); // The zone contains stuff now EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(name).finder_->find(name, RRType::SOA())->code); + this->list_->find(name).finder_->find(name, + RRType::SOA())->code); // The iterator throws, so abort the reload. - EXPECT_THROW(list_->reload(name), isc::Unexpected); + EXPECT_THROW(this->doReload(name), isc::Unexpected); // The zone is not hurt. EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(name).finder_->find(name, RRType::SOA())->code); + this->list_->find(name).finder_->find(name, + RRType::SOA())->code); } // Test we can reload the master files too (special-cased) -TEST_F(ListTest, reloadMasterFile) { +TYPED_TEST(ReloadTest, reloadMasterFile) { const char* const install_cmd = INSTALL_PROG " -c " TEST_DATA_DIR "/root.zone " TEST_DATA_BUILDDIR "/root.zone.copied"; if (system(install_cmd) != 0) { @@ -971,21 +1053,21 @@ TEST_F(ListTest, reloadMasterFile) { " \".\": \"" TEST_DATA_BUILDDIR "/root.zone.copied\"" " }" "}]")); - list_->configure(elem, true); + this->list_->configure(elem, true); // Add a record that is not in the zone EXPECT_EQ(ZoneFinder::NXDOMAIN, - list_->find(Name(".")).finder_->find(Name("nosuchdomain"), - RRType::TXT())->code); + this->list_->find(Name(".")).finder_->find(Name("nosuchdomain"), + RRType::TXT())->code); ofstream f; f.open(TEST_DATA_BUILDDIR "/root.zone.copied", ios::out | ios::app); f << "nosuchdomain.\t\t3600\tIN\tTXT\ttest" << std::endl; f.close(); // Do the reload. - EXPECT_EQ(ConfigurableClientList::ZONE_RELOADED, list_->reload(Name("."))); + EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, this->doReload(Name("."))); // It is here now. EXPECT_EQ(ZoneFinder::SUCCESS, - list_->find(Name(".")).finder_->find(Name("nosuchdomain"), - RRType::TXT())->code); + this->list_->find(Name(".")).finder_->find(Name("nosuchdomain"), + RRType::TXT())->code); } } diff --git a/src/lib/datasrc/tests/master_loader_callbacks_test.cc b/src/lib/datasrc/tests/master_loader_callbacks_test.cc new file mode 100644 index 0000000000..f814288b83 --- /dev/null +++ b/src/lib/datasrc/tests/master_loader_callbacks_test.cc @@ -0,0 +1,124 @@ +// 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 + +using namespace isc::datasrc; + +namespace { + +// An updater for the tests. Most of the virtual methods throw +// NotImplemented, as they are not used in the tests. +class MockUpdater : public ZoneUpdater { +public: + // We do the adding in this test. We currently only check these are + // the correct ones, according to a predefined set in a list. + virtual void addRRset(const isc::dns::AbstractRRset& rrset) { + ASSERT_FALSE(expected_rrsets_.empty()); + // In our tests, pointer equality is enough. + EXPECT_EQ(expected_rrsets_.front().get(), &rrset); + // And remove this RRset, as it has been used. + expected_rrsets_.pop_front(); + } + // The unused but required methods + virtual ZoneFinder& getFinder() { + isc_throw(isc::NotImplemented, "Not to be called in this test"); + } + virtual void deleteRRset(const isc::dns::AbstractRRset&) { + isc_throw(isc::NotImplemented, "Not to be called in this test"); + } + virtual void commit() { + isc_throw(isc::NotImplemented, "Not to be called in this test"); + } + // The RRsets that are expected to appear through addRRset. + std::list expected_rrsets_; +}; + +class MasterLoaderCallbackTest : public ::testing::Test { +protected: + MasterLoaderCallbackTest() : + ok_(true), + callbacks_(createMasterLoaderCallbacks(isc::dns::Name("example.org"), + isc::dns::RRClass::IN(), &ok_)) + {} + // Generate a new RRset, put it to the updater and return it. + isc::dns::RRsetPtr generateRRset() { + const isc::dns::RRsetPtr + result(new isc::dns::RRset(isc::dns::Name("example.org"), + isc::dns::RRClass::IN(), + isc::dns::RRType::A(), + isc::dns::RRTTL(3600))); + updater_.expected_rrsets_.push_back(result); + return (result); + } + // An updater to be passed to the context + MockUpdater updater_; + // Is the loading OK? + bool ok_; + // The tested context + isc::dns::MasterLoaderCallbacks callbacks_; +}; + +// Check it doesn't crash if we don't provide the OK +TEST_F(MasterLoaderCallbackTest, noOkProvided) { + createMasterLoaderCallbacks(isc::dns::Name("example.org"), + isc::dns::RRClass::IN(), NULL). + error("No source", 1, "No reason"); +} + +// Check the callbacks can be called, don't crash and the error one switches +// to non-OK mode. This, however, does not stop anybody from calling more +// callbacks. +TEST_F(MasterLoaderCallbackTest, callbacks) { + EXPECT_NO_THROW(callbacks_.warning("No source", 1, "Just for fun")); + // The warning does not hurt the OK mode. + EXPECT_TRUE(ok_); + // Now the error + EXPECT_NO_THROW(callbacks_.error("No source", 2, "Some error")); + // The OK is turned off once there's at least one error + EXPECT_FALSE(ok_); + + // Not being OK does not hurt that much, we can still call the callbacks + EXPECT_NO_THROW(callbacks_.warning("No source", 3, "Just for fun")); + // The OK is not reset back to true + EXPECT_FALSE(ok_); + EXPECT_NO_THROW(callbacks_.error("No source", 4, "Some error")); +} + +// Try adding some RRsets. +TEST_F(MasterLoaderCallbackTest, addRRset) { + isc::dns::AddRRsetCallback + callback(createMasterLoaderAddCallback(updater_)); + // Put some of them in. + EXPECT_NO_THROW(callback(generateRRset())); + EXPECT_NO_THROW(callback(generateRRset())); + // They all get pushed there right away, so there are none in the queue + EXPECT_TRUE(updater_.expected_rrsets_.empty()); +} + +} diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am index 37e9043523..a5d73b4034 100644 --- a/src/lib/datasrc/tests/memory/Makefile.am +++ b/src/lib/datasrc/tests/memory/Makefile.am @@ -32,7 +32,11 @@ run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc run_unittests_SOURCES += memory_segment_test.h run_unittests_SOURCES += segment_object_holder_unittest.cc run_unittests_SOURCES += memory_client_unittest.cc +run_unittests_SOURCES += zone_data_loader_unittest.cc +run_unittests_SOURCES += zone_data_updater_unittest.cc +run_unittests_SOURCES += zone_table_segment_test.h run_unittests_SOURCES += zone_table_segment_unittest.cc +run_unittests_SOURCES += zone_writer_unittest.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc index c5b6c10622..2a9a35d45a 100644 --- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc +++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc @@ -29,21 +29,30 @@ #include #include #include +#include +#include #include #include #include "memory_segment_test.h" +#include "zone_table_segment_test.h" #include +#include +#include + #include // for bad_alloc +using namespace isc::data; using namespace isc::dns; using namespace isc::dns::rdata; using namespace isc::datasrc; using namespace isc::datasrc::memory; using namespace isc::testutils; +using boost::shared_ptr; +using std::vector; namespace { @@ -86,8 +95,7 @@ private: MockIterator(const char** rrset_data_ptr, bool pass_empty_rrsig) : rrset_data_ptr_(rrset_data_ptr), pass_empty_rrsig_(pass_empty_rrsig) - { - } + {} const char** rrset_data_ptr_; // If true, emulate an unexpected bogus case where an RRSIG RRset is @@ -123,23 +131,53 @@ public: } }; +class MockVectorIterator : public ZoneIterator { +private: + MockVectorIterator(const vector& rrsets) : + rrsets_(rrsets), + counter_(0) + {} + + const vector rrsets_; + int counter_; + +public: + virtual ConstRRsetPtr getNextRRset() { + if (counter_ >= rrsets_.size()) { + return (ConstRRsetPtr()); + } + + return (rrsets_[counter_++]); + } + + virtual ConstRRsetPtr getSOA() const { + isc_throw(isc::NotImplemented, "Not implemented"); + } + + static ZoneIteratorPtr makeIterator(const vector& rrsets) { + return (ZoneIteratorPtr(new MockVectorIterator(rrsets))); + } +}; + class MemoryClientTest : public ::testing::Test { protected: MemoryClientTest() : zclass_(RRClass::IN()), - client_(new InMemoryClient(mem_sgmt_, zclass_)) + ztable_segment_(new test::ZoneTableSegmentTest( + zclass_, mem_sgmt_)), + client_(new InMemoryClient(ztable_segment_, zclass_)) {} ~MemoryClientTest() { - if (client_ != NULL) { - delete client_; - } + delete client_; } void TearDown() { delete client_; client_ = NULL; + ztable_segment_.reset(); EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here. } const RRClass zclass_; test::MemorySegmentTest mem_sgmt_; + shared_ptr ztable_segment_; InMemoryClient* client_; }; @@ -148,7 +186,7 @@ TEST_F(MemoryClientTest, loadRRsetDoesntMatchOrigin) { // in an exception. EXPECT_THROW(client_->load(Name("example.com"), TEST_DATA_DIR "/example.org-empty.zone"), - MasterLoadError); + ZoneLoaderException); } TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak1) { @@ -157,7 +195,7 @@ TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak1) { // allocations. EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-broken1.zone"), - MasterLoadError); + ZoneLoaderException); // Teardown checks for memory segment leaks } @@ -167,14 +205,14 @@ TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak2) { // allocations. EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-broken2.zone"), - MasterLoadError); + ZoneLoaderException); // Teardown checks for memory segment leaks } TEST_F(MemoryClientTest, loadNonExistentZoneFile) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/somerandomfilename"), - MasterLoadError); + ZoneLoaderException); // Teardown checks for memory segment leaks } @@ -187,7 +225,7 @@ TEST_F(MemoryClientTest, loadEmptyZoneFileThrows) { EXPECT_THROW(client_->load(Name("."), TEST_DATA_DIR "/empty.zone"), - InMemoryClient::EmptyZone); + EmptyZone); EXPECT_EQ(0, client_->getZoneCount()); @@ -241,13 +279,13 @@ TEST_F(MemoryClientTest, loadFromIterator) { EXPECT_THROW(client_->load(Name("example.org"), *MockIterator::makeIterator( rrset_data_separated)), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Similar to the previous case, but with separated RRSIGs. EXPECT_THROW(client_->load(Name("example.org"), *MockIterator::makeIterator( rrset_data_sigseparated)), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Emulating bogus iterator implementation that passes empty RRSIGs. EXPECT_THROW(client_->load(Name("example.org"), @@ -259,10 +297,22 @@ TEST_F(MemoryClientTest, loadMemoryAllocationFailures) { // Just to check that things get cleaned up for (int i = 1; i < 16; i++) { + SCOPED_TRACE("For throw count = " + + boost::lexical_cast(i)); mem_sgmt_.setThrowCount(i); - EXPECT_THROW(client_->load(Name("example.org"), - TEST_DATA_DIR "/example.org.zone"), - std::bad_alloc); + EXPECT_THROW({ + shared_ptr ztable_segment( + new test::ZoneTableSegmentTest( + zclass_, mem_sgmt_)); + + // Include the InMemoryClient construction too here. Now, + // even allocations done from InMemoryClient constructor + // fail (due to MemorySegmentTest throwing) and we check for + // leaks when this happens. + InMemoryClient client2(ztable_segment, zclass_); + client2.load(Name("example.org"), + TEST_DATA_DIR "/example.org.zone"); + }, std::bad_alloc); } // Teardown checks for memory segment leaks } @@ -383,7 +433,7 @@ TEST_F(MemoryClientTest, loadDuplicateType) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-duplicate-type-bad.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -392,7 +442,7 @@ TEST_F(MemoryClientTest, loadMultipleCNAMEThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-multiple-cname.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -401,7 +451,7 @@ TEST_F(MemoryClientTest, loadMultipleDNAMEThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-multiple-dname.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -410,7 +460,7 @@ TEST_F(MemoryClientTest, loadMultipleNSEC3Throws) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-multiple-nsec3.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -419,7 +469,7 @@ TEST_F(MemoryClientTest, loadMultipleNSEC3PARAMThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-multiple-nsec3param.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -428,7 +478,7 @@ TEST_F(MemoryClientTest, loadOutOfZoneThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-out-of-zone.zone"), - MasterLoadError); + ZoneLoaderException); // Teardown checks for memory segment leaks } @@ -437,7 +487,7 @@ TEST_F(MemoryClientTest, loadWildcardNSThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-wildcard-ns.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -446,7 +496,7 @@ TEST_F(MemoryClientTest, loadWildcardDNAMEThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-wildcard-dname.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -455,7 +505,7 @@ TEST_F(MemoryClientTest, loadWildcardNSEC3Throws) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-wildcard-nsec3.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -464,7 +514,7 @@ TEST_F(MemoryClientTest, loadNSEC3WithFewerLabelsThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-nsec3-fewer-labels.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -473,7 +523,7 @@ TEST_F(MemoryClientTest, loadNSEC3WithMoreLabelsThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-nsec3-more-labels.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -482,12 +532,12 @@ TEST_F(MemoryClientTest, loadCNAMEAndNotNSECThrows) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-cname-and-not-nsec-1.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-cname-and-not-nsec-2.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -513,7 +563,7 @@ TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex1) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-dname-ns-nonapex-1.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -522,17 +572,7 @@ TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) { EXPECT_THROW(client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-dname-ns-nonapex-2.zone"), - InMemoryClient::AddError); - // Teardown checks for memory segment leaks -} - -TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) { - // This causes the situation where an RRSIG is added without a covered - // RRset. Such cases are currently rejected. - EXPECT_THROW(client_->load(Name("example.org"), - TEST_DATA_DIR - "/example.org-rrsig-follows-nothing.zone"), - InMemoryClient::AddError); + ZoneDataUpdater::AddError); // Teardown checks for memory segment leaks } @@ -542,6 +582,33 @@ TEST_F(MemoryClientTest, loadRRSIGs) { EXPECT_EQ(1, client_->getZoneCount()); } +TEST_F(MemoryClientTest, loadRRSIGsRdataMixedCoveredTypes) { + vector rrsets_vec; + + rrsets_vec.push_back(textToRRset("example.org. 3600 IN SOA " + "ns1.example.org. bugs.x.w.example.org. " + "2010012601 3600 300 3600000 1200", + zclass_, Name("example.org"))); + RRsetPtr rrset(textToRRset("example.org. 3600 IN A 192.0.2.1\n" + "example.org. 3600 IN A 192.0.2.2\n")); + RRsetPtr rrsig(textToRRset("example.org. 300 IN RRSIG " + "A 5 3 3600 20000101000000 20000201000000 " + "12345 example.org. FAKEFAKEFAKE")); + // textToRRset (correctly) consider this RDATA belongs to a different + // RRSIG, so we need to manually add it. + rrsig->addRdata(generic::RRSIG("NS 5 3 3600 20000101000000 20000201000000 " + "54321 example.org. FAKEFAKEFAKEFAKE")); + rrset->addRRsig(rrsig); + + rrsets_vec.push_back(rrset); + + EXPECT_THROW( + client_->load(Name("example.org"), + *MockVectorIterator::makeIterator(rrsets_vec)), + ZoneDataUpdater::AddError); + // Teardown checks for memory segment leaks +} + TEST_F(MemoryClientTest, getZoneCount) { EXPECT_EQ(0, client_->getZoneCount()); client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone"); @@ -631,6 +698,22 @@ TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) { EXPECT_THROW(iterator->getSOA(), isc::NotImplemented); } +TEST_F(MemoryClientTest, addEmptyRRsetThrows) { + vector rrsets_vec; + rrsets_vec.push_back(textToRRset("example.org. 3600 IN SOA " + "ns1.example.org. bugs.x.w.example.org. " + "2010012601 3600 300 3600000 1200", + zclass_, Name("example.org"))); + rrsets_vec.push_back(RRsetPtr(new RRset(Name("example.org"), zclass_, + RRType::A(), RRTTL(3600)))); + + EXPECT_THROW( + client_->load(Name("example.org"), + *MockVectorIterator::makeIterator(rrsets_vec)), + ZoneDataUpdater::AddError); + // Teardown checks for memory segment leaks +} + TEST_F(MemoryClientTest, findZoneData) { client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-rrsigs.zone"); @@ -682,15 +765,4 @@ TEST_F(MemoryClientTest, getJournalReaderNotImplemented) { isc::NotImplemented); } -// TODO (upon merge of #2268): Re-add (and modify not to need -// InMemoryClient::add) the tests removed in -// 7a628baa1a158b5837d6f383e10b30542d2ac59b. Maybe some of them -// are really not needed. -// -// * MemoryClientTest::loadRRSIGsRdataMixedCoveredTypes -// * MemoryClientTest::addRRsetToNonExistentZoneThrows -// * MemoryClientTest::addOutOfZoneThrows -// * MemoryClientTest::addNullRRsetThrows -// * MemoryClientTest::addEmptyRRsetThrows -// * MemoryClientTest::add } diff --git a/src/lib/datasrc/tests/memory/rdataset_unittest.cc b/src/lib/datasrc/tests/memory/rdataset_unittest.cc index 897e53cd3b..f5999999e2 100644 --- a/src/lib/datasrc/tests/memory/rdataset_unittest.cc +++ b/src/lib/datasrc/tests/memory/rdataset_unittest.cc @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -39,6 +40,7 @@ using namespace isc::dns; using namespace isc::dns::rdata; using namespace isc::datasrc::memory; using namespace isc::testutils; +using isc::datasrc::memory::detail::SegmentObjectHolder; using boost::lexical_cast; namespace { @@ -112,7 +114,7 @@ TEST_F(RdataSetTest, create) { RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()); checkRdataSet(*rdataset, true, false); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); } TEST_F(RdataSetTest, getNext) { @@ -131,7 +133,62 @@ TEST_F(RdataSetTest, getNext) { rdataset->next = rdataset; EXPECT_EQ(rdataset, static_cast(rdataset)->getNext()); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); +} + +TEST_F(RdataSetTest, find) { + // Create some RdataSets and make a chain of them. + SegmentObjectHolder holder1( + mem_sgmt_, + RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()), + RRClass::IN()); + ConstRRsetPtr aaaa_rrset = + textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1"); + SegmentObjectHolder holder2( + mem_sgmt_, + RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()), + RRClass::IN()); + ConstRRsetPtr sigonly_rrset = + textToRRset("www.example.com. 1076895760 IN RRSIG " + "TXT 5 2 3600 20120814220826 20120715220826 " + "1234 example.com. FAKE"); + SegmentObjectHolder holder3( + mem_sgmt_, + RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset), + RRClass::IN()); + + RdataSet* rdataset_a = holder1.get(); + RdataSet* rdataset_aaaa = holder2.get(); + RdataSet* rdataset_sigonly = holder3.get(); + RdataSet* rdataset_null = NULL; + rdataset_a->next = rdataset_aaaa; + rdataset_aaaa->next = rdataset_sigonly; + + // If a non-RRSIG part of rdataset exists for the given type, it will be + // returned regardless of the value of sigonly_ok. If it's RRSIG-only + // rdataset, it returns non NULL iff sigonly_ok is explicitly set to true. + EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA())); + EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), true)); + EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), false)); + + EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT())); + EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a, RRType::TXT(), + true)); + EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT(), false)); + + // Same tests for the const version of find(). + const RdataSet* rdataset_a_const = holder1.get(); + EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA())); + EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(), + true)); + EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(), + false)); + + EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT())); + EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a_const, RRType::TXT(), + true)); + EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT(), + false)); } // A helper function to create an RRset containing the given number of @@ -154,7 +211,7 @@ TEST_F(RdataSetTest, createManyRRs) { ConstRRsetPtr()); EXPECT_EQ(8191, rdataset->getRdataCount()); EXPECT_EQ(0, rdataset->getSigRdataCount()); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); // Exceeding that will result in an exception. EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_, @@ -173,7 +230,7 @@ TEST_F(RdataSetTest, createWithRRSIG) { RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_rrset_); checkRdataSet(*rdataset, true, true); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); // Unusual case: TTL doesn't match. This implementation accepts that, // using the TTL of the covered RRset. @@ -183,7 +240,7 @@ TEST_F(RdataSetTest, createWithRRSIG) { "20120715220826 1234 example.com. FAKE")); rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_badttl); checkRdataSet(*rdataset, true, true); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); } // A helper function to create an RRSIG RRset containing the given number of @@ -218,21 +275,21 @@ TEST_F(RdataSetTest, createManyRRSIGs) { RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, getRRSIGWithRdataCount(7)); EXPECT_EQ(7, rdataset->getSigRdataCount()); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); // 8 would cause overflow in the normal 3-bit field if there were no extra // count field. rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, getRRSIGWithRdataCount(8)); EXPECT_EQ(8, rdataset->getSigRdataCount()); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); // Up to 2^16-1 RRSIGs are allowed (although that would be useless // in practice) rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, getRRSIGWithRdataCount(65535)); EXPECT_EQ(65535, rdataset->getSigRdataCount()); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); // Exceeding this limit will result in an exception. EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_, a_rrset_, @@ -250,7 +307,7 @@ TEST_F(RdataSetTest, createWithRRSIGOnly) { RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), rrsig_rrset_); checkRdataSet(*rdataset, false, true); - RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset); + RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN()); } TEST_F(RdataSetTest, badCeate) { diff --git a/src/lib/datasrc/tests/memory/testdata/2503-test.zone b/src/lib/datasrc/tests/memory/testdata/2503-test.zone new file mode 100644 index 0000000000..1609dee0a9 --- /dev/null +++ b/src/lib/datasrc/tests/memory/testdata/2503-test.zone @@ -0,0 +1,13 @@ +example.com. 3600 IN SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200 +example.com. 3600 IN RRSIG SOA 7 2 3600 20130107110921 20121210110921 55148 example.com. 3yVTkwBE98bAcJJHKwk4WL2bKHQqfejzMdcmtteH/A2H07QwexShqQiRhNNyTRj3s1FfmMYlbd67TDZhUnWAwCdMQICj2BS0hzqiZr9iftGsAkIah6LCFx9nRPNIz2CsqXMf9D7rvzKBDUNBIliYGa5h9d7MalAlM7tZAY9YnH0= +example.com. 3600 IN A 192.0.2.1 +example.com. 3600 IN RRSIG A 7 2 3600 20130107110921 20121210110921 55148 example.com. uCgf94psLo1wWZqAFEcy+5hM1Uh2hAwEVLyiiLESW+jz/cPeOjNFpa2PN1DIUjf2Dj40dDokM3Ev1rSjR6Z+rfHri8rUsahOEw3ZU+UrEDd5cf9MYeo4MlQIrBMAuz3AKOU3YXy7AiPEwIxtlrqrI6o1qGeX8+zKqhjGgnS0/vI= +example.com. 3600 IN NS example.com. +example.com. 3600 IN RRSIG NS 7 2 3600 20130107110921 20121210110921 55148 example.com. dDUDANrEdt46e7793dHxl1zOCWm88gLbr3mHYGSbIBfbtEBtISNGL1nwslejyZKnzhYM2kT8bZ3YGe5ibc9wPlGj29nUJgFwqeyIsWJhffhUCaWKYrDWod0WZLJw5uZcS2QHCuQ0+ZMm8dQoL1qswcahmh0VUI7BEKTHk9RJgzc= +example.com. 3600 IN DNSKEY 256 3 5 AwEAAZIOhUpUld/OBPeNJ26O1twKj3fRLPt4X8H6N01t4s+VT5v9jaCnCVX4O1LbALdJUv5uPwL4gy4qvf+7Z3Xanp7QCZ5i7ivS1qfiz2tfacXwtVv4aI4EqS7deYN6yD4S/vIpwW+2FoqUWhQtdhC68ex1YfjeEI+CUbAKlF5XgQR5 ;{id = 5196 (zsk), size = 1024b} +example.com. 3600 IN DNSKEY 256 3 7 AwEAAe1s2ycmRzexpaUVjRZAd25ybIv7qP+Rh/tF8zIm2bMGLZl5ByxeyajZpPaGRK2xXWdY4aKBAcDELbOPQ+lLPI4RsiZBY+jY5JuzE5inkpyTtwBFjQl6o3AdBC08BmZjrpWnh9rFx/o7tk0ekU6UblUlhpqxRCDwZPvRSIVurrIr ;{id = 55148 (zsk), size = 1024b} +example.com. 3600 IN RRSIG DNSKEY 7 2 3600 20130107110921 20121210110921 55148 example.com. FmOXL+p4GIyRA+LEWRjmoSfvWLkimw58dYrO3LS3hS0hRXP7dkhgMmuYFTOlfcfi53R9sQ9DsH6YsZwR5l0rkBE+GDq+MCfYcKXmQ7OLJZjH/3BnJgQtAlktVTfyie+kWOmwPl3l9jfim7Rmr6anXz3fYvy4S06iTsJ9z2weNJU= +example.com. 3600 IN NSEC3PARAM 1 0 1 - +example.com. 3600 IN RRSIG NSEC3PARAM 7 2 3600 20130107110921 20121210110921 55148 example.com. zIVekIFafb9uc20ATatQJeSV6QULdvg4VWDAWS4tnQgipPdA6VUggeBXSBaAdZu24wh5ObEdeKMChZOuiHcBYKtveQxdWzYEP0gEVvNXK+jTHpHh+siONJetWdfeCRgVma3F134ckCsS2wDSB9pzjV5lJfDmFa4muaTYZswAM8I= +9vq38lj9qs6s1aruer131mbtsfnvek2p.example.com. 7200 IN NSEC3 1 0 1 - 9vq38lj9qs6s1aruer131mbtsfnvek2p A NS SOA RRSIG DNSKEY NSEC3PARAM +9vq38lj9qs6s1aruer131mbtsfnvek2p.example.com. 7200 IN RRSIG NSEC3 7 3 7200 20130107110921 20121210110921 55148 example.com. q3j367t58w/NJ41I27dSFvPFQYiAFMl9hh3nwtZCaSc+KxFK+zvWP7Cm21oRDr+4oJCFkmm0kCSFGwarbFHmqJcVuorH6AKtm8Aiy3uQDvRdBZh/P+uBu4gruQSUaT7jVQLi9WdqR25nPtPC8zuxE3Yy4iRZxtZqQ/JVJFlQ/VM= diff --git a/src/lib/datasrc/tests/memory/testdata/2504-test.zone b/src/lib/datasrc/tests/memory/testdata/2504-test.zone new file mode 100644 index 0000000000..bbbcb83e25 --- /dev/null +++ b/src/lib/datasrc/tests/memory/testdata/2504-test.zone @@ -0,0 +1,10 @@ +example.com. 3600 IN SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200 +example.com. 3600 IN RRSIG SOA 5 2 3600 20130104152459 20121207152459 5196 example.com. ijAvh4ZzAfMCKKWN64aR5CWaHYTAvhJjgBLV+1/RFPG3150DwDr9EI0bCdVyRIMDDLOeQnn0N70rtc051rA2pJVNpEzG2hPIy3Yd2kDsbFBn0atiz3rvjpRLcmmWWYoZihpkFwKRxD41OzJPLg83/yJr1nAu3qSQXh4LmuNfgwI= +example.com. 3600 IN A 192.0.2.1 +example.com. 3600 IN RRSIG A 5 2 3600 20130104152459 20121207152459 5196 example.com. XnVKYuSH+BANWiULSJAk6wmwnba8hUS9j2cgrCMFcn43XlAUCCNigoMCWhaGbsssMaOyjdwfL3sQZr/NHaQ0ITSL8IZj7t8rOiCvCrUAktuG5UuHmHET6XKPkf03JaoPXIzO9smBqvjFHz1HsuPGxwGh8ztY0p291iXk0zbRwc8= +example.com. 3600 IN NS example.com. +example.com. 3600 IN RRSIG NS 5 2 3600 20130104152459 20121207152459 5196 example.com. W5v7dr/WV8FGdxWFS0h1crd1MRxkSrkcmcAs7CT0+uhmVvbx06PsBxGRuCHyL8Y5NimsMs2RLjhkUkJw1+aSLVtTlbC8Pg5dFDK3bGkzBEq3wRcIXf5bM2Lf+l/cWxGY0NgR1Wrq0ckXsnFxegGm9G3OtpCZgTv0L+9cCO4MS7c= +example.com. 3600 IN DNSKEY 256 3 5 AwEAAZIOhUpUld/OBPeNJ26O1twKj3fRLPt4X8H6N01t4s+VT5v9jaCnCVX4O1LbALdJUv5uPwL4gy4qvf+7Z3Xanp7QCZ5i7ivS1qfiz2tfacXwtVv4aI4EqS7deYN6yD4S/vIpwW+2FoqUWhQtdhC68ex1YfjeEI+CUbAKlF5XgQR5 ;{id = 5196 (zsk), size = 1024b} +example.com. 3600 IN RRSIG DNSKEY 5 2 3600 20130104152459 20121207152459 5196 example.com. OiN+DBuEDnyEmMe0Qa4MN4SIJ71e+INNhOvoGBpWARiWu83QlPkoJhdU1GADBaanYdFL8UI0os6w2dkwp4aghChD+KWO40NuhUY2LrEUS2jbO3hEcCT3/acaVyucwVv1FjfC4d561Lkfnh8DM9nk5i75IeWVLklMqret1t/f0uY= +example.com. 7200 IN NSEC example.com. A NS SOA RRSIG NSEC DNSKEY +example.com. 7200 IN RRSIG NSEC 5 2 7200 20130104152459 20121207152459 5196 example.com. P4eCHTNJImfwQ+wLa3jeySkVeJnzCmb4zFUDQEZIk3GSjLGUHZhHswqSAhpgevx6ZNX/pOqN/Dybf9oadFCZXyoMQDijP02LcDEl4X1ccNiakg6i/9RG9PcEx5ZWJlCFAJ0tOhp1kauZu/HUlkscTAVgBRud0qEclWJacH1k80E= diff --git a/src/lib/datasrc/tests/memory/testdata/Makefile.am b/src/lib/datasrc/tests/memory/testdata/Makefile.am index dddbf0a3dd..ef5f08bd5d 100644 --- a/src/lib/datasrc/tests/memory/testdata/Makefile.am +++ b/src/lib/datasrc/tests/memory/testdata/Makefile.am @@ -29,3 +29,6 @@ EXTRA_DIST += example.org-rrsigs.zone EXTRA_DIST += example.org-wildcard-dname.zone EXTRA_DIST += example.org-wildcard-ns.zone EXTRA_DIST += example.org-wildcard-nsec3.zone + +EXTRA_DIST += 2503-test.zone +EXTRA_DIST += 2504-test.zone diff --git a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc new file mode 100644 index 0000000000..c005bf1cd9 --- /dev/null +++ b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc @@ -0,0 +1,65 @@ +// 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 "memory_segment_test.h" + +#include + +using namespace isc::dns; +using namespace isc::datasrc::memory; + +namespace { + +class ZoneDataLoaderTest : public ::testing::Test { +protected: + ZoneDataLoaderTest() : zclass_(RRClass::IN()), zone_data_(NULL) {} + void TearDown() { + if (zone_data_ != NULL) { + ZoneData::destroy(mem_sgmt_, zone_data_, zclass_); + } + EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here. + } + const RRClass zclass_; + test::MemorySegmentTest mem_sgmt_; + ZoneData* zone_data_; +}; + +TEST_F(ZoneDataLoaderTest, loadRRSIGFollowsNothing) { + // This causes the situation where an RRSIG is added without a covered + // RRset. It will be accepted, and corresponding "sig-only" rdata will + // be created. + zone_data_ = loadZoneData(mem_sgmt_, zclass_, Name("example.org"), + TEST_DATA_DIR + "/example.org-rrsig-follows-nothing.zone"); + ZoneNode* node = NULL; + zone_data_->insertName(mem_sgmt_, Name("ns1.example.org"), &node); + ASSERT_NE(static_cast(NULL), node); + const RdataSet* rdset = node->getData(); + ASSERT_NE(static_cast(NULL), rdset); + EXPECT_EQ(RRType::A(), rdset->type); // there should be only 1 data here + EXPECT_EQ(0, rdset->getRdataCount()); // no RDATA + EXPECT_EQ(1, rdset->getSigRdataCount()); // but 1 SIG + + // Teardown checks for memory segment leaks +} + +} diff --git a/src/lib/datasrc/tests/memory/zone_data_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_unittest.cc index 3c28cec229..1605fa2951 100644 --- a/src/lib/datasrc/tests/memory/zone_data_unittest.cc +++ b/src/lib/datasrc/tests/memory/zone_data_unittest.cc @@ -85,11 +85,16 @@ protected: // Shared by both test cases using NSEC3 and NSEC3PARAM Rdata template void -checkNSEC3Data(MemorySegmentTest& mem_sgmt, const RdataType& expect_rdata) { - NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt, expect_rdata); +checkNSEC3Data(MemorySegmentTest& mem_sgmt, + const Name& zone_name, + const RdataType& expect_rdata) +{ + NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt, zone_name, + expect_rdata); - // Internal tree should be created and empty. - EXPECT_EQ(0, nsec3_data->getNSEC3Tree().getNodeCount()); + // Internal tree should be created and must contain just the origin + // node. + EXPECT_EQ(1, nsec3_data->getNSEC3Tree().getNodeCount()); EXPECT_EQ(expect_rdata.getHashalg(), nsec3_data->hashalg); EXPECT_EQ(expect_rdata.getFlags(), nsec3_data->flags); @@ -117,18 +122,18 @@ checkFindRdataSet(const ZoneTree& tree, const Name& name, RRType type, TEST_F(ZoneDataTest, createNSEC3Data) { // Create an NSEC3Data object from various types of RDATA (of NSEC3PARAM // and of NSEC3), check if the resulting parameters match. - checkNSEC3Data(mem_sgmt_, param_rdata_); // one 'usual' form of params - checkNSEC3Data(mem_sgmt_, param_rdata_nosalt_); // empty salt - checkNSEC3Data(mem_sgmt_, param_rdata_largesalt_); // max-len salt + checkNSEC3Data(mem_sgmt_, zname_, param_rdata_); // one 'usual' form + checkNSEC3Data(mem_sgmt_, zname_, param_rdata_nosalt_); // empty salt + checkNSEC3Data(mem_sgmt_, zname_, param_rdata_largesalt_); // max-len salt // Same concepts of the tests, using NSEC3 RDATA. - checkNSEC3Data(mem_sgmt_, nsec3_rdata_); - checkNSEC3Data(mem_sgmt_, nsec3_rdata_nosalt_); - checkNSEC3Data(mem_sgmt_, nsec3_rdata_largesalt_); + checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_); + checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_nosalt_); + checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_largesalt_); } TEST_F(ZoneDataTest, addNSEC3) { - nsec3_data_ = NSEC3Data::create(mem_sgmt_, param_rdata_); + nsec3_data_ = NSEC3Data::create(mem_sgmt_, zname_, param_rdata_); ZoneNode* node = NULL; nsec3_data_->insertName(mem_sgmt_, nsec3_rrset_->getName(), &node); @@ -161,7 +166,8 @@ TEST_F(ZoneDataTest, exceptionSafetyOnCreate) { // will fail due to bad_alloc. It shouldn't cause memory leak // (that would be caught in TearDown()). mem_sgmt_.setThrowCount(2); - EXPECT_THROW(NSEC3Data::create(mem_sgmt_, param_rdata_), std::bad_alloc); + EXPECT_THROW(NSEC3Data::create(mem_sgmt_, zname_, param_rdata_), + std::bad_alloc); // allocate() will throw on the insertion of the origin node. mem_sgmt_.setThrowCount(2); @@ -214,7 +220,7 @@ TEST_F(ZoneDataTest, getSetNSEC3Data) { // Set a new one. The set method should return NULL. The get method // should return the new one. - NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, param_rdata_); + NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, zname_, param_rdata_); NSEC3Data* old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data); EXPECT_EQ(static_cast(NULL), old_nsec3_data); EXPECT_EQ(nsec3_data, zone_data_->getNSEC3Data()); @@ -222,7 +228,7 @@ TEST_F(ZoneDataTest, getSetNSEC3Data) { // Replace an existing one with a yet another one. // We're responsible for destroying the old one. - NSEC3Data* nsec3_data2 = NSEC3Data::create(mem_sgmt_, nsec3_rdata_); + NSEC3Data* nsec3_data2 = NSEC3Data::create(mem_sgmt_, zname_, nsec3_rdata_); old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data2); EXPECT_EQ(nsec3_data, old_nsec3_data); EXPECT_EQ(nsec3_data2, zone_data_->getNSEC3Data()); diff --git a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc new file mode 100644 index 0000000000..63c69c8568 --- /dev/null +++ b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc @@ -0,0 +1,208 @@ +// 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 "memory_segment_test.h" + +#include + +#include + +#include + +using isc::testutils::textToRRset; +using namespace isc::dns; +using namespace isc::datasrc::memory; + +namespace { + +class ZoneDataUpdaterTest : public ::testing::Test { +protected: + ZoneDataUpdaterTest() : + zname_("example.org"), zclass_(RRClass::IN()), + zone_data_(ZoneData::create(mem_sgmt_, zname_)), + updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_)) + {} + + ~ZoneDataUpdaterTest() { + if (zone_data_ != NULL) { + ZoneData::destroy(mem_sgmt_, zone_data_, zclass_); + } + if (!mem_sgmt_.allMemoryDeallocated()) { + ADD_FAILURE() << "Memory leak detected"; + } + } + + void clearZoneData() { + assert(zone_data_ != NULL); + ZoneData::destroy(mem_sgmt_, zone_data_, zclass_); + zone_data_ = ZoneData::create(mem_sgmt_, zname_); + updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, + *zone_data_)); + } + + const Name zname_; + const RRClass zclass_; + test::MemorySegmentTest mem_sgmt_; + ZoneData* zone_data_; + boost::scoped_ptr updater_; +}; + +TEST_F(ZoneDataUpdaterTest, bothNull) { + // At least either covered RRset or RRSIG must be non NULL. + EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()), + ZoneDataUpdater::NullRRset); +} + +ZoneNode* +getNode(isc::util::MemorySegment& mem_sgmt, const Name& name, + ZoneData* zone_data) +{ + ZoneNode* node = NULL; + zone_data->insertName(mem_sgmt, name, &node); + EXPECT_NE(static_cast(NULL), node); + return (node); +} + +TEST_F(ZoneDataUpdaterTest, rrsigOnly) { + // RRSIG that doesn't have covered RRset can be added. The resulting + // rdataset won't have "normal" RDATA but sig RDATA. + updater_->add(ConstRRsetPtr(), textToRRset( + "www.example.org. 3600 IN RRSIG A 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_); + const RdataSet* rdset = node->getData(); + ASSERT_NE(static_cast(NULL), rdset); + rdset = RdataSet::find(rdset, RRType::A(), true); + ASSERT_NE(static_cast(NULL), rdset); + EXPECT_EQ(0, rdset->getRdataCount()); + EXPECT_EQ(1, rdset->getSigRdataCount()); + + // The RRSIG covering A prohibits an actual A RRset from being added. + // This should be loosened in future version, but we check the current + // behavior. + EXPECT_THROW(updater_->add( + textToRRset("www.example.org. 3600 IN A 192.0.2.1"), + ConstRRsetPtr()), ZoneDataUpdater::AddError); + + // The special "wildcarding" node mark should be added for the RRSIG-only + // case, too. + updater_->add(ConstRRsetPtr(), textToRRset( + "*.wild.example.org. 3600 IN RRSIG A 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_); + EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE)); + + // Simply adding RRSIG covering (delegating NS) shouldn't enable callback + // in search. + updater_->add(ConstRRsetPtr(), textToRRset( + "child.example.org. 3600 IN RRSIG NS 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_); + EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK)); + + // Same for DNAME + updater_->add(ConstRRsetPtr(), textToRRset( + "dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_); + EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK)); + + // Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone + // "NSEC3-signed". + updater_->add(ConstRRsetPtr(), textToRRset( + "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + EXPECT_FALSE(zone_data_->isNSEC3Signed()); + + // And same for (RRSIG for) NSEC and "is signed". + updater_->add(ConstRRsetPtr(), textToRRset( + "example.org. 3600 IN RRSIG NSEC 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + EXPECT_FALSE(zone_data_->isSigned()); +} + +// Commonly used checks for rrsigForNSEC3Only +void +checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name, + ZoneData* zone_data) +{ + ZoneNode* node = NULL; + zone_data->getNSEC3Data()->insertName(mem_sgmt, name, &node); + ASSERT_NE(static_cast(NULL), node); + const RdataSet* rdset = node->getData(); + ASSERT_NE(static_cast(NULL), rdset); + ASSERT_EQ(RRType::NSEC3(), rdset->type); + EXPECT_EQ(0, rdset->getRdataCount()); + EXPECT_EQ(1, rdset->getSigRdataCount()); +} + +TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) { + // Adding only RRSIG covering NSEC3 is tricky. It should go to the + // separate NSEC3 tree, but the separate space is only created when + // NSEC3 or NSEC3PARAM is added. So, in many cases RRSIG-only is allowed, + // but if no NSEC3 or NSEC3PARAM has been added it will be rejected. + + // Below we use abnormal owner names and RDATA for NSEC3s for brevity, + // but that doesn't matter for this test. + + // Add NSEC3PARAM, then RRSIG-only, which is okay. + updater_->add(textToRRset( + "example.org. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD"), + textToRRset( + "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + EXPECT_TRUE(zone_data_->isNSEC3Signed()); + updater_->add(ConstRRsetPtr(), + textToRRset( + "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_); + + // Clear the current content of zone, then add NSEC3 + clearZoneData(); + updater_->add(textToRRset( + "AABB.example.org. 3600 IN NSEC3 1 0 10 AA 00000000 A"), + textToRRset( + "AABB.example.org. 3600 IN RRSIG NSEC3 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + updater_->add(ConstRRsetPtr(), + textToRRset( + "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")); + checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_); + + // If we add only RRSIG without any NSEC3 related data beforehand, + // it will be rejected; it's a limitation of the current implementation. + clearZoneData(); + EXPECT_THROW(updater_->add( + ConstRRsetPtr(), + textToRRset( + "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 " + "20150420235959 20051021000000 1 example.org. FAKE")), + isc::NotImplemented); +} + +} diff --git a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc index a536bf5a61..42667da8e8 100644 --- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc +++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc @@ -13,6 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. #include "memory_segment_test.h" +#include "zone_table_segment_test.h" // NOTE: this faked_nsec3 inclusion (and all related code below) // was ported during #2109 for the convenience of implementing #2218 @@ -23,8 +24,12 @@ #include "../../tests/faked_nsec3.h" #include +#include #include +#include +#include #include +#include #include #include @@ -105,7 +110,8 @@ public: class_(RRClass::IN()), origin_("example.org"), zone_data_(ZoneData::create(mem_sgmt_, origin_)), - zone_finder_(*zone_data_, class_) + zone_finder_(*zone_data_, class_), + updater_(mem_sgmt_, class_, origin_, *zone_data_) { // Build test RRsets. Below, we construct an RRset for // each textual RR(s) of zone_data, and assign it to the corresponding @@ -190,130 +196,8 @@ public: ZoneData::destroy(mem_sgmt_, zone_data_, RRClass::IN()); } - // NSEC3-specific call for 'loading' data - void addZoneDataNSEC3(const ConstRRsetPtr rrset) { - assert(rrset->getType() == RRType::NSEC3()); - - const generic::NSEC3& nsec3_rdata = - dynamic_cast( - rrset->getRdataIterator()->getCurrent()); - - NSEC3Data* nsec3_data = zone_data_->getNSEC3Data(); - if (nsec3_data == NULL) { - nsec3_data = NSEC3Data::create(mem_sgmt_, nsec3_rdata); - zone_data_->setNSEC3Data(nsec3_data); - } else { - const size_t salt_len = nsec3_data->getSaltLen(); - const uint8_t* salt_data = nsec3_data->getSaltData(); - const vector& salt_data_2 = nsec3_rdata.getSalt(); - - if ((nsec3_rdata.getHashalg() != nsec3_data->hashalg) || - (nsec3_rdata.getIterations() != nsec3_data->iterations) || - (salt_data_2.size() != salt_len)) { - isc_throw(isc::Unexpected, - "NSEC3 with inconsistent parameters: " << - rrset->toText()); - } - - if ((salt_len > 0) && - (std::memcmp(&salt_data_2[0], salt_data, salt_len) != 0)) { - isc_throw(isc::Unexpected, - "NSEC3 with inconsistent parameters: " << - rrset->toText()); - } - } - - ZoneNode* node; - nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node); - - RdataSet* rdset = RdataSet::create(mem_sgmt_, encoder_, - rrset, ConstRRsetPtr()); - RdataSet* old_rdset = node->setData(rdset); - if (old_rdset != NULL) { - RdataSet::destroy(mem_sgmt_, class_, old_rdset); - } - zone_data_->setSigned(true); - } - - // simplified version of 'loading' data - void addZoneData(const ConstRRsetPtr rrset) { - ZoneNode* node = NULL; - - if (rrset->getType() == RRType::NSEC3()) { - return (addZoneDataNSEC3(rrset)); - } else if (rrset->getType() == RRType::NSEC()) { - zone_data_->setSigned(true); - } - - zone_data_->insertName(mem_sgmt_, rrset->getName(), &node); - - if (rrset->getType() == RRType::NS() && - rrset->getName() != zone_data_->getOriginNode()->getName()) { - node->setFlag(DomainTreeNode::FLAG_CALLBACK); - } else if (rrset->getType() == RRType::DNAME()) { - node->setFlag(DomainTreeNode::FLAG_CALLBACK); - } - - RdataSet* next_rds = node->getData(); - RdataSet* rdataset = - RdataSet::create(mem_sgmt_, encoder_, rrset, rrset->getRRsig()); - rdataset->next = next_rds; - node->setData(rdataset); - - // find wildcard nodes in name (go through all of them in case there - // is a nonterminal one) - // Note that this method is pretty much equal to the 'real' loader; - // but less efficient - Name name(rrset->getName()); - while (name.getLabelCount() > 1) { - if (name.isWildcard()) { - ZoneNode* wnode = NULL; - // add Wild node - zone_data_->insertName(mem_sgmt_, name.split(1), &wnode); - wnode->setFlag(ZoneData::WILDCARD_NODE); - // add wildcard name itself too - zone_data_->insertName(mem_sgmt_, name, &wnode); - } - name = name.split(1); - } - - // If we've added NSEC3PARAM at zone origin, set up NSEC3 - // specific data or check consistency with already set up - // parameters. - if (rrset->getType() == RRType::NSEC3PARAM() && - rrset->getName() == origin_) { - // We know rrset has exactly one RDATA - const generic::NSEC3PARAM& param = - dynamic_cast - (rrset->getRdataIterator()->getCurrent()); - - NSEC3Data* nsec3_data = zone_data_->getNSEC3Data(); - if (nsec3_data == NULL) { - nsec3_data = NSEC3Data::create(mem_sgmt_, param); - zone_data_->setNSEC3Data(nsec3_data); - zone_data_->setSigned(true); - } else { - size_t salt_len = nsec3_data->getSaltLen(); - const uint8_t* salt_data = nsec3_data->getSaltData(); - const vector& salt_data_2 = param.getSalt(); - - if ((param.getHashalg() != nsec3_data->hashalg) || - (param.getIterations() != nsec3_data->iterations) || - (salt_data_2.size() != salt_len)) { - isc_throw(isc::Unexpected, - "NSEC3PARAM with inconsistent parameters: " - << rrset->toText()); - } - - if ((salt_len > 0) && - (std::memcmp(&salt_data_2[0], - salt_data, salt_len) != 0)) { - isc_throw(isc::Unexpected, - "NSEC3PARAM with inconsistent parameters: " - << rrset->toText()); - } - } - } + void addToZoneData(const ConstRRsetPtr rrset) { + updater_.add(rrset, rrset->getRRsig()); } // Some data to test with @@ -323,7 +207,7 @@ public: MemorySegmentTest mem_sgmt_; memory::ZoneData* zone_data_; memory::InMemoryZoneFinder zone_finder_; - isc::datasrc::memory::RdataEncoder encoder_; + ZoneDataUpdater updater_; // Placeholder for storing RRsets to be checked with rrsetsCheck() vector actual_rrsets_; @@ -538,7 +422,7 @@ TEST_F(InMemoryZoneFinderTest, constructor) { TEST_F(InMemoryZoneFinderTest, findCNAME) { // install CNAME RR - addZoneData(rr_cname_); + addToZoneData(rr_cname_); // Find A RR of the same. Should match the CNAME findTest(rr_cname_->getName(), RRType::NS(), ZoneFinder::CNAME, true, @@ -553,10 +437,10 @@ TEST_F(InMemoryZoneFinderTest, findCNAMEUnderZoneCut) { // There's nothing special when we find a CNAME under a zone cut // (with FIND_GLUE_OK). The behavior is different from BIND 9, // so we test this case explicitly. - addZoneData(rr_child_ns_); + addToZoneData(rr_child_ns_); ConstRRsetPtr rr_cname_under_cut_ = textToRRset( "cname.child.example.org. 300 IN CNAME target.child.example.org."); - addZoneData(rr_cname_under_cut_); + addToZoneData(rr_cname_under_cut_); findTest(Name("cname.child.example.org"), RRType::AAAA(), ZoneFinder::CNAME, true, rr_cname_under_cut_, ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK); @@ -564,7 +448,7 @@ TEST_F(InMemoryZoneFinderTest, findCNAMEUnderZoneCut) { // Search under a DNAME record. It should return the DNAME TEST_F(InMemoryZoneFinderTest, findBelowDNAME) { - EXPECT_NO_THROW(addZoneData(rr_dname_)); + EXPECT_NO_THROW(addToZoneData(rr_dname_)); findTest(Name("below.dname.example.org"), RRType::A(), ZoneFinder::DNAME, true, rr_dname_); } @@ -572,8 +456,8 @@ TEST_F(InMemoryZoneFinderTest, findBelowDNAME) { // Search at the domain with DNAME. It should act as DNAME isn't there, DNAME // influences only the data below (see RFC 2672, section 3) TEST_F(InMemoryZoneFinderTest, findAtDNAME) { - EXPECT_NO_THROW(addZoneData(rr_dname_)); - EXPECT_NO_THROW(addZoneData(rr_dname_a_)); + EXPECT_NO_THROW(addToZoneData(rr_dname_)); + EXPECT_NO_THROW(addToZoneData(rr_dname_a_)); const Name dname_name(rr_dname_->getName()); findTest(dname_name, RRType::A(), ZoneFinder::SUCCESS, true, rr_dname_a_); @@ -585,8 +469,8 @@ TEST_F(InMemoryZoneFinderTest, findAtDNAME) { // Try searching something that is both under NS and DNAME, without and with // GLUE_OK mode (it should stop at the NS and DNAME respectively). TEST_F(InMemoryZoneFinderTest, DNAMEUnderNS) { - addZoneData(rr_child_ns_); - addZoneData(rr_child_dname_); + addToZoneData(rr_child_ns_); + addToZoneData(rr_child_dname_); Name lowName("below.dname.child.example.org."); @@ -598,10 +482,10 @@ TEST_F(InMemoryZoneFinderTest, DNAMEUnderNS) { // Test adding child zones and zone cut handling TEST_F(InMemoryZoneFinderTest, delegationNS) { // add in-zone data - EXPECT_NO_THROW(addZoneData(rr_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_ns_)); // install a zone cut - EXPECT_NO_THROW(addZoneData(rr_child_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_child_ns_)); // below the zone cut findTest(Name("www.child.example.org"), RRType::A(), @@ -618,7 +502,7 @@ TEST_F(InMemoryZoneFinderTest, delegationNS) { findTest(origin_, RRType::NS(), ZoneFinder::SUCCESS, true, rr_ns_); // unusual case of "nested delegation": the highest cut should be used. - EXPECT_NO_THROW(addZoneData(rr_grandchild_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_grandchild_ns_)); findTest(Name("www.grand.child.example.org"), RRType::A(), // note: !rr_grandchild_ns_ ZoneFinder::DELEGATION, true, rr_child_ns_); @@ -627,9 +511,9 @@ TEST_F(InMemoryZoneFinderTest, delegationNS) { TEST_F(InMemoryZoneFinderTest, delegationWithDS) { // Similar setup to the previous one, but with DS RR at the delegation // point. - addZoneData(rr_ns_); - addZoneData(rr_child_ns_); - addZoneData(rr_child_ds_); + addToZoneData(rr_ns_); + addToZoneData(rr_child_ns_); + addToZoneData(rr_child_ds_); // Normal types of query should result in delegation, but DS query // should be considered in-zone (but only exactly at the delegation point). @@ -647,9 +531,9 @@ TEST_F(InMemoryZoneFinderTest, delegationWithDS) { } TEST_F(InMemoryZoneFinderTest, findAny) { - EXPECT_NO_THROW(addZoneData(rr_a_)); - EXPECT_NO_THROW(addZoneData(rr_ns_)); - EXPECT_NO_THROW(addZoneData(rr_child_glue_)); + EXPECT_NO_THROW(addToZoneData(rr_a_)); + EXPECT_NO_THROW(addToZoneData(rr_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_child_glue_)); vector expected_sets; @@ -668,7 +552,7 @@ TEST_F(InMemoryZoneFinderTest, findAny) { findAllTest(rr_child_glue_->getName(), ZoneFinder::SUCCESS, expected_sets); // add zone cut - EXPECT_NO_THROW(addZoneData(rr_child_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_child_ns_)); // zone cut findAllTest(rr_child_ns_->getName(), ZoneFinder::DELEGATION, @@ -684,16 +568,16 @@ TEST_F(InMemoryZoneFinderTest, findAny) { TEST_F(InMemoryZoneFinderTest, glue) { // install zone data: // a zone cut - EXPECT_NO_THROW(addZoneData(rr_child_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_child_ns_)); // glue for this cut - EXPECT_NO_THROW(addZoneData(rr_child_glue_)); + EXPECT_NO_THROW(addToZoneData(rr_child_glue_)); // a nested zone cut (unusual) - EXPECT_NO_THROW(addZoneData(rr_grandchild_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_grandchild_ns_)); // glue under the deeper zone cut - EXPECT_NO_THROW(addZoneData(rr_grandchild_glue_)); + EXPECT_NO_THROW(addToZoneData(rr_grandchild_glue_)); // glue 'at the' zone cut - EXPECT_NO_THROW(addZoneData(rr_ns_a_)); - EXPECT_NO_THROW(addZoneData(rr_ns_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_ns_a_)); + EXPECT_NO_THROW(addToZoneData(rr_ns_ns_)); // by default glue is hidden due to the zone cut findTest(rr_child_glue_->getName(), RRType::A(), ZoneFinder::DELEGATION, @@ -749,16 +633,16 @@ InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags, rr_a_->addRRsig(createRdata(RRType::RRSIG(), RRClass::IN(), "A 5 3 3600 20120814220826 20120715220826 " "1234 example.com. FAKE")); - EXPECT_NO_THROW(addZoneData(rr_ns_)); - EXPECT_NO_THROW(addZoneData(rr_ns_a_)); - EXPECT_NO_THROW(addZoneData(rr_ns_aaaa_)); - EXPECT_NO_THROW(addZoneData(rr_a_)); + EXPECT_NO_THROW(addToZoneData(rr_ns_)); + EXPECT_NO_THROW(addToZoneData(rr_ns_a_)); + EXPECT_NO_THROW(addToZoneData(rr_ns_aaaa_)); + EXPECT_NO_THROW(addToZoneData(rr_a_)); if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) { - addZoneData(rr_nsec3_); + addToZoneData(rr_nsec3_); zone_data_->setSigned(true); } if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) { - addZoneData(rr_nsec_); + addToZoneData(rr_nsec_); zone_data_->setSigned(true); } @@ -805,7 +689,7 @@ InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags, expected_nsec, expected_flags, NULL, find_options); if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) { - addZoneData(rr_ns_nsec_); + addToZoneData(rr_ns_nsec_); zone_data_->setSigned(true); if ((find_options & ZoneFinder::FIND_DNSSEC) != 0) { expected_nsec = rr_ns_nsec_; @@ -841,8 +725,8 @@ InMemoryZoneFinderTest::findNSECENTCheck(const Name& ent_name, ConstRRsetPtr expected_nsec, ZoneFinder::FindResultFlags expected_flags) { - addZoneData(rr_emptywild_); - addZoneData(rr_under_wild_); + addToZoneData(rr_emptywild_); + addToZoneData(rr_under_wild_); // Sanity check: Should result in NXRRSET findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true, @@ -854,10 +738,10 @@ InMemoryZoneFinderTest::findNSECENTCheck(const Name& ent_name, // Now add the NSEC rrs making it a 'complete' zone (in terms of NSEC, // there are no sigs) - addZoneData(rr_nsec_); - addZoneData(rr_ent_nsec2_); - addZoneData(rr_ent_nsec3_); - addZoneData(rr_ent_nsec4_); + addToZoneData(rr_nsec_); + addToZoneData(rr_ent_nsec2_); + addToZoneData(rr_ent_nsec3_); + addToZoneData(rr_ent_nsec4_); zone_data_->setSigned(true); // Should result in NXRRSET, and RESULT_NSEC_SIGNED @@ -915,14 +799,14 @@ InMemoryZoneFinderTest::emptyNodeCheck( for (int i = 0; names[i] != NULL; ++i) { ConstRRsetPtr rrset = textToRRset(string(names[i]) + " 300 IN A 192.0.2.1"); - addZoneData(rrset); + addToZoneData(rrset); } if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) { - addZoneData(rr_nsec3_); + addToZoneData(rr_nsec3_); zone_data_->setSigned(true); } if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) { - addZoneData(rr_nsec_); + addToZoneData(rr_nsec_); zone_data_->setSigned(true); } @@ -989,15 +873,15 @@ InMemoryZoneFinderTest::wildcardCheck( "RRSIG CNAME " + string(rrsig_common))); } - addZoneData(rr_wild_); - addZoneData(rr_cnamewild_); + addToZoneData(rr_wild_); + addToZoneData(rr_cnamewild_); // If the zone is expected to be "signed" with NSEC3, add an NSEC3. // (the content of the NSEC3 shouldn't matter) if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) { - addZoneData(rr_nsec3_); + addToZoneData(rr_nsec3_); } if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) { - addZoneData(rr_nsec_); + addToZoneData(rr_nsec_); } // Search at the parent. The parent will not have the A, but it will @@ -1073,7 +957,7 @@ InMemoryZoneFinderTest::wildcardCheck( wild_expected_flags, NULL, find_options, wild_ok); } - addZoneData(rr_under_wild_); + addToZoneData(rr_under_wild_); { SCOPED_TRACE("Search under non-wildcard"); findTest(Name("bar.foo.wild.example.org"), RRType::A(), @@ -1090,7 +974,7 @@ InMemoryZoneFinderTest::wildcardCheck( // NO_WILDCARD effect itself can be checked by the result code (NXDOMAIN). ConstRRsetPtr expected_wild_nsec; // by default it's NULL if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) { - addZoneData(rr_wild_nsec_); + addToZoneData(rr_wild_nsec_); expected_wild_nsec = rr_wild_nsec_; } { @@ -1127,8 +1011,8 @@ TEST_F(InMemoryZoneFinderTest, wildcardDisabledWithoutNSEC) { * the wildcard defaults." */ TEST_F(InMemoryZoneFinderTest, delegatedWildcard) { - addZoneData(rr_child_wild_); - addZoneData(rr_child_ns_); + addToZoneData(rr_child_wild_); + addToZoneData(rr_child_ns_); { SCOPED_TRACE("Looking under delegation point"); @@ -1149,12 +1033,12 @@ void InMemoryZoneFinderTest::anyWildcardCheck( ZoneFinder::FindResultFlags expected_flags) { - addZoneData(rr_wild_); + addToZoneData(rr_wild_); if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) { - addZoneData(rr_nsec3_); + addToZoneData(rr_nsec3_); } if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) { - addZoneData(rr_nsec_); + addToZoneData(rr_nsec_); } vector expected_sets; @@ -1206,12 +1090,12 @@ InMemoryZoneFinderTest::emptyWildcardCheck( * * * wild */ - addZoneData(rr_emptywild_); + addToZoneData(rr_emptywild_); if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) { - addZoneData(rr_nsec3_); + addToZoneData(rr_nsec3_); } if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) { - addZoneData(rr_nsec_); + addToZoneData(rr_nsec_); } { @@ -1263,7 +1147,7 @@ TEST_F(InMemoryZoneFinderTest, emptyWildcardNSEC) { // Same as emptyWildcard, but with multiple * in the path. TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) { - addZoneData(rr_nested_emptywild_); + addToZoneData(rr_nested_emptywild_); { SCOPED_TRACE("Asking for the original record under wildcards"); @@ -1394,8 +1278,8 @@ InMemoryZoneFinderTest::doCancelWildcardCheck( * shouldn't be canceled isn't. */ TEST_F(InMemoryZoneFinderTest, cancelWildcard) { - addZoneData(rr_wild_); - addZoneData(rr_not_wild_); + addToZoneData(rr_wild_); + addToZoneData(rr_not_wild_); { SCOPED_TRACE("Runnig with single entry under foo.wild.example.org"); @@ -1405,7 +1289,7 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcard) { // Try putting another one under foo.wild.... // The result should be the same but it will be done in another way in the // code, because the foo.wild.example.org will exist in the tree. - addZoneData(rr_not_wild_another_); + addToZoneData(rr_not_wild_another_); { SCOPED_TRACE("Runnig with two entries under foo.wild.example.org"); doCancelWildcardCheck(); @@ -1414,15 +1298,15 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcard) { // Same tests as cancelWildcard for NSEC3-signed zone TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC3) { - addZoneData(rr_wild_); - addZoneData(rr_not_wild_); - addZoneData(rr_nsec3_); + addToZoneData(rr_wild_); + addToZoneData(rr_not_wild_); + addToZoneData(rr_nsec3_); { SCOPED_TRACE("Runnig with single entry under foo.wild.example.org"); doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED); } - addZoneData(rr_not_wild_another_); + addToZoneData(rr_not_wild_another_); { SCOPED_TRACE("Runnig with two entries under foo.wild.example.org"); doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED); @@ -1433,9 +1317,9 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC3) { // or without FIND_DNSSEC option. NSEC should be returned only when the option // is given. TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC) { - addZoneData(rr_wild_); - addZoneData(rr_not_wild_); - addZoneData(rr_nsec_); + addToZoneData(rr_wild_); + addToZoneData(rr_not_wild_); + addToZoneData(rr_nsec_); { SCOPED_TRACE("Runnig with single entry under foo.wild.example.org"); @@ -1443,7 +1327,7 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC) { ZoneFinder::FIND_DNSSEC); doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED); } - addZoneData(rr_not_wild_another_); + addToZoneData(rr_not_wild_another_); { SCOPED_TRACE("Runnig with two entries under foo.wild.example.org"); doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED, @@ -1464,7 +1348,7 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) { DataSourceError); // Only having NSEC3PARAM isn't enough - addZoneData(textToRRset("example.org. 300 IN NSEC3PARAM " + addToZoneData(textToRRset("example.org. 300 IN NSEC3PARAM " "1 0 12 aabbccdd")); EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true), DataSourceError); @@ -1473,11 +1357,100 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) { // is guaranteed. const string ns1_nsec3_text = string(ns1_hash) + ".example.org." + string(nsec3_common); - addZoneData(textToRRset(ns1_nsec3_text)); + addToZoneData(textToRRset(ns1_nsec3_text)); EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true), DataSourceError); } +TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) { + // Make the zone "NSEC signed" + addToZoneData(rr_nsec_); + const ZoneFinder::FindResultFlags expected_flags = + ZoneFinder::RESULT_NSEC_SIGNED; + + // Add A for ns.example.org, and RRSIG-only covering TXT for the same name. + // query for the TXT should result in NXRRSET. + addToZoneData(rr_ns_a_); + updater_.add(ConstRRsetPtr(), + textToRRset( + "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 " + "20120715220826 1234 example.com. FAKE")); + findTest(Name("ns.example.org"), RRType::TXT(), + ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags); + + // Add RRSIG-only covering NSEC. This shouldn't be returned when NSEC is + // requested, whether it's for NXRRSET or NXDOMAIN + updater_.add(ConstRRsetPtr(), + textToRRset( + "ns.example.org. 300 IN RRSIG NSEC 5 3 300 " + "20120814220826 20120715220826 1234 example.com. FAKE")); + // The added RRSIG for NSEC could be used for NXRRSET but shouldn't + findTest(Name("ns.example.org"), RRType::TXT(), + ZoneFinder::NXRRSET, true, ConstRRsetPtr(), + expected_flags, NULL, ZoneFinder::FIND_DNSSEC); + // The added RRSIG for NSEC could be used for NXDOMAIN but shouldn't + findTest(Name("nz.example.org"), RRType::A(), + ZoneFinder::NXDOMAIN, true, rr_nsec_, + expected_flags, NULL, ZoneFinder::FIND_DNSSEC); + + // RRSIG-only CNAME shouldn't be accidentally confused with real CNAME. + updater_.add(ConstRRsetPtr(), + textToRRset( + "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 " + "20120814220826 20120715220826 1234 example.com. FAKE")); + findTest(Name("nocname.example.org"), RRType::A(), + ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags); + + // RRSIG-only for NS wouldn't invoke delegation anyway, but we check this + // case explicitly. + updater_.add(ConstRRsetPtr(), + textToRRset( + "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 " + "20120814220826 20120715220826 1234 example.com. FAKE")); + findTest(Name("nodelegation.example.org"), RRType::A(), + ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags); + findTest(Name("www.nodelegation.example.org"), RRType::A(), + ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags); + + // Same for RRSIG-only for DNAME + updater_.add(ConstRRsetPtr(), + textToRRset( + "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 " + "20120814220826 20120715220826 1234 example.com. FAKE")); + findTest(Name("www.nodname.example.org"), RRType::A(), + ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags); + // If we have a delegation NS at this node, it will be a bit trickier, + // because the zonecut processing actually takes place at the node. + // But the RRSIG-only for DNAME shouldn't confuse the process and the NS + // should win. + ConstRRsetPtr ns_rrset = + textToRRset("nodname.example.org. 300 IN NS ns.nodname.example.org."); + addToZoneData(ns_rrset); + findTest(Name("www.nodname.example.org"), RRType::A(), + ZoneFinder::DELEGATION, true, ns_rrset); +} + +// \brief testcase for #2504 (Problem in inmem NSEC denial of existence +// handling) +TEST_F(InMemoryZoneFinderTest, NSECNonExistentTest) { + shared_ptr ztable_segment( + new ZoneTableSegmentTest(class_, mem_sgmt_)); + InMemoryClient client(ztable_segment, class_); + Name name("example.com."); + + client.load(name, TEST_DATA_DIR "/2504-test.zone"); + DataSourceClient::FindResult result(client.findZone(name)); + + // Check for a non-existing name + Name search_name("nonexist.example.com."); + ZoneFinderContextPtr find_result( + result.zone_finder->find(search_name, + RRType::A(), ZoneFinder::FIND_DNSSEC)); + // We don't find the domain, but find() must complete (not throw or + // assert). + EXPECT_EQ(ZoneFinder::NXDOMAIN, find_result->code); +} + /// \brief NSEC3 specific tests fixture for the InMemoryZoneFinder class class InMemoryZoneFinderNSEC3Test : public InMemoryZoneFinderTest { public: @@ -1492,16 +1465,16 @@ public: // zzz.example.org: hash=R5.. const string apex_nsec3_text = string(apex_hash) + ".example.org." + string(nsec3_common); - addZoneData(textToRRset(apex_nsec3_text)); + addToZoneData(textToRRset(apex_nsec3_text)); const string ns1_nsec3_text = string(ns1_hash) + ".example.org." + string(nsec3_common); - addZoneData(textToRRset(ns1_nsec3_text)); + addToZoneData(textToRRset(ns1_nsec3_text)); const string w_nsec3_text = string(w_hash) + ".example.org." + string(nsec3_common); - addZoneData(textToRRset(w_nsec3_text)); + addToZoneData(textToRRset(w_nsec3_text)); const string zzz_nsec3_text = string(zzz_hash) + ".example.org." + string(nsec3_common); - addZoneData(textToRRset(zzz_nsec3_text)); + addToZoneData(textToRRset(zzz_nsec3_text)); } private: @@ -1601,4 +1574,42 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3Walk) { } } } + +TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) { + // add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it + // should result in an exception. + const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"; + updater_.add(ConstRRsetPtr(), + textToRRset( + n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 " + "20120814220826 20120715220826 1234 example.com. FAKE")); + EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false), + DataSourceError); +} + +// \brief testcase for #2503 (Problem in inmem NSEC3 denial of existence +// handling) +TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) { + // Set back the default hash calculator. + DefaultNSEC3HashCreator creator; + setNSEC3HashCreator(&creator); + + shared_ptr ztable_segment( + new ZoneTableSegmentTest(class_, mem_sgmt_)); + InMemoryClient client(ztable_segment, class_); + Name name("example.com."); + + client.load(name, TEST_DATA_DIR "/2503-test.zone"); + DataSourceClient::FindResult result(client.findZone(name)); + + // Check for a non-existing name + Name search_name("nonexist.example.com."); + ZoneFinder::FindNSEC3Result find_result( + result.zone_finder->findNSEC3(search_name, true)); + // findNSEC3() must have completed (not throw or assert). Because + // the find was recursive, it always must find something and return + // true. + EXPECT_TRUE(find_result.matched); +} + } diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_test.h b/src/lib/datasrc/tests/memory/zone_table_segment_test.h new file mode 100644 index 0000000000..2078036376 --- /dev/null +++ b/src/lib/datasrc/tests/memory/zone_table_segment_test.h @@ -0,0 +1,116 @@ +// 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_MEMORY_ZONE_TABLE_SEGMENT_TEST_H +#define DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H 1 + +#include +#include +#include +#include + +namespace isc { +namespace datasrc { +namespace memory { +namespace test { + +// A special ZoneTableSegment that can be used for tests. It can be +// passed a MemorySegment that can be used later to test if all memory +// was de-allocated on it. +class ZoneTableSegmentTest : public ZoneTableSegment { +public: + ZoneTableSegmentTest(isc::dns::RRClass rrclass, + isc::util::MemorySegment& mem_sgmt) : + ZoneTableSegment(rrclass), + mem_sgmt_(mem_sgmt), + header_(ZoneTable::create(mem_sgmt_, rrclass)) + {} + + virtual ~ZoneTableSegmentTest() { + ZoneTable::destroy(mem_sgmt_, header_.getTable()); + } + + virtual ZoneTableHeader& getHeader() { + return (header_); + } + + virtual const ZoneTableHeader& getHeader() const { + return (header_); + } + + virtual isc::util::MemorySegment& getMemorySegment() { + return (mem_sgmt_); + } + + virtual ZoneWriter* getZoneWriter(const LoadAction& load_action, + const dns::Name& name, + const dns::RRClass& rrclass) + { + return (new Writer(this, load_action, name, rrclass)); + } + +private: + isc::util::MemorySegment& mem_sgmt_; + ZoneTableHeader header_; + + // A writer for this segment. The implementation is similar + // to ZoneWriterLocal, but all the error handling is stripped + // for simplicity. Also, we do everything inside the + // install(), for the same reason. We just need something + // inside the tests, not a full-blown implementation + // for background loading. + class Writer : public ZoneWriter { + public: + Writer(ZoneTableSegmentTest* segment, const LoadAction& load_action, + const dns::Name& name, const dns::RRClass& rrclass) : + segment_(segment), + load_action_(load_action), + name_(name), + rrclass_(rrclass) + {} + + void load() {} + + void install() { + ZoneTable* table(segment_->getHeader().getTable()); + const ZoneTable::AddResult + result(table->addZone(segment_->getMemorySegment(), rrclass_, + name_, + load_action_(segment_-> + getMemorySegment()))); + if (result.zone_data != NULL) { + ZoneData::destroy(segment_->getMemorySegment(), + result.zone_data, rrclass_); + } + } + + virtual void cleanup() {} + private: + ZoneTableSegmentTest* segment_; + LoadAction load_action_; + dns::Name name_; + dns::RRClass rrclass_; + }; +}; + +} // namespace test +} // namespace memory +} // namespace datasrc +} // namespace isc + +#endif // DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc index 6bdd7379af..ac114e282c 100644 --- a/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc +++ b/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc @@ -12,72 +12,86 @@ // 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 + +using namespace isc::dns; using namespace isc::datasrc::memory; using namespace isc::data; using namespace isc::util; using namespace std; +using boost::scoped_ptr; namespace { class ZoneTableSegmentTest : public ::testing::Test { protected: ZoneTableSegmentTest() : - config_(Element::fromJSON("{}")), - segment_(ZoneTableSegment::create((*config_.get()))) + ztable_segment_(ZoneTableSegment::create(isc::data::NullElement(), + RRClass::IN())) {} - ~ZoneTableSegmentTest() { - if (segment_ != NULL) { - ZoneTableSegment::destroy(segment_); - } - } - void TearDown() { - // Catch any future leaks here. - const MemorySegment& mem_sgmt = segment_->getMemorySegment(); - EXPECT_TRUE(mem_sgmt.allMemoryDeallocated()); - - ZoneTableSegment::destroy(segment_); - segment_ = NULL; + ZoneTableSegment::destroy(ztable_segment_); + ztable_segment_ = NULL; } - const ElementPtr config_; - ZoneTableSegment* segment_; + ZoneTableSegment* ztable_segment_; }; TEST_F(ZoneTableSegmentTest, create) { // By default, a local zone table segment is created. - EXPECT_NE(static_cast(NULL), segment_); + EXPECT_NE(static_cast(NULL), ztable_segment_); } // Helper function to check const and non-const methods. template void -testGetHeader(ZoneTableSegment* segment) { - TH& header = static_cast(segment)->getHeader(); +testGetHeader(ZoneTableSegment* ztable_segment) { + TH& header = static_cast(ztable_segment)->getHeader(); - // The zone table is unset. + // The zone table must be set. TT* table = header.getTable(); - EXPECT_EQ(static_cast(NULL), table); + EXPECT_NE(static_cast(NULL), table); } TEST_F(ZoneTableSegmentTest, getHeader) { // non-const version. - testGetHeader(segment_); + testGetHeader + (ztable_segment_); // const version. testGetHeader(segment_); + const ZoneTable>(ztable_segment_); } TEST_F(ZoneTableSegmentTest, getMemorySegment) { // This doesn't do anything fun except test the API. - MemorySegment& mem_sgmt = segment_->getMemorySegment(); - EXPECT_TRUE(mem_sgmt.allMemoryDeallocated()); + MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment(); + mem_sgmt.allMemoryDeallocated(); // use mem_sgmt +} + +ZoneData* +loadAction(MemorySegment&) { + // The function won't be called, so this is OK + return (NULL); +} + +// Test we can get a writer. +TEST_F(ZoneTableSegmentTest, getZoneWriter) { + scoped_ptr + writer(ztable_segment_->getZoneWriter(loadAction, Name("example.org"), + RRClass::IN())); + // We have to get something + EXPECT_NE(static_cast(NULL), writer.get()); + // And for now, it should be the local writer + EXPECT_NE(static_cast(NULL), + dynamic_cast(writer.get())); } } // anonymous namespace diff --git a/src/lib/datasrc/tests/memory/zone_table_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_unittest.cc index 401d43431b..3c53a59f9c 100644 --- a/src/lib/datasrc/tests/memory/zone_table_unittest.cc +++ b/src/lib/datasrc/tests/memory/zone_table_unittest.cc @@ -12,6 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +#include "memory_segment_test.h" + #include #include @@ -34,27 +36,6 @@ using namespace isc::datasrc::memory; using namespace isc::datasrc::memory::detail; namespace { -// Memory segment specified for tests. It normally behaves like a "local" -// memory segment. If "throw count" is set to non 0 via setThrowCount(), -// it continues the normal behavior up to the specified number of calls to -// allocate(), and throws an exception at the next call. -class TestMemorySegment : public isc::util::MemorySegmentLocal { -public: - TestMemorySegment() : throw_count_(0) {} - virtual void* allocate(size_t size) { - if (throw_count_ > 0) { - if (--throw_count_ == 0) { - throw std::bad_alloc(); - } - } - return (isc::util::MemorySegmentLocal::allocate(size)); - } - void setThrowCount(size_t count) { throw_count_ = count; } - -private: - size_t throw_count_; -}; - class ZoneTableTest : public ::testing::Test { protected: ZoneTableTest() : zclass_(RRClass::IN()), @@ -65,17 +46,17 @@ protected: {} ~ZoneTableTest() { if (zone_table != NULL) { - ZoneTable::destroy(mem_sgmt_, zone_table, zclass_); + ZoneTable::destroy(mem_sgmt_, zone_table); } } void TearDown() { - ZoneTable::destroy(mem_sgmt_, zone_table, zclass_); + ZoneTable::destroy(mem_sgmt_, zone_table); zone_table = NULL; EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here. } const RRClass zclass_; const Name zname1, zname2, zname3; - TestMemorySegment mem_sgmt_; + test::MemorySegmentTest mem_sgmt_; ZoneTable* zone_table; }; diff --git a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc new file mode 100644 index 0000000000..13bcc3b469 --- /dev/null +++ b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc @@ -0,0 +1,240 @@ +// 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 + +using boost::scoped_ptr; +using boost::bind; +using isc::dns::RRClass; +using isc::dns::Name; +using namespace isc::datasrc::memory; + +namespace { + +class TestException {}; + +class ZoneWriterLocalTest : public ::testing::Test { +public: + ZoneWriterLocalTest() : + // FIXME: The NullElement probably isn't the best one, but we don't + // know how the config will look, so it just fills the argument + // (which is currently ignored) + segment_(ZoneTableSegment::create(isc::data::NullElement(), + RRClass::IN())), + writer_(new + ZoneWriterLocal(dynamic_cast(segment_. + get()), + bind(&ZoneWriterLocalTest::loadAction, this, _1), + Name("example.org"), RRClass::IN())), + load_called_(false), + load_throw_(false), + load_null_(false), + load_data_(false) + {} + void TearDown() { + // Release the writer + writer_.reset(); + } +protected: + scoped_ptr segment_; + scoped_ptr writer_; + bool load_called_; + bool load_throw_; + bool load_null_; + bool load_data_; +private: + ZoneData* loadAction(isc::util::MemorySegment& segment) { + // Make sure it is the correct segment passed. We know the + // exact instance, can compare pointers to them. + EXPECT_EQ(&segment_->getMemorySegment(), &segment); + // We got called + load_called_ = true; + if (load_throw_) { + throw TestException(); + } + + if (load_null_) { + // Be nasty to the caller and return NULL, which is forbidden + return (NULL); + } + ZoneData* data = ZoneData::create(segment, Name("example.org")); + if (load_data_) { + // Put something inside. The node itself should be enough for + // the tests. + ZoneNode* node(NULL); + data->insertName(segment, Name("subdomain.example.org"), &node); + EXPECT_NE(static_cast(NULL), node); + } + return (data); + } +}; + +// We call it the way we are supposed to, check every callback is called in the +// right moment. +TEST_F(ZoneWriterLocalTest, correctCall) { + // Nothing called before we call it + EXPECT_FALSE(load_called_); + + // Just the load gets called now + EXPECT_NO_THROW(writer_->load()); + EXPECT_TRUE(load_called_); + load_called_ = false; + + EXPECT_NO_THROW(writer_->install()); + EXPECT_FALSE(load_called_); + + // We don't check explicitly how this works, but call it to free memory. If + // everything is freed should be checked inside the TearDown. + EXPECT_NO_THROW(writer_->cleanup()); +} + +TEST_F(ZoneWriterLocalTest, loadTwice) { + // Load it the first time + EXPECT_NO_THROW(writer_->load()); + EXPECT_TRUE(load_called_); + load_called_ = false; + + // The second time, it should not be possible + EXPECT_THROW(writer_->load(), isc::InvalidOperation); + EXPECT_FALSE(load_called_); + + // The object should not be damaged, try installing and clearing now + EXPECT_NO_THROW(writer_->install()); + EXPECT_FALSE(load_called_); + + // We don't check explicitly how this works, but call it to free memory. If + // everything is freed should be checked inside the TearDown. + EXPECT_NO_THROW(writer_->cleanup()); +} + +// Try loading after call to install and call to cleanup. Both is +// forbidden. +TEST_F(ZoneWriterLocalTest, loadLater) { + // Load first, so we can install + EXPECT_NO_THROW(writer_->load()); + EXPECT_NO_THROW(writer_->install()); + // Reset so we see nothing is called now + load_called_ = false; + + EXPECT_THROW(writer_->load(), isc::InvalidOperation); + EXPECT_FALSE(load_called_); + + // Cleanup and try loading again. Still shouldn't work. + EXPECT_NO_THROW(writer_->cleanup()); + + EXPECT_THROW(writer_->load(), isc::InvalidOperation); + EXPECT_FALSE(load_called_); +} + +// Try calling install at various bad times +TEST_F(ZoneWriterLocalTest, invalidInstall) { + // Nothing loaded yet + EXPECT_THROW(writer_->install(), isc::InvalidOperation); + EXPECT_FALSE(load_called_); + + EXPECT_NO_THROW(writer_->load()); + load_called_ = false; + // This install is OK + EXPECT_NO_THROW(writer_->install()); + // But we can't call it second time now + EXPECT_THROW(writer_->install(), isc::InvalidOperation); + EXPECT_FALSE(load_called_); +} + +// We check we can clean without installing first and nothing bad +// happens. We also misuse the testcase to check we can't install +// after cleanup. +TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) { + EXPECT_NO_THROW(writer_->load()); + EXPECT_NO_THROW(writer_->cleanup()); + + EXPECT_TRUE(load_called_); + + // We cleaned up, no way to install now + EXPECT_THROW(writer_->install(), isc::InvalidOperation); +} + +// Test the case when load callback throws +TEST_F(ZoneWriterLocalTest, loadThrows) { + load_throw_ = true; + EXPECT_THROW(writer_->load(), TestException); + + // We can't install now + EXPECT_THROW(writer_->install(), isc::InvalidOperation); + EXPECT_TRUE(load_called_); + + // But we can cleanup + EXPECT_NO_THROW(writer_->cleanup()); +} + +// Check the strong exception guarantee - if it throws, nothing happened +// to the content. +TEST_F(ZoneWriterLocalTest, retry) { + // First attempt fails due to some exception. + load_throw_ = true; + EXPECT_THROW(writer_->load(), TestException); + // This one shall succeed. + load_called_ = load_throw_ = false; + // We want some data inside. + load_data_ = true; + EXPECT_NO_THROW(writer_->load()); + // And this one will fail again. But the old data will survive. + load_data_ = false; + EXPECT_THROW(writer_->load(), isc::InvalidOperation); + + // The rest still works correctly + EXPECT_NO_THROW(writer_->install()); + ZoneTable* const table(segment_->getHeader().getTable()); + const ZoneTable::FindResult found(table->findZone(Name("example.org"))); + ASSERT_EQ(isc::datasrc::result::SUCCESS, found.code); + // For some reason it doesn't seem to work by the ZoneNode typedef, using + // the full definition instead. At least on some compilers. + const isc::datasrc::memory::DomainTreeNode* node; + EXPECT_EQ(isc::datasrc::memory::DomainTree::EXACTMATCH, + found.zone_data->getZoneTree(). + find(Name("subdomain.example.org"), &node)); + EXPECT_NO_THROW(writer_->cleanup()); +} + +// Check the writer defends itsefl when load action returns NULL +TEST_F(ZoneWriterLocalTest, loadNull) { + load_null_ = true; + EXPECT_THROW(writer_->load(), isc::InvalidOperation); + + // We can't install that + EXPECT_THROW(writer_->install(), isc::InvalidOperation); + + // It should be possible to clean up safely + EXPECT_NO_THROW(writer_->cleanup()); +} + +// Check the object cleans up in case we forget it. +TEST_F(ZoneWriterLocalTest, autoCleanUp) { + // Load data and forget about it. It should get released + // when the writer itself is destroyed. + EXPECT_NO_THROW(writer_->load()); +} + +} diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc index 5223d83a1d..85be310f20 100644 --- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc +++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc @@ -1219,7 +1219,7 @@ TEST_F(InMemoryZoneFinderTest, loadFromIterator) { // purpose of this test, so it should just succeed. db_client = unittest::createSQLite3Client( class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied", - TEST_DATA_DIR "/contexttest.zone"); + TEST_DATA_DIR "/contexttest-almost-obsolete.zone"); zone_finder_.load(*db_client->getIterator(origin_)); // just checking a couple of RRs in the new version of zone. diff --git a/src/lib/datasrc/tests/test_client.h b/src/lib/datasrc/tests/test_client.h index 2c692d3745..1e35cd39a8 100644 --- a/src/lib/datasrc/tests/test_client.h +++ b/src/lib/datasrc/tests/test_client.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __TEST_DATA_SOURCE_CLIENT_H -#define __TEST_DATA_SOURCE_CLIENT_H 1 +#ifndef TEST_DATA_SOURCE_CLIENT_H +#define TEST_DATA_SOURCE_CLIENT_H 1 #include #include @@ -64,7 +64,7 @@ createSQLite3Client(dns::RRClass zclass, const dns::Name& zname, } // end of datasrc } // end of isc -#endif // __TEST_DATA_SOURCE_CLIENT_H +#endif // TEST_DATA_SOURCE_CLIENT_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone b/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone new file mode 100644 index 0000000000..f65f8c1efb --- /dev/null +++ b/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone @@ -0,0 +1,85 @@ +;; test zone file used for ZoneFinderContext tests. +;; RRSIGs are (obviouslly) faked ones for testing. + +example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600 +example.org. 3600 IN NS ns1.example.org. +example.org. 3600 IN NS ns2.example.org. +example.org. 3600 IN RRSIG NS 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE +example.org. 3600 IN MX 1 mx1.example.org. +example.org. 3600 IN MX 2 mx2.example.org. +example.org. 3600 IN MX 3 mx.a.example.org. + +ns1.example.org. 3600 IN A 192.0.2.1 +ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE +ns1.example.org. 3600 IN AAAA 2001:db8::1 +ns1.example.org. 3600 IN RRSIG AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE +ns2.example.org. 3600 IN A 192.0.2.2 +ns2.example.org. 3600 IN TXT "text data" + +mx1.example.org. 3600 IN A 192.0.2.10 +mx2.example.org. 3600 IN AAAA 2001:db8::10 + +;; delegation +a.example.org. 3600 IN NS ns1.a.example.org. +a.example.org. 3600 IN NS ns2.a.example.org. +a.example.org. 3600 IN NS ns.example.com. + +ns1.a.example.org. 3600 IN A 192.0.2.5 +ns2.a.example.org. 3600 IN A 192.0.2.6 +ns2.a.example.org. 3600 IN AAAA 2001:db8::6 +mx.a.example.org. 3600 IN A 192.0.2.7 + +;; delegation, one of its NS names is at zone cut. +b.example.org. 3600 IN NS ns.b.example.org. +b.example.org. 3600 IN NS b.example.org. +b.example.org. 3600 IN AAAA 2001:db8::8 + +ns.b.example.org. 3600 IN A 192.0.2.9 + +;; The MX name is at a zone cut. shouldn't be included in the +;; additional section. +mxatcut.example.org. 3600 IN MX 1 b.example.org. + +;; delegation, one of its NS names is under a DNAME delegation point; +;; another is at that point; and yet another is under DNAME below a +;; zone cut. +c.example.org. 3600 IN NS ns.dname.example.org. +c.example.org. 3600 IN NS dname.example.org. +c.example.org. 3600 IN NS ns.deepdname.example.org. +ns.dname.example.org. 3600 IN A 192.0.2.11 +dname.example.org. 3600 IN A 192.0.2.12 +ns.deepdname.example.org. 3600 IN AAAA 2001:db8::9 + +;; delegation, one of its NS name is at an empty non terminal. +d.example.org. 3600 IN NS ns.empty.example.org. +d.example.org. 3600 IN NS ns1.example.org. +;; by adding these two we can create an empty RB node for +;; ns.empty.example.org in the in-memory zone +foo.ns.empty.example.org. 3600 IN A 192.0.2.13 +bar.ns.empty.example.org. 3600 IN A 192.0.2.14 + +;; delegation; the NS name matches a wildcard (and there's no exact +;; match). One of the NS names matches an empty wildcard node, for +;; which no additional record should be provided (or any other +;; disruption should happen). +e.example.org. 3600 IN NS ns.wild.example.org. +e.example.org. 3600 IN NS ns.emptywild.example.org. +e.example.org. 3600 IN NS ns2.example.org. +*.wild.example.org. 3600 IN A 192.0.2.15 +a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2 + +;; additional for an answer RRset (MX) as a result of wildcard +;; expansion +*.wildmx.example.org. 3600 IN MX 1 mx1.example.org. + +;; the owner name of additional for an answer RRset (MX) has DNAME +dnamemx.example.org. 3600 IN MX 1 dname.example.org. + +;; CNAME +alias.example.org. 3600 IN CNAME cname.example.org. + +;; DNAME +dname.example.org. 3600 IN DNAME dname.example.com. + +;; DNAME under a NS (strange one) +deepdname.c.example.org. 3600 IN DNAME deepdname.example.com. diff --git a/src/lib/datasrc/tests/testdata/contexttest.zone b/src/lib/datasrc/tests/testdata/contexttest.zone index 0c1393cd56..ae028c4c73 100644 --- a/src/lib/datasrc/tests/testdata/contexttest.zone +++ b/src/lib/datasrc/tests/testdata/contexttest.zone @@ -68,6 +68,13 @@ e.example.org. 3600 IN NS ns2.example.org. *.wild.example.org. 3600 IN A 192.0.2.15 a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2 +;; One of the additional records actually only has RRSIG, which should +;; be ignored. +f.example.org. 3600 IN MX 5 mx1.f.example.org. +f.example.org. 3600 IN MX 10 mx2.f.example.org. +mx1.f.example.org. 3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE +mx2.f.example.org. 3600 IN A 192.0.2.16 + ;; additional for an answer RRset (MX) as a result of wildcard ;; expansion *.wildmx.example.org. 3600 IN MX 1 mx1.example.org. diff --git a/src/lib/datasrc/tests/testdata/example.edu-broken b/src/lib/datasrc/tests/testdata/example.edu-broken new file mode 100644 index 0000000000..dde11cf5aa --- /dev/null +++ b/src/lib/datasrc/tests/testdata/example.edu-broken @@ -0,0 +1,11 @@ +example.edu. 3600 IN SOA ns1.example.edu. admin.example.edu. 1234 3600 1800 2419200 7200 +example.edu. 3600 IN NS ns1.example.edu. +example.edu. 3600 IN NS ns2.example.edu. +example.edu. 3600 IN MX 10 mail.example.edu. +www.example.edu. 3600 IN A 192.0.2.1 +ns1.example.edu. 3600 IN A 192.0.2.3 +ns2.example.edu. 3600 IN A 192.0.2.4 + +;; DNAME + NS (non-apex) throws ZoneDataUpdater::AddError +ns1.example.edu. 3600 IN DNAME foo.example.edu. +ns1.example.edu. 3600 IN NS bar.example.edu. diff --git a/src/lib/datasrc/tests/testdata/example.net-empty b/src/lib/datasrc/tests/testdata/example.net-empty new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/lib/datasrc/tests/testdata/example.net-empty @@ -0,0 +1 @@ + diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc index 14429aea51..6844712c57 100644 --- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc +++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc @@ -14,14 +14,13 @@ #include -#include - #include #include #include #include #include +#include #include #include @@ -41,10 +40,12 @@ using namespace std; using boost::shared_ptr; +using namespace isc::data; using namespace isc::util; using namespace isc::dns; using namespace isc::datasrc; using isc::datasrc::memory::InMemoryClient; +using isc::datasrc::memory::ZoneTableSegment; using namespace isc::testutils; namespace { @@ -58,15 +59,17 @@ typedef shared_ptr DataSourceClientPtr; // This is the type used as the test parameter. Note that this is // intentionally a plain old type (i.e. a function pointer), not a class; // otherwise it could cause initialization fiasco at the instantiation time. -typedef DataSourceClientPtr (*ClientCreator)(MemorySegment&, RRClass, - const Name&); +typedef DataSourceClientPtr (*ClientCreator)(RRClass, const Name&); // Creator for the in-memory client to be tested DataSourceClientPtr -createInMemoryClient(MemorySegment& mem_sgmt, RRClass zclass, - const Name& zname) +createInMemoryClient(RRClass zclass, const Name& zname) { - shared_ptr client(new InMemoryClient(mem_sgmt, zclass)); + const ElementPtr config(Element::fromJSON("{}")); + shared_ptr ztable_segment( + ZoneTableSegment::create(*config, zclass)); + shared_ptr client(new InMemoryClient(ztable_segment, + zclass)); client->load(zname, TEST_ZONE_FILE); return (client); @@ -78,7 +81,7 @@ addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) { } DataSourceClientPtr -createSQLite3Client(MemorySegment&, RRClass zclass, const Name& zname) { +createSQLite3Client(RRClass zclass, const Name& zname) { // We always begin with an empty template SQLite3 DB file and install // the zone data from the zone file to ensure both cases have the // same test data. @@ -105,7 +108,7 @@ class ZoneFinderContextTest : { protected: ZoneFinderContextTest() : qclass_(RRClass::IN()), qzone_("example.org") { - client_ = (*GetParam())(mem_sgmt_, qclass_, qzone_); + client_ = (*GetParam())(qclass_, qzone_); REQUESTED_A.push_back(RRType::A()); REQUESTED_AAAA.push_back(RRType::AAAA()); REQUESTED_BOTH.push_back(RRType::A()); @@ -116,7 +119,6 @@ protected: ASSERT_TRUE(finder_); } - MemorySegmentLocal mem_sgmt_; const RRClass qclass_; const Name qzone_; DataSourceClientPtr client_; @@ -416,4 +418,16 @@ TEST_P(ZoneFinderContextTest, getAdditionalForAny) { result_sets_.begin(), result_sets_.end()); } +TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) { + // This has two MX records, but type-A for one of them only has RRSIG. + // It shouldn't be contained in the result. + ZoneFinderContextPtr ctx = finder_->find(Name("f.example.org"), + RRType::MX(), + ZoneFinder::FIND_DNSSEC); + EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code); + ctx->getAdditional(REQUESTED_BOTH, result_sets_); + rrsetsCheck("mx2.f.example.org. 3600 IN A 192.0.2.16\n", + result_sets_.begin(), result_sets_.end()); +} + } diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h index 36a1cffe4f..0d7438daa5 100644 --- a/src/lib/datasrc/zone.h +++ b/src/lib/datasrc/zone.h @@ -12,13 +12,14 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ZONE_H -#define __ZONE_H 1 +#ifndef ZONE_H +#define ZONE_H 1 #include #include #include +#include #include #include @@ -31,10 +32,10 @@ namespace datasrc { /// /// This is thrown when a method is called for a name or RRset which /// is not in or below the zone. -class OutOfZone : public Exception { +class OutOfZone : public ZoneException { public: OutOfZone(const char* file, size_t line, const char* what) : - isc::Exception(file, line, what) {} + ZoneException(file, line, what) {} }; /// \brief The base class to search a zone for RRsets @@ -1065,7 +1066,7 @@ typedef boost::shared_ptr ZoneJournalReaderPtr; } // end of datasrc } // end of isc -#endif // __ZONE_H +#endif // ZONE_H // Local Variables: // mode: c++ diff --git a/src/lib/datasrc/zonetable.h b/src/lib/datasrc/zonetable.h index 93a021c9e8..911391cd39 100644 --- a/src/lib/datasrc/zonetable.h +++ b/src/lib/datasrc/zonetable.h @@ -12,8 +12,8 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef __ZONETABLE_H -#define __ZONETABLE_H 1 +#ifndef ZONETABLE_H +#define ZONETABLE_H 1 #include @@ -153,7 +153,7 @@ private: }; } } -#endif // __ZONETABLE_H +#endif // ZONETABLE_H // Local Variables: // mode: c++ diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index 6585a386c3..f41ce53e66 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -2,7 +2,6 @@ SUBDIRS = . tests AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib AM_CPPFLAGS += $(BOOST_INCLUDES) - AM_CXXFLAGS = $(B10_CXXFLAGS) # Some versions of GCC warn about some versions of Boost regarding @@ -13,43 +12,38 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG) CLEANFILES = *.gcno *.gcda -lib_LTLIBRARIES = libb10-dhcp++.la libb10-dhcpsrv.la +lib_LTLIBRARIES = libb10-dhcp++.la libb10_dhcp___la_SOURCES = -libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h -libb10_dhcp___la_SOURCES += lease_mgr.cc lease_mgr.h +libb10_dhcp___la_SOURCES += duid.cc duid.h libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h -libb10_dhcp___la_SOURCES += iface_mgr_linux.cc libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc +libb10_dhcp___la_SOURCES += iface_mgr_linux.cc libb10_dhcp___la_SOURCES += iface_mgr_sun.cc +libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h libb10_dhcp___la_SOURCES += option.cc option.h +libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h +libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h +libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h +libb10_dhcp___la_SOURCES += option6_int.h +libb10_dhcp___la_SOURCES += option6_int_array.h libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h -libb10_dhcp___la_SOURCES += duid.cc duid.h - -libb10_dhcpsrv_la_SOURCES = cfgmgr.cc cfgmgr.h -libb10_dhcpsrv_la_SOURCES += pool.cc pool.h -libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h -libb10_dhcpsrv_la_SOURCES += triplet.h -libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h -libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS) -libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES) -libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la -libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la -libb10_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 2:0:0 - -EXTRA_DIST = README +libb10_dhcp___la_SOURCES += std_option_defs.h libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS) libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES) libb10_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la +libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0 +EXTRA_DIST = README libdhcp++.dox + if USE_CLANGPP # Disable unused parameter warning caused by some of the # Boost headers when compiling with clang. diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h index 15c306da66..116019e3fe 100644 --- a/src/lib/dhcp/dhcp6.h +++ b/src/lib/dhcp/dhcp6.h @@ -105,10 +105,11 @@ extern const int dhcpv6_type_name_max; /* DUID type definitions (RFC3315 section 9). */ -#define DUID_LLT 1 -#define DUID_EN 2 -#define DUID_LL 3 -#define DUID_UUID 4 +// see isc::dhcp::DUID::DUIDType enum in dhcp/duid.h +// #define DUID_LLT 1 +// #define DUID_EN 2 +// #define DUID_LL 3 +// #define DUID_UUID 4 // Define hardware types // Taken from http://www.iana.org/assignments/arp-parameters/ @@ -190,4 +191,4 @@ extern const int dhcpv6_type_name_max; #define IRT_DEFAULT 86400 #define IRT_MINIMUM 600 -#endif +#endif /* DHCP6_H */ diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc index db7ba25624..91efe94ad3 100644 --- a/src/lib/dhcp/duid.cc +++ b/src/lib/dhcp/duid.cc @@ -12,11 +12,15 @@ // 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 + +#include namespace isc { namespace dhcp { @@ -29,7 +33,7 @@ DUID::DUID(const std::vector& duid) { } } -DUID::DUID(const uint8_t * data, size_t len) { +DUID::DUID(const uint8_t* data, size_t len) { if (len > MAX_DUID_LEN) { isc_throw(OutOfRange, "DUID too large"); } @@ -53,36 +57,51 @@ DUID::DUIDType DUID::getType() const { } } -bool DUID::operator == (const DUID& other) const { +std::string DUID::toText() const { + std::stringstream tmp; + tmp << std::hex; + bool delim = false; + for (std::vector::const_iterator it = duid_.begin(); + it != duid_.end(); ++it) { + if (delim) { + tmp << ":"; + } + tmp << std::setw(2) << std::setfill('0') << static_cast(*it); + delim = true; + } + return (tmp.str()); +} + +bool DUID::operator==(const DUID& other) const { return (this->duid_ == other.duid_); } -bool DUID::operator != (const DUID& other) const { +bool DUID::operator!=(const DUID& other) const { return (this->duid_ != other.duid_); } -/// constructor based on vector +// Constructor based on vector ClientId::ClientId(const std::vector& clientid) - :DUID(clientid) { + : DUID(clientid) { } -/// constructor based on C-style data +// Constructor based on C-style data ClientId::ClientId(const uint8_t *clientid, size_t len) - :DUID(clientid, len) { + : DUID(clientid, len) { } -/// @brief returns a copy of client-id data +// Returns a copy of client-id data const std::vector ClientId::getClientId() const { return (duid_); } -// compares two client-ids -bool ClientId::operator == (const ClientId& other) const { +// Compares two client-ids +bool ClientId::operator==(const ClientId& other) const { return (this->duid_ == other.duid_); } -// compares two client-ids -bool ClientId::operator != (const ClientId& other) const { +// Compares two client-ids +bool ClientId::operator!=(const ClientId& other) const { return (this->duid_ != other.duid_); } diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h index 53257dbbb5..1b75f73ebf 100644 --- a/src/lib/dhcp/duid.h +++ b/src/lib/dhcp/duid.h @@ -12,11 +12,15 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include -#include -#include +#ifndef DUID_H +#define DUID_H + #include +#include + +#include +#include namespace isc { namespace dhcp { @@ -41,13 +45,13 @@ class DUID { DUID_MAX ///< not a real type, just maximum defined value + 1 } DUIDType; - /// @brief creates a DUID + /// @brief Constructor from vector DUID(const std::vector& duid); - /// @brief creates a DUID - DUID(const uint8_t *duid, size_t len); + /// @brief Constructor from array and array size + DUID(const uint8_t* duid, size_t len); - /// @brief returns a const reference to the actual DUID value + /// @brief Returns a const reference to the actual DUID value /// /// Note: For safety reasons, this method returns a copy of data as /// otherwise the reference would be only valid as long as the object that @@ -56,43 +60,61 @@ class DUID { /// (e.g. storeSelf()) that will avoid data copying. const std::vector getDuid() const; - /// @brief returns DUID type + /// @brief Returns the DUID type DUIDType getType() const; - // compares two DUIDs - bool operator == (const DUID& other) const; + /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff) + std::string toText() const; - // compares two DUIDs - bool operator != (const DUID& other) const; + /// @brief Compares two DUIDs for equality + bool operator==(const DUID& other) const; + + /// @brief Compares two DUIDs for inequality + bool operator!=(const DUID& other) const; protected: - /// the actual content of the DUID + /// The actual content of the DUID std::vector duid_; }; +/// @brief Shared pointer to a DUID +typedef boost::shared_ptr DuidPtr; + + + /// @brief Holds Client identifier or client IPv4 address /// /// This class is intended to be a generic IPv4 client identifier. It can hold /// a client-id class ClientId : DUID { - public: +public: + /// @brief Maximum size of a client ID + /// + /// This is the same as the maximum size of the underlying DUID. + /// + /// @note RFC 2131 does not specify an upper length of a client ID, the + /// value chosen here just being that of the underlying DUID. For + /// some backend database, there may be a possible (minor) + /// performance enhancement if this were smaller. + static const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN; - /// constructor based on vector + /// @brief Constructor based on vector ClientId(const std::vector& clientid); - /// constructor based on C-style data - ClientId(const uint8_t *clientid, size_t len); + /// @brief Constructor based on array and array size + ClientId(const uint8_t* clientid, size_t len); - /// @brief returns reference to the client-id data - /// + /// @brief Returns reference to the client-id data const std::vector getClientId() const; - // compares two client-ids - bool operator == (const ClientId& other) const; + /// @brief Compares two client-ids for equality + bool operator==(const ClientId& other) const; - // compares two client-ids - bool operator != (const ClientId& other) const; + /// @brief Compares two client-ids for inequality + bool operator!=(const ClientId& other) const; }; }; // end of isc::dhcp namespace }; // end of isc namespace + +#endif /* DUID_H */ diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index ecaa652949..adf1731cad 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/src/lib/dhcp/iface_mgr.cc @@ -13,22 +13,27 @@ // PERFORMANCE OF THIS SOFTWARE. #include -#include -#include -#include -#include -#include -#include + +// This must be included before udp_endpoint.h #include +#include +#include #include #include #include #include -#include -#include #include + +#include +#include + +#include +#include +#include +#include + using namespace std; using namespace isc::asiolink; using namespace isc::util::io::internal; @@ -247,6 +252,15 @@ bool IfaceMgr::openSockets6(const uint16_t port) { continue; } + // Bind link-local addresses only. Otherwise we bind several sockets + // on interfaces that have several global addresses. For examples + // with interface with 2 global addresses, we would bind 3 sockets + // (one for link-local and two for global). That would result in + // getting each message 3 times. + if (!addr->getAddress().to_v6().is_link_local()){ + continue; + } + sock = openSocket(iface->getName(), *addr, port); if (sock < 0) { isc_throw(SocketConfigError, "failed to open unicast socket"); @@ -1084,9 +1098,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ pkt->updateTimestamp(); - pkt->setLocalAddr(IOAddress::from_bytes(AF_INET6, + pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6, reinterpret_cast(&to_addr))); - pkt->setRemoteAddr(IOAddress::from_bytes(AF_INET6, + pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6, reinterpret_cast(&from.sin6_addr))); pkt->setRemotePort(ntohs(from.sin6_port)); pkt->setIndex(ifindex); diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h index a7b9a789ff..a669a6d48e 100644 --- a/src/lib/dhcp/iface_mgr.h +++ b/src/lib/dhcp/iface_mgr.h @@ -15,16 +15,18 @@ #ifndef IFACE_MGR_H #define IFACE_MGR_H -#include -#include -#include -#include #include -#include #include +#include #include #include +#include +#include +#include + +#include + namespace isc { namespace dhcp { @@ -681,4 +683,4 @@ private: }; // namespace isc::dhcp }; // namespace isc -#endif +#endif // IFACE_MGR_H diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc index 189fe90943..e7d5048887 100644 --- a/src/lib/dhcp/iface_mgr_linux.cc +++ b/src/lib/dhcp/iface_mgr_linux.cc @@ -31,18 +31,17 @@ #if defined(OS_LINUX) +#include #include #include +#include + +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include using namespace std; using namespace isc; @@ -125,7 +124,7 @@ const static size_t RCVBUF_SIZE = 32768; /// @brief Opens netlink socket and initializes handle structure. /// -/// @exception Unexpected Thrown if socket configuration fails. +/// @throw isc::Unexpected Thrown if socket configuration fails. void Netlink::rtnl_open_socket() { fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); @@ -303,7 +302,7 @@ void Netlink::ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info) { memcpy(addr, RTA_DATA(rta_tb[IFLA_ADDRESS]), ifa->ifa_family==AF_INET?V4ADDRESS_LEN:V6ADDRESS_LEN); - IOAddress a = IOAddress::from_bytes(ifa->ifa_family, addr); + IOAddress a = IOAddress::fromBytes(ifa->ifa_family, addr); iface.addAddress(a); /// TODO: Read lifetimes of configured IPv6 addresses diff --git a/src/lib/dhcp/lease_mgr.cc b/src/lib/dhcp/lease_mgr.cc deleted file mode 100644 index d09bd583c0..0000000000 --- a/src/lib/dhcp/lease_mgr.cc +++ /dev/null @@ -1,68 +0,0 @@ -// 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 -#include -#include "lease_mgr.h" - -using namespace std; - -using namespace isc::dhcp; - -LeaseMgr::LeaseMgr(const std::string& dbconfig) { - - if (dbconfig.length() == 0) { - return; - } - - vector tokens; - - // we need to pass a string to is_any_of, not just char *. Otherwise there - // are cryptic warnings on Debian6 running g++ 4.4 in /usr/include/c++/4.4 - // /bits/stl_algo.h:2178 "array subscript is above array bounds" - boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") )); - BOOST_FOREACH(std::string token, tokens) { - size_t pos = token.find("="); - if (pos != string::npos) { - string name = token.substr(0, pos); - string value = token.substr(pos + 1, -1); - parameters_.insert(pair(name, value)); - } else { - isc_throw(InvalidParameter, "Cannot parse " << token - << ", expected format is name=value"); - } - - } -} - -std::string LeaseMgr::getParameter(const std::string& name) const { - std::map::const_iterator param - = parameters_.find(name); - if (param == parameters_.end()) { - isc_throw(BadValue, "Parameter not found"); - } - return (param->second); -} - -LeaseMgr::~LeaseMgr() { -} diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index 12e1bbdfd4..2773d095b0 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -12,17 +12,22 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include -#include -#include -#include -#include -#include "config.h" +#include + #include #include +#include #include #include #include +#include +#include +#include +#include +#include + +#include +#include using namespace std; using namespace isc::dhcp; @@ -34,6 +39,38 @@ std::map LibDHCP::v4factories_; // static array with factories for options std::map LibDHCP::v6factories_; +// Static container with DHCPv4 option definitions. +OptionDefContainer LibDHCP::v4option_defs_; + +// Static container with DHCPv6 option definitions. +OptionDefContainer LibDHCP::v6option_defs_; + +const OptionDefContainer& +LibDHCP::getOptionDefs(const Option::Universe u) { + switch (u) { + case Option::V4: + initStdOptionDefs4(); + return (v4option_defs_); + case Option::V6: + if (v6option_defs_.size() == 0) { + initStdOptionDefs6(); + } + return (v6option_defs_); + default: + isc_throw(isc::BadValue, "invalid universe " << u << " specified"); + } +} + +OptionDefinitionPtr +LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) { + const OptionDefContainer& defs = getOptionDefs(u); + const OptionDefContainerTypeIndex& idx = defs.get<1>(); + const OptionDefContainerTypeRange& range = idx.equal_range(code); + if (range.first != range.second) { + return (*range.first); + } + return (OptionDefinitionPtr()); +} OptionPtr LibDHCP::optionFactory(Option::Universe u, @@ -75,25 +112,43 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, // @todo: consider throwing exception here. return (offset); } + + // Get the list of stdandard option definitions. + OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6); + // Get the search index #1. It allows to search for option definitions + // using option code. + const OptionDefContainerTypeIndex& idx = option_defs.get<1>(); + // Get all options with the particular option code. Note that option code + // is non-unique within this container however at this point we expect + // to get one option definition with the particular code. If more are + // returned we report an error. + const OptionDefContainerTypeRange& range = idx.equal_range(opt_type); + // Get the number of returned option definitions for the option code. + size_t num_defs = distance(range.first, range.second); OptionPtr opt; - switch (opt_type) { - case D6O_IA_NA: - case D6O_IA_PD: - opt = OptionPtr(new Option6IA(opt_type, - buf.begin() + offset, - buf.begin() + offset + opt_len)); - break; - case D6O_IAADDR: - opt = OptionPtr(new Option6IAAddr(opt_type, - buf.begin() + offset, - buf.begin() + offset + opt_len)); - break; - default: - opt = OptionPtr(new Option(Option::V6, - opt_type, + if (num_defs > 1) { + // Multiple options of the same code are not supported right now! + isc_throw(isc::Unexpected, "Internal error: multiple option definitions" + " for option type " << opt_type << " returned. Currently it is not" + " supported to initialize multiple option definitions" + " for the same option code. This will be supported once" + " support for option spaces is implemented"); + } else if (num_defs == 0) { + // @todo Don't crash if definition does not exist because only a few + // option definitions are initialized right now. In the future + // we will initialize definitions for all options and we will + // remove this elseif. For now, return generic option. + opt = OptionPtr(new Option(Option::V6, opt_type, buf.begin() + offset, buf.begin() + offset + opt_len)); - break; + } else { + // The option definition has been found. Use it to create + // the option instance from the provided buffer chunk. + const OptionDefinitionPtr& def = *(range.first); + assert(def); + opt = def->optionFactory(Option::V6, opt_type, + buf.begin() + offset, + buf.begin() + offset + opt_len); } // add option to options options.insert(std::make_pair(opt_type, opt)); @@ -201,3 +256,36 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u, return; } + +void +LibDHCP::initStdOptionDefs4() { + isc_throw(isc::NotImplemented, "initStdOptionDefs4 is not implemented"); +} + +void +LibDHCP::initStdOptionDefs6() { + v6option_defs_.clear(); + + for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) { + OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS6[i].name, + OPTION_DEF_PARAMS6[i].code, + OPTION_DEF_PARAMS6[i].type, + OPTION_DEF_PARAMS6[i].array)); + + for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) { + definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]); + } + + try { + definition->validate(); + } catch (const Exception& ex) { + // This is unlikely event that validation fails and may + // be only caused by programming error. To guarantee the + // data consistency we clear all option definitions that + // have been added so far and pass the exception forward. + v6option_defs_.clear(); + throw; + } + v6option_defs_.push_back(definition); + } +} diff --git a/src/lib/dhcp/libdhcp++.dox b/src/lib/dhcp/libdhcp++.dox new file mode 100644 index 0000000000..013b6f27e8 --- /dev/null +++ b/src/lib/dhcp/libdhcp++.dox @@ -0,0 +1,79 @@ +/** +@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. It is a generic purpose library that +can be used by server, client, relay, performance tools and other DHCP-related +tools. For server specific library, see \ref libdhcpsrv. Please do not +add any server-specific code to libdhcp++ and use \ref libdhcpsrv instead. + +The following classes for packet manipulation 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/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h index ae907012cc..e855070571 100644 --- a/src/lib/dhcp/libdhcp++.h +++ b/src/lib/dhcp/libdhcp++.h @@ -1,4 +1,4 @@ -// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-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 @@ -12,12 +12,14 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef LIBDHCP_H_ -#define LIBDHCP_H_ +#ifndef LIBDHCP_H +#define LIBDHCP_H + +#include +#include +#include #include -#include -#include namespace isc { namespace dhcp { @@ -29,6 +31,30 @@ public: /// Map of factory functions. typedef std::map FactoryMap; + /// @brief Return collection of option definitions. + /// + /// Method returns the collection of DHCP standard DHCP + /// option definitions. + /// @todo DHCPv4 option definitions are not implemented. For now + /// this function will throw isc::NotImplemented in case of attempt + /// to get option definitions for V4 universe. + /// + /// @param u universe of the options (V4 or V6). + /// + /// @return collection of option definitions. + static const OptionDefContainer& getOptionDefs(const Option::Universe u); + + /// @brief Return the first option definition matching a + /// particular option code. + /// + /// @param u universe (V4 or V6) + /// @param code option code. + /// + /// @return reference to an option definition being requested + /// or NULL pointer if option definition has not been found. + static OptionDefinitionPtr getOptionDef(const Option::Universe u, + const uint16_t code); + /// @brief Factory function to create instance of option. /// /// Factory method creates instance of specified option. The option @@ -38,9 +64,11 @@ public: /// @param u universe of the option (V4 or V6) /// @param type option-type /// @param buf option-buffer - /// @throw isc::InvalidOperation if there is no factory function - /// registered for specified option type. + /// /// @return instance of option. + /// + /// @throw isc::InvalidOperation if there is no factory function registered + /// for the specified option type. static isc::dhcp::OptionPtr optionFactory(isc::dhcp::Option::Universe u, uint16_t type, const OptionBuffer& buf); @@ -93,8 +121,8 @@ public: /// Registers factory method that produces options of specific option types. /// - /// @exception BadValue if provided type is already registered, has too large - /// value or invalid universe is specified + /// @throw isc::BadValue if provided the type is already registered, has + /// too large a value or an invalid universe is specified. /// /// @param u universe of the option (V4 or V6) /// @param type option-type @@ -102,15 +130,42 @@ public: static void OptionFactoryRegister(Option::Universe u, uint16_t type, Option::Factory * factory); -protected: + +private: + + /// Initialize standard DHCPv4 option definitions. + /// + /// The method creates option definitions for all DHCPv4 options. + /// Currently this function is not implemented. + /// + /// @todo implemend this function. + /// + /// @throw isc::NotImplemeneted + static void initStdOptionDefs4(); + + /// Initialize standard DHCPv6 option definitions. + /// + /// The method creates option definitions for all DHCPv6 options. + /// + /// @throw std::bad_alloc if system went out of memory. + /// @throw MalformedOptionDefinition if any of the definitions + /// is incorect. This is a programming error. + static void initStdOptionDefs6(); + /// pointers to factories that produce DHCPv6 options static FactoryMap v4factories_; /// pointers to factories that produce DHCPv6 options static FactoryMap v6factories_; + + /// Container with DHCPv4 option definitions. + static OptionDefContainer v4option_defs_; + + /// Container with DHCPv6 option definitions. + static OptionDefContainer v6option_defs_; }; } } -#endif +#endif // LIBDHCP_H diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc index fb441f93d1..2a53f0f247 100644 --- a/src/lib/dhcp/option.cc +++ b/src/lib/dhcp/option.cc @@ -12,16 +12,17 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include -#include -#include -#include -#include -#include "exceptions/exceptions.h" -#include "util/io_utilities.h" +#include +#include +#include +#include -#include "dhcp/option.h" -#include "dhcp/libdhcp++.h" +#include +#include + +#include +#include +#include using namespace std; using namespace isc::util; @@ -61,14 +62,14 @@ Option::Option(Universe u, uint16_t type, OptionBufferConstIter first, void Option::check() { if ( (universe_ != V4) && (universe_ != V6) ) { - isc_throw(BadValue, "Invalid universe type specified." + isc_throw(BadValue, "Invalid universe type specified. " << "Only V4 and V6 are allowed."); } if (universe_ == V4) { if (type_ > 255) { - isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big." + isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. " << "For DHCPv4 allowed type range is 0..255"); } else if (data_.size() > 255) { isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big."); @@ -86,20 +87,21 @@ void Option::pack(isc::util::OutputBuffer& buf) { switch (universe_) { case V6: return (pack6(buf)); + case V4: return (pack4(buf)); + default: - isc_throw(BadValue, "Failed to pack " << type_ << " option. Do not " - << "use this method for options other than DHCPv6."); + isc_throw(BadValue, "Failed to pack " << type_ << " option as the " + << "universe type is unknown."); } } void Option::pack4(isc::util::OutputBuffer& buf) { - switch (universe_) { - case V4: { + if (universe_ == V4) { if (len() > 255) { - isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big." + isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. " << "At most 255 bytes are supported."); /// TODO Larger options can be stored as separate instances /// of DHCPv4 options. Clients MUST concatenate them. @@ -108,33 +110,46 @@ Option::pack4(isc::util::OutputBuffer& buf) { buf.writeUint8(type_); buf.writeUint8(len() - getHeaderLen()); + if (!data_.empty()) { + buf.writeData(&data_[0], data_.size()); + } - buf.writeData(&data_[0], data_.size()); + packOptions(buf); - LibDHCP::packOptions(buf, options_); - return; + } else { + isc_throw(BadValue, "Invalid universe type " << universe_); } - case V6: - /// TODO: Do we need a sanity check for option size here? - buf.writeUint16(type_); - buf.writeUint16(len() - getHeaderLen()); - LibDHCP::packOptions(buf, options_); - return; - default: - isc_throw(OutOfRange, "Invalid universe type" << universe_); - } + return; } void Option::pack6(isc::util::OutputBuffer& buf) { - buf.writeUint16(type_); - buf.writeUint16(len() - getHeaderLen()); + if (universe_ == V6) { + buf.writeUint16(type_); + buf.writeUint16(len() - getHeaderLen()); + if (!data_.empty()) { + buf.writeData(&data_[0], data_.size()); + } - if (! data_.empty()) { - buf.writeData(&data_[0], data_.size()); + packOptions(buf); + } else { + isc_throw(BadValue, "Invalid universe type " << universe_); } + return; +} - return LibDHCP::packOptions6(buf, options_); +void +Option::packOptions(isc::util::OutputBuffer& buf) { + switch (universe_) { + case V4: + LibDHCP::packOptions(buf, options_); + return; + case V6: + LibDHCP::packOptions6(buf, options_); + return; + default: + isc_throw(isc::BadValue, "Invalid universe type " << universe_); + } } void Option::unpack(OptionBufferConstIter begin, @@ -142,6 +157,20 @@ void Option::unpack(OptionBufferConstIter begin, data_ = OptionBuffer(begin, end); } +void +Option::unpackOptions(const OptionBuffer& buf) { + switch (universe_) { + case V4: + LibDHCP::unpackOptions4(buf, options_); + return; + case V6: + LibDHCP::unpackOptions6(buf, options_); + return; + default: + isc_throw(isc::BadValue, "Invalid universe type " << universe_); + } +} + uint16_t Option::len() { // Returns length of the complete option (data length + DHCPv4/DHCPv6 // option header) @@ -285,6 +314,10 @@ void Option::setData(const OptionBufferConstIter first, std::copy(first, last, data_.begin()); } +bool Option::equal(const OptionPtr& other) const { + return ( (getType() == other->getType()) && + (getData() == other->getData()) ); +} Option::~Option() { diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h index 080a869996..29f0f01760 100644 --- a/src/lib/dhcp/option.h +++ b/src/lib/dhcp/option.h @@ -12,15 +12,17 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef OPTION_H_ -#define OPTION_H_ +#ifndef OPTION_H +#define OPTION_H -#include -#include -#include -#include #include +#include + +#include +#include +#include + namespace isc { namespace dhcp { @@ -78,9 +80,11 @@ public: /// @param u universe of the option (V4 or V6) /// @param type option-type /// @param buf option-buffer - /// @throw isc::InvalidOperation if there is no factory function - /// registered for specified option type. + /// /// @return instance of option. + /// + /// @throw isc::InvalidOperation if there is no factory function + /// registered for specified option type. static OptionPtr factory(Option::Universe u, uint16_t type, const OptionBuffer& buf); @@ -95,9 +99,11 @@ public: /// /// @param u universe of the option (V4 or V6) /// @param type option-type - /// @throw isc::InvalidOperation if there is no factory function - /// registered for specified option type. + /// /// @return instance of option. + /// + /// @throw isc::InvalidOperation if there is no factory function + /// registered for specified option type. static OptionPtr factory(Option::Universe u, uint16_t type) { return factory(u, type, OptionBuffer()); } @@ -113,7 +119,7 @@ public: /// This constructor takes vector& which is used in cases /// when content of the option will be copied and stored within /// option object. V4 Options follow that approach already. - /// TODO Migrate V6 options to that approach. + /// @todo Migrate V6 options to that approach. /// /// @param u specifies universe (V4 or V6) /// @param type option type (0-255 for V4 and 0-65535 for V6) @@ -125,7 +131,7 @@ public: /// This contructor is similar to the previous one, but it does not take /// the whole vector, but rather subset of it. /// - /// TODO: This can be templated to use different containers, not just + /// @todo This can be templated to use different containers, not just /// vector. Prototype should look like this: /// template Option(Universe u, uint16_t type, /// InputIterator first, InputIterator last); @@ -154,19 +160,24 @@ public: /// byte after stored option (that is useful for writing options one after /// another). Used in DHCPv6 options. /// - /// TODO: Migrate DHCPv6 code to pack(OutputBuffer& buf) version + /// @todo Migrate DHCPv6 code to pack(OutputBuffer& buf) version /// /// @param buf pointer to a buffer + /// + /// @throw BadValue Universe of the option is neither V4 nor V6. virtual void pack(isc::util::OutputBuffer& buf); /// @brief Writes option in a wire-format to a buffer. /// /// Method will throw if option storing fails for some reason. /// - /// TODO Once old (DHCPv6) implementation is rewritten, + /// @todo Once old (DHCPv6) implementation is rewritten, /// unify pack4() and pack6() and rename them to just pack(). /// /// @param buf output buffer (option will be stored there) + /// + /// @throw OutOfRange Option type is greater than 255. + /// @throw BadValue Universe is not V4. virtual void pack4(isc::util::OutputBuffer& buf); /// @brief Parses received buffer. @@ -186,7 +197,7 @@ public: /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6) /// /// @return option type - uint16_t getType() { return (type_); } + uint16_t getType() const { return (type_); } /// Returns length of the complete option (data length + DHCPv4/DHCPv6 /// option header) @@ -208,7 +219,7 @@ public: /// /// @return pointer to actual data (or reference to an empty vector /// if there is no data) - virtual const OptionBuffer& getData() { return (data_); } + virtual const OptionBuffer& getData() const { return (data_); } /// Adds a sub-option. /// @@ -240,21 +251,21 @@ public: /// @brief Returns content of first byte. /// - /// @exception OutOfRange Thrown if the option has a length of 0. + /// @throw isc::OutOfRange Thrown if the option has a length of 0. /// /// @return value of the first byte uint8_t getUint8(); /// @brief Returns content of first word. /// - /// @exception OutOfRange Thrown if the option has a length less than 2. + /// @throw isc::OutOfRange Thrown if the option has a length less than 2. /// /// @return uint16_t value stored on first two bytes uint16_t getUint16(); /// @brief Returns content of first double word. /// - /// @exception OutOfRange Thrown if the option has a length less than 4. + /// @throw isc::OutOfRange Thrown if the option has a length less than 4. /// /// @return uint32_t value stored on first four bytes uint32_t getUint32(); @@ -292,13 +303,57 @@ public: /// just to force that every option has virtual dtor virtual ~Option(); + /// @brief Checks if two options are equal + /// + /// Equality verifies option type and option content. Care should + /// be taken when using this method. Implementation for derived + /// classes should be provided when this method is expected to be + /// used. It is safe in general, as the first check (different types) + /// will detect differences between base Option and derived + /// objects. + /// + /// @param other the other option + /// @return true if both options are equal + virtual bool equal(const OptionPtr& other) const; + protected: /// Builds raw (over-wire) buffer of this option, including all /// defined suboptions. Version for building DHCPv4 options. /// /// @param buf output buffer (built options will be stored here) + /// + /// @throw BadValue Universe is not V6. virtual void pack6(isc::util::OutputBuffer& buf); + /// @brief Store sub options in a buffer. + /// + /// This method stores all sub-options defined for a particular + /// option in a on-wire format in output buffer provided. + /// This function is called by pack function in this class or + /// derived classes that override pack. + /// + /// @param [out] buf output buffer. + /// + /// @todo The set of exceptions thrown by this function depend on + /// exceptions thrown by pack methods invoked on objects + /// representing sub options. We should consider whether to aggregate + /// those into one exception which can be documented here. + void packOptions(isc::util::OutputBuffer& buf); + + /// @brief Builds a collection of sub options from the buffer. + /// + /// This method parses the provided buffer and builds a collection + /// of objects representing sub options. This function may throw + /// different exceptions when option assembly fails. + /// + /// @param buf buffer to be parsed. + /// + /// @todo The set of exceptions thrown by this function depend on + /// exceptions thrown by unpack methods invoked on objects + /// representing sub options. We should consider whether to aggregate + /// those into one exception which can be documented here. + void unpackOptions(const OptionBuffer& buf); + /// @brief A private method used for option correctness. /// /// It is used in constructors. In there are any problems detected @@ -318,11 +373,11 @@ protected: /// collection for storing suboptions OptionCollection options_; - /// TODO: probably 2 different containers have to be used for v4 (unique + /// @todo probably 2 different containers have to be used for v4 (unique /// options) and v6 (options with the same type can repeat) }; } // namespace isc::dhcp } // namespace isc -#endif +#endif // OPTION_H diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc index 4b0224f017..76341b25f7 100644 --- a/src/lib/dhcp/option4_addrlst.cc +++ b/src/lib/dhcp/option4_addrlst.cc @@ -12,15 +12,17 @@ // 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 +#include + +#include +#include + +#include +#include +#include using namespace std; using namespace isc::util; diff --git a/src/lib/dhcp/option4_addrlst.h b/src/lib/dhcp/option4_addrlst.h index 3bedc6df36..b266cbf0a9 100644 --- a/src/lib/dhcp/option4_addrlst.h +++ b/src/lib/dhcp/option4_addrlst.h @@ -12,16 +12,19 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef OPTION4_ADDRLST_H_ -#define OPTION4_ADDRLST_H_ +#ifndef OPTION4_ADDRLST_H +#define OPTION4_ADDRLST_H -#include -#include -#include -#include -#include -#include +#include #include +#include + +#include +#include + +#include +#include +#include namespace isc { namespace dhcp { @@ -161,4 +164,4 @@ protected: } // namespace isc::dhcp } // namespace isc -#endif +#endif // OPTION4_ADDRLST_H diff --git a/src/lib/dhcp/option6_addrlst.cc b/src/lib/dhcp/option6_addrlst.cc index d23b7008bb..c53fe780e8 100644 --- a/src/lib/dhcp/option6_addrlst.cc +++ b/src/lib/dhcp/option6_addrlst.cc @@ -12,16 +12,17 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include -#include -#include -#include "exceptions/exceptions.h" +#include +#include +#include +#include +#include +#include -#include "asiolink/io_address.h" -#include "util/io_utilities.h" -#include "dhcp/libdhcp++.h" -#include "dhcp/option6_addrlst.h" -#include "dhcp/dhcp6.h" +#include + +#include +#include using namespace std; using namespace isc; @@ -83,7 +84,7 @@ void Option6AddrLst::unpack(OptionBufferConstIter begin, << " is not divisible by 16."); } while (begin != end) { - addrs_.push_back(IOAddress::from_bytes(AF_INET6, &(*begin))); + addrs_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin))); begin += V6ADDRESS_LEN; } } diff --git a/src/lib/dhcp/option6_addrlst.h b/src/lib/dhcp/option6_addrlst.h index 209d2ddaba..b9c0debc06 100644 --- a/src/lib/dhcp/option6_addrlst.h +++ b/src/lib/dhcp/option6_addrlst.h @@ -12,13 +12,14 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef OPTION6_ADDRLST_H_ -#define OPTION6_ADDRLST_H_ +#ifndef OPTION6_ADDRLST_H +#define OPTION6_ADDRLST_H -#include #include #include +#include + namespace isc { namespace dhcp { @@ -96,4 +97,4 @@ protected: } // isc::dhcp namespace } // isc namespace -#endif /* OPTION_ADDRLST_H_ */ +#endif // OPTION_ADDRLST_H diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc index 65be711488..64c29362c5 100644 --- a/src/lib/dhcp/option6_ia.cc +++ b/src/lib/dhcp/option6_ia.cc @@ -12,16 +12,16 @@ // 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 +#include +#include +#include + using namespace std; using namespace isc::util; @@ -29,7 +29,7 @@ namespace isc { namespace dhcp { Option6IA::Option6IA(uint16_t type, uint32_t iaid) - :Option(Option::V6, type), iaid_(iaid) { + :Option(Option::V6, type), iaid_(iaid), t1_(0), t2_(0) { } Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin, OptionBufferConstIter end) @@ -44,7 +44,7 @@ void Option6IA::pack(isc::util::OutputBuffer& buf) { buf.writeUint32(t1_); buf.writeUint32(t2_); - LibDHCP::packOptions6(buf, options_); + packOptions(buf); } void Option6IA::unpack(OptionBufferConstIter begin, @@ -62,7 +62,7 @@ void Option6IA::unpack(OptionBufferConstIter begin, t2_ = readUint32( &(*begin) ); begin += sizeof(uint32_t); - LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_); + unpackOptions(OptionBuffer(begin, end)); } std::string Option6IA::toText(int indent /* = 0*/) { diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h index c2089d41fd..32f3667db1 100644 --- a/src/lib/dhcp/option6_ia.h +++ b/src/lib/dhcp/option6_ia.h @@ -12,11 +12,12 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef OPTION_IA_H_ -#define OPTION_IA_H_ +#ifndef OPTION_IA_H +#define OPTION_IA_H + +#include #include -#include "option.h" namespace isc { namespace dhcp { @@ -67,12 +68,17 @@ public: /// Sets T1 timer. /// /// @param t1 t1 value to be set - void setT1(uint32_t t1) { t1_=t1; } + void setT1(uint32_t t1) { t1_ = t1; } /// Sets T2 timer. /// /// @param t2 t2 value to be set - void setT2(uint32_t t2) { t2_=t2; } + void setT2(uint32_t t2) { t2_ = t2; } + + /// Sets Identity Association Identifier. + /// + /// @param iaid IAID value to be set + void setIAID(uint32_t iaid) { iaid_ = iaid; } /// Returns IA identifier. /// @@ -112,4 +118,4 @@ protected: } // isc::dhcp namespace } // isc namespace -#endif /* OPTION_IA_H_ */ +#endif // OPTION_IA_H diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc index 084a5f3f7c..8db50478d2 100644 --- a/src/lib/dhcp/option6_iaaddr.cc +++ b/src/lib/dhcp/option6_iaaddr.cc @@ -12,16 +12,17 @@ // 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 -#include "exceptions/exceptions.h" - -#include "dhcp/libdhcp++.h" -#include "dhcp/option6_iaaddr.h" -#include "dhcp/dhcp6.h" -#include "asiolink/io_address.h" -#include "util/io_utilities.h" using namespace std; using namespace isc::asiolink; @@ -58,7 +59,7 @@ void Option6IAAddr::pack(isc::util::OutputBuffer& buf) { buf.writeUint32(valid_); // parse suboption (there shouldn't be any for IAADDR) - LibDHCP::packOptions6(buf, options_); + packOptions(buf); } void Option6IAAddr::unpack(OptionBuffer::const_iterator begin, @@ -68,7 +69,7 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin, } // 16 bytes: IPv6 address - addr_ = IOAddress::from_bytes(AF_INET6, &(*begin)); + addr_ = IOAddress::fromBytes(AF_INET6, &(*begin)); begin += V6ADDRESS_LEN; preferred_ = readUint32( &(*begin) ); @@ -76,7 +77,8 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin, valid_ = readUint32( &(*begin) ); begin += sizeof(uint32_t); - LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_); + + unpackOptions(OptionBuffer(begin, end)); } std::string Option6IAAddr::toText(int indent /* =0 */) { diff --git a/src/lib/dhcp/option6_iaaddr.h b/src/lib/dhcp/option6_iaaddr.h index e6e2c161e7..28c5abcb2e 100644 --- a/src/lib/dhcp/option6_iaaddr.h +++ b/src/lib/dhcp/option6_iaaddr.h @@ -12,11 +12,11 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#ifndef OPTION6_IAADDR_H_ -#define OPTION6_IAADDR_H_ +#ifndef OPTION6_IAADDR_H +#define OPTION6_IAADDR_H -#include "asiolink/io_address.h" -#include "dhcp/option.h" +#include +#include namespace isc { namespace dhcp { @@ -120,4 +120,4 @@ protected: } // isc::dhcp namespace } // isc namespace -#endif /* OPTION_IA_H_ */ +#endif // OPTION_IA_H diff --git a/src/lib/dhcp/option6_int.h b/src/lib/dhcp/option6_int.h new file mode 100644 index 0000000000..d61509bc52 --- /dev/null +++ b/src/lib/dhcp/option6_int.h @@ -0,0 +1,189 @@ +// 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 OPTION6_INT_H +#define OPTION6_INT_H + +#include +#include +#include +#include + +#include + +namespace isc { +namespace dhcp { + +/// This template class represents DHCPv6 option with single value. +/// This value is of integer type and can be any of the following: +/// - uint8_t, +/// - uint16_t, +/// - uint32_t, +/// - int8_t, +/// - int16_t, +/// - int32_t. +/// +/// @param T data field type (see above). +template +class Option6Int: public Option { + +public: + /// @brief Constructor. + /// + /// @param type option type. + /// @param value option value. + /// + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + Option6Int(uint16_t type, T value) + : Option(Option::V6, type), value_(value) { + if (!OptionDataTypeTraits::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + } + + /// @brief Constructor. + /// + /// This constructor creates option from a buffer. This construtor + /// may throw exception if \ref unpack function throws during buffer + /// parsing. + /// + /// @param type option type. + /// @param begin iterator to first byte of option data. + /// @param end iterator to end of option data (first byte after option end). + /// + /// @throw isc::OutOfRange if provided buffer is shorter than data size. + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + Option6Int(uint16_t type, OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(Option::V6, type) { + if (!OptionDataTypeTraits::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + unpack(begin, end); + } + + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param [out] buf buffer (option will be stored here) + /// + /// @throw isc::dhcp::InvalidDataType if size of a data field type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + void pack(isc::util::OutputBuffer& buf) { + buf.writeUint16(type_); + buf.writeUint16(len() - OPTION6_HDR_LEN); + // Depending on the data type length we use different utility functions + // writeUint16 or writeUint32 which write the data in the network byte + // order to the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + switch (OptionDataTypeTraits::len) { + case 1: + buf.writeUint8(value_); + break; + case 2: + buf.writeUint16(value_); + break; + case 4: + buf.writeUint32(value_); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + packOptions(buf); + } + + /// @brief Parses received buffer + /// + /// Parses received buffer and returns offset to the first unused byte after + /// parsed option. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + /// + /// @throw isc::OutOfRange if provided buffer is shorter than data size. + /// @throw isc::dhcp::InvalidDataType if size of a data field type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + if (distance(begin, end) < sizeof(T)) { + isc_throw(OutOfRange, "Option " << getType() << " truncated"); + } + // @todo consider what to do if buffer is longer than data type. + + // Depending on the data type length we use different utility functions + // readUint16 or readUint32 which read the data laid in the network byte + // order from the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + int data_size_len = OptionDataTypeTraits::len; + switch (data_size_len) { + case 1: + value_ = *begin; + break; + case 2: + value_ = isc::util::readUint16(&(*begin)); + break; + case 4: + value_ = isc::util::readUint32(&(*begin)); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + // Use local variable to set a new value for this iterator. + // When using OptionDataTypeTraits::len directly some versions + // of clang complain about unresolved reference to + // OptionDataTypeTraits structure during linking. + begin += data_size_len; + unpackOptions(OptionBuffer(begin, end)); + } + + /// @brief Set option value. + /// + /// @param value new option value. + void setValue(T value) { value_ = value; } + + /// @brief Return option value. + /// + /// @return option value. + T getValue() const { return value_; } + + /// @brief returns complete length of option + /// + /// Returns length of this option, including option header and suboptions + /// + /// @return length of this option + virtual uint16_t len() { + uint16_t length = OPTION6_HDR_LEN + sizeof(T); + // length of all suboptions + for (Option::OptionCollection::iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + return (length); + } + +private: + + T value_; ///< Value conveyed by the option. +}; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION6_INT_H diff --git a/src/lib/dhcp/option6_int_array.h b/src/lib/dhcp/option6_int_array.h new file mode 100644 index 0000000000..3b642137fa --- /dev/null +++ b/src/lib/dhcp/option6_int_array.h @@ -0,0 +1,228 @@ +// 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 OPTION6_INT_ARRAY_H +#define OPTION6_INT_ARRAY_H + +#include +#include +#include +#include + +#include + +namespace isc { +namespace dhcp { + +/// This template class represents DHCPv6 option with array of +/// integer values. The type of the elements in the array can be +/// any of the following: +/// - uint8_t, +/// - uint16_t, +/// - uint32_t, +/// - int8_t, +/// - int16_t, +/// - int32_t. +/// +/// @warning Since this option may convey variable number of integer +/// values, sub-options are should not be added in this option as +/// there is no way to distinguish them from other data. The API will +/// allow addition of sub-options but they will be ignored during +/// packing and unpacking option data. +/// +/// @param T data field type (see above). +template +class Option6IntArray: public Option { + +public: + + /// @brief Constructor. + /// + /// Creates option with empty values vector. + /// + /// @param type option type. + /// + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + Option6IntArray(uint16_t type) + : Option(Option::V6, type), + values_(0) { + if (!OptionDataTypeTraits::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + } + + /// @brief Constructor. + /// + /// @param type option type. + /// @param buf buffer with option data (must not be empty). + /// + /// @throw isc::OutOfRange if provided buffer is empty or its length + /// is not multiple of size of the data type in bytes. + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + Option6IntArray(uint16_t type, const OptionBuffer& buf) + : Option(Option::V6, type) { + if (!OptionDataTypeTraits::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + unpack(buf.begin(), buf.end()); + } + + /// @brief Constructor. + /// + /// This constructor creates option from a buffer. This construtor + /// may throw exception if \ref unpack function throws during buffer + /// parsing. + /// + /// @param type option type. + /// @param begin iterator to first byte of option data. + /// @param end iterator to end of option data (first byte after option end). + /// + /// @throw isc::OutOfRange if provided buffer is empty or its length + /// is not multiple of size of the data type in bytes. + /// @throw isc::dhcp::InvalidDataType if data field type provided + /// as template parameter is not a supported integer type. + Option6IntArray(uint16_t type, OptionBufferConstIter begin, + OptionBufferConstIter end) + : Option(Option::V6, type) { + if (!OptionDataTypeTraits::integer_type) { + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + unpack(begin, end); + } + + /// Writes option in wire-format to buf, returns pointer to first unused + /// byte after stored option. + /// + /// @param [out] buf buffer (option will be stored here) + /// + /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + void pack(isc::util::OutputBuffer& buf) { + buf.writeUint16(type_); + buf.writeUint16(len() - OPTION6_HDR_LEN); + for (int i = 0; i < values_.size(); ++i) { + // Depending on the data type length we use different utility functions + // writeUint16 or writeUint32 which write the data in the network byte + // order to the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + switch (OptionDataTypeTraits::len) { + case 1: + buf.writeUint8(values_[i]); + break; + case 2: + buf.writeUint16(values_[i]); + break; + case 4: + buf.writeUint32(values_[i]); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + } + // We don't pack sub-options here because we have array-type option. + // We don't allow sub-options in array-type options as there is no + // way to distinguish them from the data fields on option reception. + } + + /// @brief Parses received buffer + /// + /// Parses received buffer and returns offset to the first unused byte after + /// parsed option. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + /// + /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not + /// equal to 1, 2 or 4 bytes. The data type is not checked in this function + /// because it is checked in a constructor. + virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) { + if (distance(begin, end) == 0) { + isc_throw(OutOfRange, "option " << getType() << " empty"); + } + if (distance(begin, end) % sizeof(T) != 0) { + isc_throw(OutOfRange, "option " << getType() << " truncated"); + } + // @todo consider what to do if buffer is longer than data type. + + values_.clear(); + while (begin != end) { + // Depending on the data type length we use different utility functions + // readUint16 or readUint32 which read the data laid in the network byte + // order from the provided buffer. The same functions can be safely used + // for either unsigned or signed integers so there is not need to create + // special cases for intX_t types. + int data_size_len = OptionDataTypeTraits::len; + switch (data_size_len) { + case 1: + values_.push_back(*begin); + break; + case 2: + values_.push_back(isc::util::readUint16(&(*begin))); + break; + case 4: + values_.push_back(isc::util::readUint32(&(*begin))); + break; + default: + isc_throw(dhcp::InvalidDataType, "non-integer type"); + } + // Use local variable to set a new value for this iterator. + // When using OptionDataTypeTraits::len directly some versions + // of clang complain about unresolved reference to + // OptionDataTypeTraits structure during linking. + begin += data_size_len; + } + // We do not unpack sub-options here because we have array-type option. + // Such option have variable number of data fields, thus there is no + // way to assess where sub-options start. + } + + /// @brief Return collection of option values. + /// + /// @return collection of values. + const std::vector& getValues() const { return (values_); } + + /// @brief Set option values. + /// + /// @param values collection of values to be set for option. + void setValues(const std::vector& values) { values_ = values; } + + /// @brief returns complete length of option + /// + /// Returns length of this option, including option header and suboptions + /// + /// @return length of this option + virtual uint16_t len() { + uint16_t length = OPTION6_HDR_LEN + values_.size() * sizeof(T); + // length of all suboptions + for (Option::OptionCollection::iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + return (length); + } + +private: + + std::vector values_; +}; + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION6_INT_ARRAY_H diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc new file mode 100644 index 0000000000..0c3cb818af --- /dev/null +++ b/src/lib/dhcp/option_custom.cc @@ -0,0 +1,636 @@ +// 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 + +namespace isc { +namespace dhcp { + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u) + : Option(u, def.getCode(), OptionBuffer()), + definition_(def) { + createBuffers(); +} + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u, + const OptionBuffer& data) + : Option(u, def.getCode(), data.begin(), data.end()), + definition_(def) { + // It is possible that no data is provided if an option + // is being created on a server side. In such case a bunch + // of buffers with default values is first created and then + // the values are replaced using writeXXX functions. Thus + // we need to detect that no data has been specified and + // take a different code path. + if (!data_.empty()) { + createBuffers(data_); + } else { + createBuffers(); + } +} + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u, + OptionBufferConstIter first, + OptionBufferConstIter last) + : Option(u, def.getCode(), first, last), + definition_(def) { + createBuffers(data_); +} + +void +OptionCustom::addArrayDataField(const asiolink::IOAddress& address) { + checkArrayType(); + + if ((address.getFamily() == AF_INET && + definition_.getType() != OPT_IPV4_ADDRESS_TYPE) || + (address.getFamily() == AF_INET6 && + definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) { + isc_throw(BadDataTypeCast, "invalid address specified " + << address.toText() << ". Expected a valid IPv" + << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ? "4" : "6") + << " address."); + } + + OptionBuffer buf; + OptionDataTypeUtil::writeAddress(address, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::addArrayDataField(const bool value) { + checkArrayType(); + + OptionBuffer buf; + OptionDataTypeUtil::writeBool(value, buf); + buffers_.push_back(buf); +} + +void +OptionCustom::checkIndex(const uint32_t index) const { + if (index >= buffers_.size()) { + isc_throw(isc::OutOfRange, "specified data field index " << index + << " is out of range."); + } +} + +template +void +OptionCustom::checkDataType(const uint32_t index) const { + // Check that the requested return type is a supported integer. + if (!OptionDataTypeTraits::integer_type) { + isc_throw(isc::dhcp::InvalidDataType, "specified data type" + " is not a supported integer type."); + } + + // Get the option definition type. + OptionDataType data_type = definition_.getType(); + if (data_type == OPT_RECORD_TYPE) { + const OptionDefinition::RecordFieldsCollection& record_fields = + definition_.getRecordFields(); + // When we initialized buffers we have already checked that + // the number of these buffers is equal to number of option + // fields in the record so the condition below should be met. + assert(index < record_fields.size()); + // Get the data type to be returned. + data_type = record_fields[index]; + } + + if (OptionDataTypeTraits::type != data_type) { + isc_throw(isc::dhcp::InvalidDataType, + "specified data type " << data_type << " does not" + " match the data type in an option definition for field" + " index " << index); + } +} + +void +OptionCustom::createBuffers() { + definition_.validate(); + + std::vector buffers; + + OptionDataType data_type = definition_.getType(); + // This function is called when an empty data buffer has been + // passed to the constructor. In such cases values for particular + // data fields will be set using modifier functions but for now + // we need to initialize a set of buffers that are specified + // for an option by its definition. Since there is no data yet, + // we are going to fill these buffers with default values. + if (data_type == OPT_RECORD_TYPE) { + // For record types we need to iterate over all data fields + // specified in option definition and create corresponding + // buffers for each of them. + const OptionDefinition::RecordFieldsCollection fields = + definition_.getRecordFields(); + + for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); + field != fields.end(); ++field) { + OptionBuffer buf; + + // For data types that have a fixed size we can use the + // utility function to get the buffer's size. + size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field); + + // For variable data sizes the utility function returns zero. + // It is ok for string values because the default string + // is 'empty'. However for FQDN the empty value is not valid + // so we initialize it to '.'. + if (data_size == 0 && + *field == OPT_FQDN_TYPE) { + OptionDataTypeUtil::writeFqdn(".", buf); + } else { + // At this point we can resize the buffer. Note that + // for string values we are setting the empty buffer + // here. + buf.resize(data_size); + } + // We have the buffer with default value prepared so we + // add it to the set of buffers. + buffers.push_back(buf); + } + } else if (!definition_.getArrayType() && + data_type != OPT_EMPTY_TYPE) { + // For either 'empty' options we don't have to create any buffers + // for obvious reason. For arrays we also don't create any buffers + // yet because the set of fields that belong to the array is open + // ended so we can't allocate required buffers until we know how + // many of them are needed. + // For non-arrays we have a single value being held by the option + // so we have to allocate exactly one buffer. + OptionBuffer buf; + size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type); + if (data_size == 0 && + data_type == OPT_FQDN_TYPE) { + OptionDataTypeUtil::writeFqdn(".", buf); + } else { + // Note that if our option holds a string value then + // we are making empty buffer here. + buf.resize(data_size); + } + // Add a buffer that we have created and leave. + buffers.push_back(buf); + } + // The 'swap' is used here because we want to make sure that we + // don't touch buffers_ until we successfully allocate all + // buffers to be stored there. + std::swap(buffers, buffers_); +} + +void +OptionCustom::createBuffers(const OptionBuffer& data_buf) { + // Check that the option definition is correct as we are going + // to use it to split the data_ buffer into set of sub buffers. + definition_.validate(); + + std::vector buffers; + OptionBuffer::const_iterator data = data_buf.begin(); + + OptionDataType data_type = definition_.getType(); + if (data_type == OPT_RECORD_TYPE) { + // An option comprises a record of data fields. We need to + // get types of these data fields to allocate enough space + // for each buffer. + const OptionDefinition::RecordFieldsCollection& fields = + definition_.getRecordFields(); + + // Go over all data fields within a record. + for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); + field != fields.end(); ++field) { + // For fixed-size data type such as boolean, integer, even + // IP address we can use the utility function to get the required + // buffer size. + size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field); + + // For variable size types (e.g. string) the function above will + // return 0 so we need to do a runtime check of the length. + if (data_size == 0) { + // FQDN is a special data type as it stores variable length data + // but the data length is encoded in the buffer. The easiest way + // to obtain the length of the data is to read the FQDN. The + // utility function will return the size of the buffer on success. + if (*field == OPT_FQDN_TYPE) { + std::string fqdn = + OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end())); + // The size of the buffer holding an FQDN is always + // 1 byte larger than the size of the string + // representation of this FQDN. + data_size = fqdn.size() + 1; + } else { + // In other case we are dealing with string or binary value + // which size can't be determined. Thus we consume the + // remaining part of the buffer for it. Note that variable + // size data can be laid at the end of the option only and + // that the validate() function in OptionDefinition object + // should have checked wheter it is a case for this option. + data_size = std::distance(data, data_buf.end()); + } + if (data_size == 0) { + // If we reached the end of buffer we assume that this option is + // truncated because there is no remaining data to initialize + // an option field. + if (data_size == 0) { + isc_throw(OutOfRange, "option buffer truncated"); + } + } + } else { + // Our data field requires that there is a certain chunk of + // data left in the buffer. If not, option is truncated. + if (std::distance(data, data_buf.end()) < data_size) { + isc_throw(OutOfRange, "option buffer truncated"); + } + } + // Store the created buffer. + buffers.push_back(OptionBuffer(data, data + data_size)); + // Proceed to the next data field. + data += data_size; + } + } else if (data_type != OPT_EMPTY_TYPE) { + // If data_type value is other than OPT_RECORD_TYPE, our option is + // empty (have no data at all) or it comprises one or more + // data fields of the same type. The type of those fields + // is held in the data_type variable so let's use it to determine + // a size of buffers. + size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type); + // The check below will fail if the input buffer is too short + // for the data size being held by this option. + // Note that data_size returned by getDataTypeLen may be zero + // if variable length data is being held by the option but + // this will not cause this check to throw exception. + if (std::distance(data, data_buf.end()) < data_size) { + isc_throw(OutOfRange, "option buffer truncated"); + } + // For an array of values we are taking different path because + // we have to handle multiple buffers. + if (definition_.getArrayType()) { + while (data != data_buf.end()) { + // FQDN is a special case because it is of a variable length. + // The actual length for a particular FQDN is encoded within + // a buffer so we have to actually read the FQDN from a buffer + // to get it. + if (data_type == OPT_FQDN_TYPE) { + std::string fqdn = + OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end())); + // The size of the buffer holding an FQDN is always + // 1 byte larger than the size of the string + // representation of this FQDN. + data_size = fqdn.size() + 1; + } + // We don't perform other checks for data types that can't be + // used together with array indicator such as strings, empty field + // etc. This is because OptionDefinition::validate function should + // have checked this already. Thus data_size must be greater than + // zero. + assert(data_size > 0); + // Get chunks of data and store as a collection of buffers. + // Truncate any remaining part which length is not divisible by + // data_size. Note that it is ok to truncate the data if and only + // if the data buffer is long enough to keep at least one value. + // This has been checked above already. + if (std::distance(data, data_buf.end()) < data_size) { + break; + } + buffers.push_back(OptionBuffer(data, data + data_size)); + data += data_size; + } + } else { + // For non-arrays the data_size can be zero because + // getDataTypeLen returns zero for variable size data types + // such as strings. Simply take whole buffer. + if (data_size == 0) { + // For FQDN we get the size by actually reading the FQDN. + if (data_type == OPT_FQDN_TYPE) { + std::string fqdn = + OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end())); + // The size of the buffer holding an FQDN is always + // 1 bytes larger than the size of the string + // representation of this FQDN. + data_size = fqdn.size() + 1; + } else { + data_size = std::distance(data, data_buf.end()); + } + } + if (data_size > 0) { + buffers.push_back(OptionBuffer(data, data + data_size)); + } else { + isc_throw(OutOfRange, "option buffer truncated"); + } + } + } + // If everything went ok we can replace old buffer set with new ones. + std::swap(buffers_, buffers); +} + +std::string +OptionCustom::dataFieldToText(const OptionDataType data_type, + const uint32_t index) const { + std::ostringstream text; + + // Get the value of the data field. + switch (data_type) { + case OPT_BINARY_TYPE: + text << util::encode::encodeHex(readBinary(index)); + break; + case OPT_BOOLEAN_TYPE: + text << (readBoolean(index) ? "true" : "false"); + break; + case OPT_INT8_TYPE: + text << readInteger(index); + break; + case OPT_INT16_TYPE: + text << readInteger(index); + break; + case OPT_INT32_TYPE: + text << readInteger(index); + break; + case OPT_UINT8_TYPE: + text << readInteger(index); + break; + case OPT_UINT16_TYPE: + text << readInteger(index); + break; + case OPT_UINT32_TYPE: + text << readInteger(index); + break; + case OPT_IPV4_ADDRESS_TYPE: + case OPT_IPV6_ADDRESS_TYPE: + text << readAddress(index).toText(); + break; + case OPT_FQDN_TYPE: + text << readFqdn(index); + break; + case OPT_STRING_TYPE: + text << readString(index); + break; + default: + ; + } + + // Append data field type in brackets. + text << " ( " << OptionDataTypeUtil::getDataTypeName(data_type) << " ) "; + + return (text.str()); +} + +void +OptionCustom::pack4(isc::util::OutputBuffer& buf) { + if (len() > 255) { + isc_throw(OutOfRange, "DHCPv4 Option " << type_ + << " value is too high. At most 255 is supported."); + } + + buf.writeUint8(type_); + buf.writeUint8(len() - getHeaderLen()); + + // Write data from buffers. + for (std::vector::const_iterator it = buffers_.begin(); + it != buffers_.end(); ++it) { + // In theory the createBuffers function should have taken + // care that there are no empty buffers added to the + // collection but it is almost always good to make sure. + if (!it->empty()) { + buf.writeData(&(*it)[0], it->size()); + } + } + + // Write suboptions. + packOptions(buf); +} + +void +OptionCustom::pack6(isc::util::OutputBuffer& buf) { + buf.writeUint16(type_); + buf.writeUint16(len() - getHeaderLen()); + + // Write data from buffers. + for (std::vector::const_iterator it = buffers_.begin(); + it != buffers_.end(); ++it) { + if (!it->empty()) { + buf.writeData(&(*it)[0], it->size()); + } + } + + packOptions(buf); +} + +asiolink::IOAddress +OptionCustom::readAddress(const uint32_t index) const { + checkIndex(index); + + // The address being read can be either IPv4 or IPv6. The decision + // is made based on the buffer length. If it holds 4 bytes it is IPv4 + // address, if it holds 16 bytes it is IPv6. + if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) { + return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET)); + } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) { + return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6)); + } else { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " IP address. Invalid buffer length " + << buffers_[index].size() << "."); + } +} + +void +OptionCustom::writeAddress(const asiolink::IOAddress& address, + const uint32_t index) { + using namespace isc::asiolink; + + checkIndex(index); + + if ((address.getFamily() == AF_INET && + buffers_[index].size() != V4ADDRESS_LEN) || + (address.getFamily() == AF_INET6 && + buffers_[index].size() != V6ADDRESS_LEN)) { + isc_throw(BadDataTypeCast, "invalid address specified " + << address.toText() << ". Expected a valid IPv" + << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6") + << " address."); + } + + OptionBuffer buf; + OptionDataTypeUtil::writeAddress(address, buf); + std::swap(buf, buffers_[index]); +} + +const OptionBuffer& +OptionCustom::readBinary(const uint32_t index) const { + checkIndex(index); + return (buffers_[index]); +} + +void +OptionCustom::writeBinary(const OptionBuffer& buf, + const uint32_t index) { + checkIndex(index); + buffers_[index] = buf; +} + +bool +OptionCustom::readBoolean(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readBool(buffers_[index])); +} + +void +OptionCustom::writeBoolean(const bool value, const uint32_t index) { + checkIndex(index); + + buffers_[index].clear(); + OptionDataTypeUtil::writeBool(value, buffers_[index]); +} + +std::string +OptionCustom::readFqdn(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readFqdn(buffers_[index])); +} + +void +OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) { + checkIndex(index); + + // Create a temporay buffer where the FQDN will be written. + OptionBuffer buf; + // Try to write to the temporary buffer rather than to the + // buffers_ member directly guarantees that we don't modify + // (clear) buffers_ until we are sure that the provided FQDN + // is valid. + OptionDataTypeUtil::writeFqdn(fqdn, buf); + // If we got to this point it means that the FQDN is valid. + // We can move the contents of the teporary buffer to the + // target buffer. + std::swap(buffers_[index], buf); +} + +std::string +OptionCustom::readString(const uint32_t index) const { + checkIndex(index); + return (OptionDataTypeUtil::readString(buffers_[index])); +} + +void +OptionCustom::writeString(const std::string& text, const uint32_t index) { + checkIndex(index); + + // Let's clear a buffer as we want to replace the value of the + // whole buffer. If we fail to clear the buffer the data will + // be appended. + buffers_[index].clear(); + // If the text value is empty we can leave because the buffer + // is already empty. + if (!text.empty()) { + OptionDataTypeUtil::writeString(text, buffers_[index]); + } +} + +void +OptionCustom::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + data_ = OptionBuffer(begin, end); + // Chop the buffer stored in data_ into set of sub buffers. + createBuffers(data_); +} + +uint16_t +OptionCustom::len() { + // The length of the option is a sum of option header ... + int length = getHeaderLen(); + + // ... lengths of all buffers that hold option data ... + for (std::vector::const_iterator buf = buffers_.begin(); + buf != buffers_.end(); ++buf) { + length += buf->size(); + } + + // ... and lengths of all suboptions + for (OptionCustom::OptionCollection::iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + + return (length); +} + +void OptionCustom::setData(const OptionBufferConstIter first, + const OptionBufferConstIter last) { + // We will copy entire option buffer, so we have to resize data_. + data_.resize(std::distance(first, last)); + std::copy(first, last, data_.begin()); + + // Chop the data_ buffer into set of buffers that represent + // option fields data. + createBuffers(data_); +} + +std::string OptionCustom::toText(int indent) { + std::stringstream tmp; + + for (int i = 0; i < indent; ++i) + tmp << " "; + + tmp << "type=" << type_ << ", len=" << len()-getHeaderLen() + << ", data fields:" << std::endl; + + OptionDataType data_type = definition_.getType(); + if (data_type == OPT_RECORD_TYPE) { + const OptionDefinition::RecordFieldsCollection& fields = + definition_.getRecordFields(); + + // For record types we iterate over fields defined in + // option definition and match the appropriate buffer + // with them. + for (OptionDefinition::RecordFieldsConstIter field = fields.begin(); + field != fields.end(); ++field) { + for (int j = 0; j < indent + 2; ++j) { + tmp << " "; + } + tmp << "#" << std::distance(fields.begin(), field) << " " + << dataFieldToText(*field, std::distance(fields.begin(), + field)) + << std::endl; + } + } else { + // For non-record types we iterate over all buffers + // and print the data type set globally for an option + // definition. We take the same code path for arrays + // and non-arrays as they only differ in such a way that + // non-arrays have just single data field. + for (unsigned int i = 0; i < getDataFieldsNum(); ++i) { + for (int j = 0; j < indent + 2; ++j) { + tmp << " "; + } + tmp << "#" << i << " " + << dataFieldToText(definition_.getType(), i) + << std::endl; + } + } + + // print suboptions + for (OptionCollection::const_iterator opt = options_.begin(); + opt != options_.end(); + ++opt) { + tmp << (*opt).second->toText(indent+2); + } + return tmp.str(); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h new file mode 100644 index 0000000000..d9a4c18417 --- /dev/null +++ b/src/lib/dhcp/option_custom.h @@ -0,0 +1,359 @@ +// 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 OPTION_CUSTOM_H +#define OPTION_CUSTOM_H + +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Option with defined data fields represented as buffers that can +/// be accessed using data field index. +/// +/// This class represents an option which has defined structure: data fields +/// of specific types and order. Those fields can be accessed using indexes, +/// where index 0 represents first data field within an option. The last +/// field can be accessed using index equal to 'number of fields' - 1. +/// Internally, the option data is stored as a collection of OptionBuffer +/// objects, each representing data for a particular data field. This data +/// can be converted to the actual data type using methods implemented +/// within this class. This class is used to represent those options that +/// can't be represented by any other specialized class (this excludes the +/// Option class which is generic and can be used to represent any option). +class OptionCustom : public Option { +public: + + /// @brief Constructor, used for options to be sent. + /// + /// This constructor creates an instance of an option with default + /// data set for all data fields. The option buffers are allocated + /// according to data size being stored in particular data fields. + /// For variable size data empty buffers are created. + /// + /// @param def option definition. + /// @param u specifies universe (V4 or V6) + OptionCustom(const OptionDefinition& def, Universe u); + + /// @brief Constructor, used for options to be sent. + /// + /// This constructor creates an instance of an option from the whole + /// supplied buffer. This constructor is mainly used to create an + /// instances of options to be stored in outgoing DHCP packets. + /// The buffer used to create the instance of an option can be + /// created from the option data specified in server's configuration. + /// + /// @param def option definition. + /// @param u specifies universe (V4 or V6). + /// @param data content of the option. + /// + /// @throw OutOfRange if option buffer is truncated. + /// + /// @todo list all exceptions thrown by ctor. + OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data); + + /// @brief Constructor, used for received options. + /// + /// This constructor creates an instance an option from the portion + /// of the buffer specified by iterators. This is mainly useful when + /// parsing received packets. Such packets are represented by a single + /// buffer holding option data and all sub options. Methods that are + /// parsing a packet, supply relevant portions of the packet buffer + /// to this constructor to create option instances out of it. + /// + /// @param def option definition. + /// @param u specifies universe (V4 or V6). + /// @param first iterator to the first element that should be copied. + /// @param last iterator to the next element after the last one + /// to be copied. + /// + /// @throw OutOfRange if option buffer is truncated. + /// + /// @todo list all exceptions thrown by ctor. + OptionCustom(const OptionDefinition& def, Universe u, + OptionBufferConstIter first, OptionBufferConstIter last); + + /// @brief Create new buffer and set its value as an IP address. + /// + /// @param address IPv4 or IPv6 address to be written to + /// a buffer being created. + void addArrayDataField(const asiolink::IOAddress& address); + + /// @brief Create new buffer and store boolean value in it. + /// + /// @param value value to be stored in the created buffer. + void addArrayDataField(const bool value); + + /// @brief Create new buffer and store integer value in it. + /// + /// @param value value to be stored in the created buffer. + /// @tparam T integer type of the value being stored. + template + void addArrayDataField(const T value) { + checkArrayType(); + + OptionDataType data_type = definition_.getType(); + if (OptionDataTypeTraits::type != data_type) { + isc_throw(isc::dhcp::InvalidDataType, + "specified data type " << data_type << " does not" + " match the data type in an option definition"); + } + + OptionBuffer buf; + OptionDataTypeUtil::writeInt(value, buf); + buffers_.push_back(buf); + } + + /// @brief Return a number of the data fields. + /// + /// @return number of data fields held by the option. + uint32_t getDataFieldsNum() const { return (buffers_.size()); } + + /// @brief Read a buffer as IP address. + /// + /// @param index buffer index. + /// + /// @return IP address read from a buffer. + /// @throw isc::OutOfRange if index is out of range. + asiolink::IOAddress readAddress(const uint32_t index = 0) const; + + /// @brief Write an IP address into a buffer. + /// + /// @param address IP address being written. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid. + void writeAddress(const asiolink::IOAddress& address, + const uint32_t index = 0); + + /// @brief Read a buffer as binary data. + /// + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @return read buffer holding binary data. + const OptionBuffer& readBinary(const uint32_t index = 0) const; + + /// @brief Write binary data into a buffer. + /// + /// @param buf buffer holding binary data to be written. + /// @param index buffer index. + void writeBinary(const OptionBuffer& buf, const uint32_t index = 0); + + /// @brief Read a buffer as boolean value. + /// + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @return read boolean value. + bool readBoolean(const uint32_t index = 0) const; + + /// @brief Write a boolean value into a buffer. + /// + /// @param value boolean value to be written. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void writeBoolean(const bool value, const uint32_t index = 0); + + /// @brief Read a buffer as FQDN. + /// + /// @param index buffer index. + /// @param [out] len number of bytes read from a buffer. + /// + /// @throw isc::OutOfRange if buffer index is out of range. + /// @throw isc::dhcp::BadDataTypeCast if a buffer being read + /// does not hold a valid FQDN. + /// @return string representation if FQDN. + std::string readFqdn(const uint32_t index = 0) const; + + /// @brief Write an FQDN into a buffer. + /// + /// @param fqdn text representation of FQDN. + /// @param index buffer index. + /// + /// @throw isc::OutOfRange if index is out of range. + void writeFqdn(const std::string& fqdn, const uint32_t index = 0); + + /// @brief Read a buffer as integer value. + /// + /// @param index buffer index. + /// @tparam integer type of a value being returned. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @throw isc::dhcp::InvalidDataType if T is invalid. + /// @return read integer value. + template + T readInteger(const uint32_t index = 0) const { + // Check that the index is not out of range. + checkIndex(index); + // Check that T points to a valid integer type and this type + // is consistent with an option definition. + checkDataType(index); + // When we created the buffer we have checked that it has a + // valid size so this condition here should be always fulfiled. + assert(buffers_[index].size() == OptionDataTypeTraits::len); + // Read an integer value. + return (OptionDataTypeUtil::readInt(buffers_[index])); + } + + /// @brief Write an integer value into a buffer. + /// + /// @param value integer value to be written. + /// @param index buffer index. + /// @tparam T integer type of a value being written. + /// + /// @throw isc::OutOfRange if index is out of range. + /// @throw isc::dhcp::InvalidDataType if T is invalid. + template + void writeInteger(const T value, const uint32_t index = 0) { + // Check that the index is not out of range. + checkIndex(index); + // Check that T points to a valid integer type and this type + // is consistent with an option definition. + checkDataType(index); + // Get some temporary buffer. + OptionBuffer buf; + // Try to write to the buffer. + OptionDataTypeUtil::writeInt(value, buf); + // If successful, replace the old buffer with new one. + std::swap(buffers_[index], buf); + } + + /// @brief Read a buffer as string value. + /// + /// @param index buffer index. + /// + /// @return string value read from buffer. + /// @throw isc::OutOfRange if index is out of range. + std::string readString(const uint32_t index = 0) const; + + /// @brief Write a string value into a buffer. + /// + /// @param text the string value to be written. + /// @param buffer index. + void writeString(const std::string& text, + const uint32_t index = 0); + + /// @brief Parses received buffer. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Returns string representation of the option. + /// + /// @param indent number of spaces before printed text. + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0); + + /// @brief Returns length of the complete option (data length + + /// DHCPv4/DHCPv6 option header) + /// + /// @return length of the option + virtual uint16_t len(); + + /// @brief Sets content of this option from buffer. + /// + /// Option will be resized to length of buffer. + /// + /// @param first iterator pointing begining of buffer to copy. + /// @param last iterator pointing to end of buffer to copy. + void setData(const OptionBufferConstIter first, + const OptionBufferConstIter last); + +protected: + + /// @brief Writes DHCPv4 option in a wire format to a buffer. + /// + /// @param buf output buffer (option will be stored there). + virtual void pack4(isc::util::OutputBuffer& buf); + + /// @brief Writes DHCPv6 option in a wire format to a buffer. + /// + /// @param buf output buffer (built options will be stored here) + virtual void pack6(isc::util::OutputBuffer& buf); + +private: + + /// @brief Verify that the option comprises an array of values. + /// + /// This helper function is used by createArrayEntry functions + /// and throws an exception if the particular option is not + /// an array. + /// + /// @throw isc::InvalidOperation if option is not an array. + inline void checkArrayType() const { + if (!definition_.getArrayType()) { + isc_throw(InvalidOperation, "failed to add new array entry to an" + << " option. The option is not an array."); + } + } + + /// @brief Verify that the integer type is consistent with option + /// field type. + /// + /// This convenience function checks that the data type specified as T + /// is consistent with a type of a data field identified by index. + /// + /// @param index data field index. + /// @tparam data type to be validated. + /// + /// @throw isc::dhcp::InvalidDataType if the type is invalid. + template + void checkDataType(const uint32_t index) const; + + /// @brief Check if data field index is valid. + /// + /// @param index Data field index to check. + /// + /// @throw isc::OutOfRange if index is out of range. + void checkIndex(const uint32_t index) const; + + /// @brief Create a collection of non initialized buffers. + void createBuffers(); + + /// @brief Create collection of buffers representing data field values. + /// + /// @param data_buf a buffer to be parsed. + void createBuffers(const OptionBuffer& data_buf); + + /// @brief Return a text representation of a data field. + /// + /// @param data_type data type of a field. + /// @param index data field buffer index within a custom option. + /// + /// @return text representation of a data field. + std::string dataFieldToText(const OptionDataType data_type, + const uint32_t index) const; + + /// Option definition used to create an option. + OptionDefinition definition_; + + /// The collection of buffers holding data for option fields. + /// The order of buffers corresponds to the order of option + /// fields. + std::vector buffers_; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION_CUSTOM_H diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc new file mode 100644 index 0000000000..0c512d79d1 --- /dev/null +++ b/src/lib/dhcp/option_data_types.cc @@ -0,0 +1,266 @@ +// 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 + +namespace isc { +namespace dhcp { + +OptionDataTypeUtil::OptionDataTypeUtil() { + data_types_["empty"] = OPT_EMPTY_TYPE; + data_types_["binary"] = OPT_BINARY_TYPE; + data_types_["boolean"] = OPT_BOOLEAN_TYPE; + data_types_["int8"] = OPT_INT8_TYPE; + data_types_["int16"] = OPT_INT16_TYPE; + data_types_["int32"] = OPT_INT32_TYPE; + data_types_["uint8"] = OPT_UINT8_TYPE; + data_types_["uint16"] = OPT_UINT16_TYPE; + data_types_["uint32"] = OPT_UINT32_TYPE; + data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE; + data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE; + data_types_["string"] = OPT_STRING_TYPE; + data_types_["fqdn"] = OPT_FQDN_TYPE; + data_types_["record"] = OPT_RECORD_TYPE; + + data_type_names_[OPT_EMPTY_TYPE] = "empty"; + data_type_names_[OPT_BINARY_TYPE] = "binary"; + data_type_names_[OPT_BOOLEAN_TYPE] = "boolean"; + data_type_names_[OPT_INT8_TYPE] = "int8"; + data_type_names_[OPT_INT16_TYPE] = "int16"; + data_type_names_[OPT_INT32_TYPE] = "int32"; + data_type_names_[OPT_UINT8_TYPE] = "uint8"; + data_type_names_[OPT_UINT16_TYPE] = "uint16"; + data_type_names_[OPT_UINT32_TYPE] = "uint32"; + data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address"; + data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address"; + data_type_names_[OPT_STRING_TYPE] = "string"; + data_type_names_[OPT_FQDN_TYPE] = "fqdn"; + data_type_names_[OPT_RECORD_TYPE] = "record"; + // The "unknown" data type is declared here so as + // it can be returned by reference by a getDataTypeName + // function it no other type is suitable. Other than that + // this is unused. + data_type_names_[OPT_UNKNOWN_TYPE] = "unknown"; +} + +OptionDataType +OptionDataTypeUtil::getDataType(const std::string& data_type) { + return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type)); +} + +OptionDataType +OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const { + std::map::const_iterator data_type_it = + data_types_.find(data_type); + if (data_type_it != data_types_.end()) { + return (data_type_it->second); + } + return (OPT_UNKNOWN_TYPE); +} + +int +OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) { + switch (data_type) { + case OPT_BOOLEAN_TYPE: + case OPT_INT8_TYPE: + case OPT_UINT8_TYPE: + return (1); + + case OPT_INT16_TYPE: + case OPT_UINT16_TYPE: + return (2); + + case OPT_INT32_TYPE: + case OPT_UINT32_TYPE: + return (4); + + case OPT_IPV4_ADDRESS_TYPE: + return (asiolink::V4ADDRESS_LEN); + + case OPT_IPV6_ADDRESS_TYPE: + return (asiolink::V6ADDRESS_LEN); + + default: + ; + } + return (0); +} + +const std::string& +OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) { + return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type)); +} + +const std::string& +OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const { + std::map::const_iterator data_type_it = + data_type_names_.find(data_type); + if (data_type_it != data_type_names_.end()) { + return (data_type_it->second); + } + return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second); +} + +OptionDataTypeUtil& +OptionDataTypeUtil::instance() { + static OptionDataTypeUtil instance; + return (instance); +} + +asiolink::IOAddress +OptionDataTypeUtil::readAddress(const std::vector& buf, + const short family) { + using namespace isc::asiolink; + if (family == AF_INET) { + if (buf.size() < V4ADDRESS_LEN) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " IPv4 address. Invalid buffer size: " << buf.size()); + } + return (IOAddress::fromBytes(AF_INET, &buf[0])); + } else if (family == AF_INET6) { + if (buf.size() < V6ADDRESS_LEN) { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + << " IPv6 address. Invalid buffer size: " << buf.size()); + } + return (IOAddress::fromBytes(AF_INET6, &buf[0])); + } else { + isc_throw(BadDataTypeCast, "unable to read data from the buffer as" + "IP address. Invalid family: " << family); + } +} + +void +OptionDataTypeUtil::writeAddress(const asiolink::IOAddress& address, + std::vector& buf) { + // @todo There is a ticket 2396 submitted, which adds the + // functionality to return a buffer representation of + // IOAddress. If so, this function can be simplified. + if (address.getAddress().is_v4()) { + asio::ip::address_v4::bytes_type addr_bytes = + address.getAddress().to_v4().to_bytes(); + // Increase the buffer size by the size of IPv4 address. + buf.resize(buf.size() + addr_bytes.size()); + std::copy_backward(addr_bytes.begin(), addr_bytes.end(), + buf.end()); + } else if (address.getAddress().is_v6()) { + asio::ip::address_v6::bytes_type addr_bytes = + address.getAddress().to_v6().to_bytes(); + // Incresase the buffer size by the size of IPv6 address. + buf.resize(buf.size() + addr_bytes.size()); + std::copy_backward(addr_bytes.begin(), addr_bytes.end(), + buf.end()); + } else { + isc_throw(BadDataTypeCast, "the address " << address.toText() + << " is neither valid IPv4 not IPv6 address."); + } +} + +void +OptionDataTypeUtil::writeBinary(const std::string& hex_str, + std::vector& buf) { + // Binary value means that the value is encoded as a string + // of hexadecimal digits. We need to decode this string + // to the binary format here. + OptionBuffer binary; + try { + util::encode::decodeHex(hex_str, binary); + } catch (const Exception& ex) { + isc_throw(BadDataTypeCast, "unable to cast " << hex_str + << " to binary data type: " << ex.what()); + } + // Decode was successful so append decoded binary value + // to the buffer. + buf.insert(buf.end(), binary.begin(), binary.end()); +} + +bool +OptionDataTypeUtil::readBool(const std::vector& buf) { + if (buf.size() < 1) { + isc_throw(BadDataTypeCast, "unable to read the buffer as boolean" + << " value. Invalid buffer size " << buf.size()); + } + if (buf[0] == 1) { + return (true); + } else if (buf[0] == 0) { + return (false); + } + isc_throw(BadDataTypeCast, "unable to read the buffer as boolean" + << " value. Invalid value " << static_cast(buf[0])); +} + +void +OptionDataTypeUtil::writeBool(const bool value, + std::vector& buf) { + buf.push_back(static_cast(value ? 1 : 0)); +} + +std::string +OptionDataTypeUtil::readFqdn(const std::vector& buf) { + // If buffer is empty emit an error. + if (buf.empty()) { + isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer." + << " The buffer is empty."); + } + // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN. + isc::util::InputBuffer in_buf(static_cast(&buf[0]), buf.size()); + try { + // Try to create an object from the buffer. If exception is thrown + // it means that the buffer doesn't hold a valid domain name (invalid + // syntax). + isc::dns::Name name(in_buf); + return (name.toText()); + } catch (const isc::Exception& ex) { + // Unable to convert the data in the buffer into FQDN. + isc_throw(BadDataTypeCast, ex.what()); + } +} + +void +OptionDataTypeUtil::writeFqdn(const std::string& fqdn, + std::vector& buf) { + try { + isc::dns::Name name(fqdn); + isc::dns::LabelSequence labels(name); + if (labels.getDataLength() > 0) { + size_t read_len = 0; + const uint8_t* data = labels.getData(&read_len); + buf.insert(buf.end(), data, data + read_len); + } + } catch (const isc::Exception& ex) { + isc_throw(BadDataTypeCast, ex.what()); + } +} + +std::string +OptionDataTypeUtil::readString(const std::vector& buf) { + std::string value; + if (!buf.empty()) { + value.insert(value.end(), buf.begin(), buf.end()); + } + return (value); +} + +void +OptionDataTypeUtil::writeString(const std::string& value, + std::vector& buf) { + if (value.size() > 0) { + buf.insert(buf.end(), value.begin(), value.end()); + } +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h new file mode 100644 index 0000000000..ff5789d16e --- /dev/null +++ b/src/lib/dhcp/option_data_types.h @@ -0,0 +1,431 @@ +// 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 OPTION_DATA_TYPES_H +#define OPTION_DATA_TYPES_H + +#include +#include +#include +#include + +#include + +namespace isc { +namespace dhcp { + +/// @brief Exception to be thrown when invalid type specified as template parameter. +class InvalidDataType : public Exception { +public: + InvalidDataType(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception to be thrown when cast to the data type was unsuccessful. +class BadDataTypeCast : public Exception { +public: + BadDataTypeCast(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +/// @brief Data types of DHCP option fields. +/// +/// @warning The order of data types matters: OPT_UNKNOWN_TYPE +/// must always be the last position. Also, OPT_RECORD_TYPE +/// must be at last but one position. This is because some +/// functions perform sanity checks on data type values using +/// '>' operators, assuming that all values beyond the +/// OPT_RECORD_TYPE are invalid. +enum OptionDataType { + OPT_EMPTY_TYPE, + OPT_BINARY_TYPE, + OPT_BOOLEAN_TYPE, + OPT_INT8_TYPE, + OPT_INT16_TYPE, + OPT_INT32_TYPE, + OPT_UINT8_TYPE, + OPT_UINT16_TYPE, + OPT_UINT32_TYPE, + OPT_ANY_ADDRESS_TYPE, + OPT_IPV4_ADDRESS_TYPE, + OPT_IPV6_ADDRESS_TYPE, + OPT_STRING_TYPE, + OPT_FQDN_TYPE, + OPT_RECORD_TYPE, + OPT_UNKNOWN_TYPE +}; + +/// @brief Trait class for data types supported in DHCP option definitions. +/// +/// This is useful to check whether the type specified as template parameter +/// is supported by classes like Option6Int, Option6IntArray and some template +/// factory functions in OptionDefinition class. +template +struct OptionDataTypeTraits { + static const bool valid = false; + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_UNKNOWN_TYPE; +}; + +/// binary type is supported +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_BINARY_TYPE; +}; + +/// bool type is supported +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = sizeof(bool); + static const bool integer_type = false; + static const OptionDataType type = OPT_BOOLEAN_TYPE; +}; + +/// int8_t type is supported. +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = 1; + static const bool integer_type = true; + static const OptionDataType type = OPT_INT8_TYPE; +}; + +/// int16_t type is supported. +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = 2; + static const bool integer_type = true; + static const OptionDataType type = OPT_INT16_TYPE; +}; + +/// int32_t type is supported. +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = 4; + static const bool integer_type = true; + static const OptionDataType type = OPT_INT32_TYPE; +}; + +/// uint8_t type is supported. +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = 1; + static const bool integer_type = true; + static const OptionDataType type = OPT_UINT8_TYPE; +}; + +/// uint16_t type is supported. +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = 2; + static const bool integer_type = true; + static const OptionDataType type = OPT_UINT16_TYPE; +}; + +/// uint32_t type is supported. +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + static const int len = 4; + static const bool integer_type = true; + static const OptionDataType type = OPT_UINT32_TYPE; +}; + +/// IPv4 and IPv6 address type is supported +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + // The len value is used to determine the size of the data + // to be written to an option buffer. IOAddress object may + // either represent an IPv4 or IPv6 addresses which have + // different lengths. Thus we can't put fixed value here. + // The length of a data to be written into an option buffer + // have to be determined in the runtime for a particular + // IOAddress object. Thus setting len to zero. + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_ANY_ADDRESS_TYPE; +}; + +/// string type is supported +template<> +struct OptionDataTypeTraits { + static const bool valid = true; + // The len value is used to determine the size of the data + // to be written to an option buffer. For strings this + // size is unknown until we actually deal with the particular + // string to be written. Thus setting it to zero. + static const int len = 0; + static const bool integer_type = false; + static const OptionDataType type = OPT_STRING_TYPE; +}; + +/// @brief Utility class for option data types. +/// +/// This class provides a set of utility functions to operate on +/// supported DHCP option data types. It includes conversion +/// between enumerator values representing data types and data +/// type names. It also includes a set of functions that write +/// data into option buffers and read data from option buffers. +/// The data being written and read are converted from/to actual +/// data types. +/// @note This is a singleton class but it can be accessed via +/// static methods only. +class OptionDataTypeUtil { +public: + + /// @brief Return option data type from its name. + /// + /// @param data_type data type name. + /// @return option data type. + static OptionDataType getDataType(const std::string& data_type); + + /// @brief Return option data type name from the data type enumerator. + /// + /// @param data_type option data type. + /// @return option data type name. + static const std::string& getDataTypeName(const OptionDataType data_type); + + /// @brief Get data type buffer length. + /// + /// This function returns the size of a particular data type. + /// Values retured by this function correspond to the data type + /// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and + /// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate + /// the fixed length of the data being written into the buffer, + /// not the size of the particular data type. Thus for data types + /// such as string, binary etc. for which the buffer length can't + /// be determined this function returns 0. + /// In addition, this function returns the data sizes for + /// IPV4_ADDRESS_TYPE and IPV6_ADDRESS_TYPE as their buffer + /// representations have fixed data lengths: 4 and 16 respectively. + /// + /// @param data_type data type which size is to be returned. + /// @return data type size or zero for variable length types. + static int getDataTypeLen(const OptionDataType data_type); + + /// @brief Read IPv4 or IPv6 address from a buffer. + /// + /// @param buf input buffer. + /// @param family address family: AF_INET or AF_INET6. + /// + /// @throw isc::dhcp::BadDataTypeCast when the data being read + /// is truncated. + /// @return address being read. + static asiolink::IOAddress readAddress(const std::vector& buf, + const short family); + + /// @brief Append IPv4 or IPv6 address to a buffer. + /// + /// @param address IPv4 or IPv6 address. + /// @param [out] buf output buffer. + static void writeAddress(const asiolink::IOAddress& address, + std::vector& buf); + + /// @brief Append hex-encoded binary values to a buffer. + /// + /// @param hex_str string representing a binary value encoded + /// with hexadecimal digits (without 0x prefix). + /// @param [out] buf output buffer. + static void writeBinary(const std::string& hex_str, + std::vector& buf); + + /// @brief Read boolean value from a buffer. + /// + /// @param buf input buffer. + /// + /// @throw isc::dhcp::BadDataTypeCast when the data being read + /// is truncated or the value is invalid (neither 1 nor 0). + /// @return boolean value read from a buffer. + static bool readBool(const std::vector& buf); + + /// @brief Append boolean value into a buffer. + /// + /// The bool value is encoded in a buffer in such a way that + /// "1" means "true" and "0" means "false". + /// + /// @param value boolean value to be written. + /// @param [out] buf output buffer. + static void writeBool(const bool value, std::vector& buf); + + /// @brief Read integer value from a buffer. + /// + /// @param buf input buffer. + /// @tparam integer type of the returned value. + /// + /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer + /// is truncated. + /// @return integer value being read. + template + static T readInt(const std::vector& buf) { + if (!OptionDataTypeTraits::integer_type) { + isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned" + " by readInteger is unsupported integer type"); + } + + if (buf.size() < OptionDataTypeTraits::len) { + isc_throw(isc::dhcp::BadDataTypeCast, + "failed to read an integer value from a buffer" + << " - buffer is truncated."); + } + + T value; + switch (OptionDataTypeTraits::len) { + case 1: + value = *(buf.begin()); + break; + case 2: + // Calling readUint16 works either for unsigned + // or signed types. + value = isc::util::readUint16(&(*buf.begin())); + break; + case 4: + // Calling readUint32 works either for unsigned + // or signed types. + value = isc::util::readUint32(&(*buf.begin())); + break; + default: + // This should not happen because we made checks on data types + // but it does not hurt to keep throw statement here. + isc_throw(isc::dhcp::InvalidDataType, + "invalid size of the data type to be read as integer."); + } + return (value); + } + + /// @brief Append integer or unsigned integer value to a buffer. + /// + /// @param value an integer value to be written into a buffer. + /// @param [out] buf output buffer. + /// @tparam data type of the value. + template + static void writeInt(const T value, + std::vector& buf) { + if (!OptionDataTypeTraits::integer_type) { + isc_throw(InvalidDataType, "provided data type is not the supported."); + } + switch (OptionDataTypeTraits::len) { + case 1: + buf.push_back(static_cast(value)); + break; + case 2: + buf.resize(buf.size() + 2); + isc::util::writeUint16(static_cast(value), &buf[buf.size() - 2]); + break; + case 4: + buf.resize(buf.size() + 4); + isc::util::writeUint32(static_cast(value), &buf[buf.size() - 4]); + break; + default: + // The cases above cover whole range of possible data lengths because + // we check at the beginning of this function that given data type is + // a supported integer type which can be only 1,2 or 4 bytes long. + ; + } + } + + /// @brief Read FQDN from a buffer as a string value. + /// + /// The format of an FQDN within a buffer complies with RFC1035, + /// section 3.1. + /// + /// @param buf input buffer holding a FQDN. + /// + /// @throw BadDataTypeCast if a FQDN stored within a buffer is + /// invalid (e.g. empty, contains invalid characters, truncated). + /// @return fully qualified domain name in a text form. + static std::string readFqdn(const std::vector& buf); + + /// @brief Append FQDN into a buffer. + /// + /// This method appends the Fully Qualified Domain Name (FQDN) + /// represented as string value into a buffer. The format of + /// the FQDN being stored into a buffer complies with RFC1035, + /// section 3.1. + /// + /// @param fqdn fully qualified domain name to be written. + /// @param [out] buf output buffer. + /// + /// @throw isc::dhcp::BadDataTypeCast if provided FQDN + /// is invalid. + static void writeFqdn(const std::string& fqdn, + std::vector& buf); + + /// @brief Read string value from a buffer. + /// + /// @param buf input buffer. + /// + /// @return string value being read. + static std::string readString(const std::vector& buf); + + /// @brief Write UTF8-encoded string into a buffer. + /// + /// @param value string value to be written into a buffer. + /// @param [out] buf output buffer. + static void writeString(const std::string& value, + std::vector& buf); +private: + + /// The container holding mapping of data type names to + /// data types enumerator. + std::map data_types_; + + /// The container holding mapping of data types to data + /// type names. + std::map data_type_names_; + + /// @brief Private constructor. + /// + /// This constructor is private because this class should + /// be used as singleton (through static public functions). + OptionDataTypeUtil(); + + /// @brief Return instance of OptionDataTypeUtil + /// + /// This function is used by some of the public static functions + /// to create an instance of OptionDataTypeUtil class. + /// When instance is called it calls the class'es constructor + /// and initializes some of the private data members. + /// + /// @return instance of OptionDataTypeUtil singleton. + static OptionDataTypeUtil& instance(); + + /// @brief Return option data type from its name. + /// + /// @param data_type data type name. + /// @return option data type. + OptionDataType getDataTypeImpl(const std::string& data_type) const; + + /// @brief Return option data type name from the data type enumerator. + /// + /// @param data_type option data type. + /// @return option data type name. + const std::string& getDataTypeNameImpl(const OptionDataType data_type) const; +}; + + +} // isc::dhcp namespace +} // isc namespace + +#endif // OPTION_DATA_TYPES_H diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc new file mode 100644 index 0000000000..2248bd751e --- /dev/null +++ b/src/lib/dhcp/option_definition.cc @@ -0,0 +1,473 @@ +// 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 + +using namespace std; +using namespace isc::util; + +namespace isc { +namespace dhcp { + + +OptionDefinition::OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& type, + const bool array_type /* = false */) + : name_(name), + code_(code), + type_(OPT_UNKNOWN_TYPE), + array_type_(array_type) { + // Data type is held as enum value by this class. + // Use the provided option type string to get the + // corresponding enum value. + type_ = OptionDataTypeUtil::getDataType(type); +} + +OptionDefinition::OptionDefinition(const std::string& name, + const uint16_t code, + const OptionDataType type, + const bool array_type /* = false */) + : name_(name), + code_(code), + type_(type), + array_type_(array_type) { +} + +void +OptionDefinition::addRecordField(const std::string& data_type_name) { + OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name); + addRecordField(data_type); +} + +void +OptionDefinition::addRecordField(const OptionDataType data_type) { + if (type_ != OPT_RECORD_TYPE) { + isc_throw(isc::InvalidOperation, "'record' option type must be used" + " to add data fields to the record"); + } + if (data_type >= OPT_RECORD_TYPE || + data_type == OPT_ANY_ADDRESS_TYPE || + data_type == OPT_EMPTY_TYPE) { + isc_throw(isc::BadValue, "attempted to add invalid data type to the record."); + } + record_fields_.push_back(data_type); +} + +OptionPtr +OptionDefinition::optionFactory(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) const { + validate(); + + try { + switch(type_) { + case OPT_EMPTY_TYPE: + return (factoryEmpty(u, type)); + + case OPT_BINARY_TYPE: + return (factoryGeneric(u, type, begin, end)); + + case OPT_UINT8_TYPE: + return (array_type_ ? factoryGeneric(u, type, begin, end) : + factoryInteger(u, type, begin, end)); + + case OPT_INT8_TYPE: + return (array_type_ ? factoryGeneric(u, type, begin, end) : + factoryInteger(u, type, begin, end)); + + case OPT_UINT16_TYPE: + return (array_type_ ? factoryIntegerArray(type, begin, end) : + factoryInteger(u, type, begin, end)); + + case OPT_INT16_TYPE: + return (array_type_ ? factoryIntegerArray(type, begin, end) : + factoryInteger(u, type, begin, end)); + + case OPT_UINT32_TYPE: + return (array_type_ ? factoryIntegerArray(type, begin, end) : + factoryInteger(u, type, begin, end)); + + case OPT_INT32_TYPE: + return (array_type_ ? factoryIntegerArray(type, begin, end) : + factoryInteger(u, type, begin, end)); + + case OPT_IPV4_ADDRESS_TYPE: + // If definition specifies that an option is an array + // of IPv4 addresses we return an instance of specialized + // class (OptionAddrLst4). For non-array types there is no + // specialized class yet implemented so we drop through + // to return an instance of OptionCustom. + if (array_type_) { + return (factoryAddrList4(type, begin, end)); + } + break; + + case OPT_IPV6_ADDRESS_TYPE: + // Handle array type only here (see comments for + // OPT_IPV4_ADDRESS_TYPE case). + if (array_type_) { + return (factoryAddrList6(type, begin, end)); + } + break; + + default: + if (u == Option::V6) { + if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) && + haveIA6Format()) { + // Return Option6IA instance for IA_PD and IA_NA option + // types only. We don't want to return Option6IA for other + // options that comprise 3 UINT32 data fields because + // Option6IA accessors' and modifiers' names are derived + // from the IA_NA and IA_PD options' field names: IAID, + // T1, T2. Using functions such as getIAID, getT1 etc. for + // options other than IA_NA and IA_PD would be bad practice + // and cause confusion. + return (factoryIA6(type, begin, end)); + + } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) { + // Rerurn Option6IAAddr option instance for the IAADDR + // option only for the same reasons as described in + // for IA_NA and IA_PD above. + return (factoryIAAddr6(type, begin, end)); + } + } + } + return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end)))); + + } catch (const Exception& ex) { + isc_throw(InvalidOptionValue, ex.what()); + } +} + +OptionPtr +OptionDefinition::optionFactory(Option::Universe u, uint16_t type, + const OptionBuffer& buf) const { + return (optionFactory(u, type, buf.begin(), buf.end())); +} + +OptionPtr +OptionDefinition::optionFactory(Option::Universe u, uint16_t type, + const std::vector& values) const { + validate(); + + OptionBuffer buf; + if (!array_type_ && type_ != OPT_RECORD_TYPE) { + if (values.empty()) { + isc_throw(InvalidOptionValue, "no option value specified"); + } + writeToBuffer(values[0], type_, buf); + } else if (array_type_ && type_ != OPT_RECORD_TYPE) { + for (size_t i = 0; i < values.size(); ++i) { + writeToBuffer(values[i], type_, buf); + } + } else if (type_ == OPT_RECORD_TYPE) { + const RecordFieldsCollection& records = getRecordFields(); + if (records.size() > values.size()) { + isc_throw(InvalidOptionValue, "number of data fields for the option" + << " type " << type_ << " is greater than number of values" + << " provided."); + } + for (size_t i = 0; i < records.size(); ++i) { + writeToBuffer(values[i], records[i], buf); + } + } + return (optionFactory(u, type, buf.begin(), buf.end())); +} + +void +OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe, + const Option::Universe actual_universe) { + if (expected_universe != actual_universe) { + isc_throw(isc::BadValue, "invalid universe specified for the option"); + } +} + +void +OptionDefinition::validate() const { + std::ostringstream err_str; + if (name_.empty()) { + // Option name must not be empty. + err_str << "option name must not be empty."; + } else if (name_.find(" ") != string::npos) { + // Option name must not contain spaces. + err_str << "option name must not contain spaces."; + } else if (type_ >= OPT_UNKNOWN_TYPE) { + // Option definition must be of a known type. + err_str << "option type value " << type_ << " is out of range."; + } else if (array_type_) { + if (type_ == OPT_STRING_TYPE) { + // Array of strings is not allowed because there is no way + // to determine the size of a particular string and thus there + // it no way to tell when other data fields begin. + err_str << "array of strings is not a valid option definition."; + } else if (type_ == OPT_BINARY_TYPE) { + err_str << "array of binary values is not a valid option definition."; + } else if (type_ == OPT_EMPTY_TYPE) { + err_str << "array of empty value is not a valid option definition."; + } + } else if (type_ == OPT_RECORD_TYPE) { + // At least two data fields should be added to the record. Otherwise + // non-record option definition could be used. + if (getRecordFields().size() < 2) { + err_str << "invalid number of data fields: " << getRecordFields().size() + << " specified for the option of type 'record'. Expected at" + << " least 2 fields."; + } else { + // If the number of fields is valid we have to check if their order + // is valid too. We check that string or binary data fields are not + // laid before other fields. But we allow that they are laid at the end of + // an option. + const RecordFieldsCollection& fields = getRecordFields(); + for (RecordFieldsConstIter it = fields.begin(); + it != fields.end(); ++it) { + if (*it == OPT_STRING_TYPE && + it < fields.end() - 1) { + err_str << "string data field can't be laid before data fields" + << " of other types."; + break; + } + if (*it == OPT_BINARY_TYPE && + it < fields.end() - 1) { + err_str << "binary data field can't be laid before data fields" + << " of other types."; + } + /// Empty type is not allowed within a record. + if (*it == OPT_EMPTY_TYPE) { + err_str << "empty data type can't be stored as a field in an" + << " option record."; + break; + } + } + } + + } + + // Non-empty error string means that we have hit the error. We throw + // exception and include error string. + if (!err_str.str().empty()) { + isc_throw(MalformedOptionDefinition, err_str.str()); + } +} + +bool +OptionDefinition::haveIAx6Format(OptionDataType first_type) const { + return (haveType(OPT_RECORD_TYPE) && + record_fields_.size() == 3 && + record_fields_[0] == first_type && + record_fields_[1] == OPT_UINT32_TYPE && + record_fields_[2] == OPT_UINT32_TYPE); +} + +bool +OptionDefinition::haveIA6Format() const { + // Expect that IA_NA option format is defined as record. + // Although it consists of 3 elements of the same (uint32) + // type it can't be defined as array of uint32 elements because + // arrays do not impose limitations on number of elements in + // the array while this limitation is needed for IA_NA - need + // exactly 3 elements. + return (haveIAx6Format(OPT_UINT32_TYPE)); +} + +bool +OptionDefinition::haveIAAddr6Format() const { + return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE)); +} + +template +T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const { + // Lexical cast in case of our data types make sense only + // for uintX_t, intX_t and bool type. + if (!OptionDataTypeTraits::integer_type && + OptionDataTypeTraits::type != OPT_BOOLEAN_TYPE) { + isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and" + << " non-boolean data type"); + } + // We use the 64-bit value here because it has wider range than + // any other type we use here and it allows to detect out of + // bounds conditions e.g. negative value specified for uintX_t + // data type. Obviously if the value exceeds the limits of int64 + // this function will not handle that properly. + int64_t result = 0; + try { + result = boost::lexical_cast(value_str); + } catch (const boost::bad_lexical_cast& ex) { + // Prepare error message here. + std::string data_type_str = "boolean"; + if (OptionDataTypeTraits::integer_type) { + data_type_str = "integer"; + } + isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str + << " data type for value " << value_str << ": " << ex.what()); + } + // Perform range checks for integer values only (exclude bool values). + if (OptionDataTypeTraits::integer_type) { + if (result > numeric_limits::max() || + result < numeric_limits::min()) { + isc_throw(BadDataTypeCast, "unable to do lexical cast for value " + << value_str << ". This value is expected to be in the range of " + << numeric_limits::min() << ".." << numeric_limits::max()); + } + } + return (static_cast(result)); +} + +void +OptionDefinition::writeToBuffer(const std::string& value, + const OptionDataType type, + OptionBuffer& buf) const { + // We are going to write value given by value argument to the buffer. + // The actual type of the value is given by second argument. Check + // this argument to determine how to write this value to the buffer. + switch (type) { + case OPT_BINARY_TYPE: + OptionDataTypeUtil::writeBinary(value, buf); + return; + case OPT_BOOLEAN_TYPE: + // We encode the true value as 1 and false as 0 on 8 bits. + // That way we actually waste 7 bits but it seems to be the + // simpler way to encode boolean. + // @todo Consider if any other encode methods can be used. + OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck(value), buf); + return; + case OPT_INT8_TYPE: + OptionDataTypeUtil::writeInt(lexicalCastWithRangeCheck(value), + buf); + return; + case OPT_INT16_TYPE: + OptionDataTypeUtil::writeInt(lexicalCastWithRangeCheck(value), + buf); + return; + case OPT_INT32_TYPE: + OptionDataTypeUtil::writeInt(lexicalCastWithRangeCheck(value), + buf); + return; + case OPT_UINT8_TYPE: + OptionDataTypeUtil::writeInt(lexicalCastWithRangeCheck(value), + buf); + return; + case OPT_UINT16_TYPE: + OptionDataTypeUtil::writeInt(lexicalCastWithRangeCheck(value), + buf); + return; + case OPT_UINT32_TYPE: + OptionDataTypeUtil::writeInt(lexicalCastWithRangeCheck(value), + buf); + return; + case OPT_IPV4_ADDRESS_TYPE: + case OPT_IPV6_ADDRESS_TYPE: + { + asiolink::IOAddress address(value); + if (address.getFamily() != AF_INET && + address.getFamily() != AF_INET6) { + isc_throw(BadDataTypeCast, "provided address " << address.toText() + << " is not a valid " + << (address.getAddress().is_v4() ? "IPv4" : "IPv6") + << " address"); + } + OptionDataTypeUtil::writeAddress(address, buf); + return; + } + case OPT_STRING_TYPE: + OptionDataTypeUtil::writeString(value, buf); + return; + case OPT_FQDN_TYPE: + { + // FQDN implementation is not terribly complicated but will require + // creation of some additional logic (maybe object) that will parse + // the fqdn into labels. + isc_throw(isc::NotImplemented, "write of FQDN record into option buffer" + " is not supported yet"); + return; + } + default: + // We hit this point because invalid option data type has been specified + // This may be the case because 'empty' or 'record' data type has been + // specified. We don't throw exception here because it will be thrown + // at the exit point from this function. + ; + } + isc_throw(isc::BadValue, "attempt to write invalid option data field type" + " into the option buffer: " << type); + +} + +OptionPtr +OptionDefinition::factoryAddrList4(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + boost::shared_ptr option(new Option4AddrLst(type, begin, end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryAddrList6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + boost::shared_ptr option(new Option6AddrLst(type, begin, end)); + return (option); +} + + +OptionPtr +OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type) { + OptionPtr option(new Option(u, type)); + return (option); +} + +OptionPtr +OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + OptionPtr option(new Option(u, type, begin, end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryIA6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) { + isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected " + "at least " << Option6IA::OPTION6_IA_LEN << " bytes"); + } + boost::shared_ptr option(new Option6IA(type, begin, end)); + return (option); +} + +OptionPtr +OptionDefinition::factoryIAAddr6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) { + isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected " + " at least " << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes"); + } + boost::shared_ptr option(new Option6IAAddr(type, begin, end)); + return (option); +} + + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h new file mode 100644 index 0000000000..ca404288c9 --- /dev/null +++ b/src/lib/dhcp/option_definition.h @@ -0,0 +1,505 @@ +// 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 OPTION_DEFINITION_H +#define OPTION_DEFINITION_H + +#include +#include + +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Exception to be thrown when invalid option value has been +/// specified for a particular option definition. +class InvalidOptionValue : public Exception { +public: + InvalidOptionValue(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception to be thrown when option definition is invalid. +class MalformedOptionDefinition : public Exception { +public: + MalformedOptionDefinition(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Forward declaration to OptionDefinition. +class OptionDefinition; + +/// @brief Pointer to option definition object. +typedef boost::shared_ptr OptionDefinitionPtr; + +/// @brief Forward declaration to Option6Int. +/// +/// This forward declaration is needed to access Option6Int class +/// without having to include option6_int.h header. This is because +/// this header includes libdhcp++.h and this causes circular +/// inclusion between libdhcp++.h, option_definition.h and +/// option6_int.h. +template +class Option6Int; + +/// @brief Forward declaration to Option6IntArray. +/// +/// This forward declaration is needed to access Option6IntArray class +/// without having to include option6_int_array.h header. This is because +/// this header includes libdhcp++.h and this causes circular +/// inclusion between libdhcp++.h, option_definition.h and +/// option6_int_array.h. +template +class Option6IntArray; + +/// @brief Base class representing a DHCP option definition. +/// +/// This is a base class representing a DHCP option definition, which describes +/// the format of the option. In particular, it defines: +/// - option name, +/// - option code, +/// - data fields order and their types, +/// - sub options space that the particular option encapsulates. +/// +/// The option type specifies the data type(s) which an option conveys. If +/// this is a single value the option type points to the data type of the +/// value. For example, DHCPv6 option 8 comprises a two-byte option code, a +/// two-byte option length and two-byte field that carries a uint16 value +/// (RFC 3315 - http://ietf.org/rfc/rfc3315.txt). In such a case, the option +/// type is defined as "uint16". +/// +/// When the option has a more complex structure, the option type may be +/// defined as "array", "record" or even "array of records". +/// +/// Array types should be used when the option contains multiple contiguous +/// data values of the same type laid. For example, DHCPv6 option 6 includes +/// multiple fields holding uint16 codes of requested DHCPv6 options (RFC 3315). +/// Such an option can be represented with this class by setting the option +/// type to "uint16" and the array indicator (array_type) to true. The number +/// of elements in the array is effectively unlimited (although it is actually +/// limited by the maximal DHCPv6 option length). +/// +/// Should the option comprise data fields of different types, the "record" +/// option type is used. In such cases the data field types within the record +/// are specified using \ref OptionDefinition::addRecordField. +/// +/// When the OptionDefinition object has been sucessfully created, it can be +/// queried to return the appropriate option factory function for the specified +/// specified option format. There are a number of "standard" factory functions +/// that cover well known (common) formats. If the particular format does not +/// match any common format the generic factory function is returned. +/// +/// The following data type strings are supported: +/// - "empty" (option does not contain data fields) +/// - "boolean" +/// - "int8" +/// - "int16" +/// - "int32" +/// - "uint8" +/// - "uint16" +/// - "uint32" +/// - "ipv4-address" (IPv4 Address) +/// - "ipv6-address" (IPV6 Address) +/// - "string" +/// - "fqdn" (fully qualified name) +/// - "record" (set of data fields of different types) +/// +/// @todo Extend the comment to describe "generic factories". +/// @todo Extend this class to use custom namespaces. +/// @todo Extend this class with more factory functions. +class OptionDefinition { +public: + + /// List of fields within the record. + typedef std::vector RecordFieldsCollection; + /// Const iterator for record data fields. + typedef std::vector::const_iterator RecordFieldsConstIter; + + /// @brief Constructor. + /// + /// @param name option name. + /// @param code option code. + /// @param type option data type as string. + /// @param array_type array indicator, if true it indicates that the + /// option fields are the array. + OptionDefinition(const std::string& name, + const uint16_t code, + const std::string& type, + const bool array_type = false); + + /// @brief Constructor. + /// + /// @param name option name. + /// @param code option code. + /// @param type option data type. + /// @param array_type array indicator, if true it indicates that the + /// option fields are the array. + OptionDefinition(const std::string& name, + const uint16_t code, + const OptionDataType type, + const bool array_type = false); + + /// @brief Adds data field to the record. + /// + /// @param data_type_name name of the data type for the field. + /// + /// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE. + /// @throw isc::BadValue if specified invalid data type. + void addRecordField(const std::string& data_type_name); + + /// @brief Adds data field to the record. + /// + /// @param data_type data type for the field. + /// + /// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE. + /// @throw isc::BadValue if specified invalid data type. + void addRecordField(const OptionDataType data_type); + + /// @brief Return array type indicator. + /// + /// The method returns the bool value to indicate whether the option is a + /// a single value or an array of values. + /// + /// @return true if option comprises an array of values. + bool getArrayType() const { return (array_type_); } + + /// @brief Return option code. + /// + /// @return option code. + uint16_t getCode() const { return (code_); } + + /// @brief Return option name. + /// + /// @return option name. + const std::string& getName() const { return (name_); } + + /// @brief Return list of record fields. + /// + /// @return list of record fields. + const RecordFieldsCollection& getRecordFields() const { return (record_fields_); } + + /// @brief Return option data type. + /// + /// @return option data type. + OptionDataType getType() const { return (type_); }; + + /// @brief Check if the option definition is valid. + /// + /// @throw MalformedOptionDefinition option definition is invalid. + void validate() const; + + /// @brief Check if specified format is IA_NA option format. + /// + /// @return true if specified format is IA_NA option format. + bool haveIA6Format() const; + + /// @brief Check if specified format is IAADDR option format. + /// + /// @return true if specified format is IAADDR option format. + bool haveIAAddr6Format() const; + + /// @brief Option factory. + /// + /// This function creates an instance of DHCP option using + /// provided chunk of buffer. This function may be used to + /// create option which is to be sent in the outgoing packet. + /// + /// @param u option universe (V4 or V6). + /// @param type option type. + /// @param begin beginning of the option buffer. + /// @param end end of the option buffer. + /// + /// @return instance of the DHCP option. + /// @throw MalformedOptionDefinition if option definition is invalid. + /// @throw InvalidOptionValue if data for the option is invalid. + OptionPtr optionFactory(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) const; + + /// @brief Option factory. + /// + /// This function creates an instance of DHCP option using + /// whole provided buffer. This function may be used to + /// create option which is to be sent in the outgoing packet. + /// + /// @param u option universe (V4 or V6). + /// @param type option type. + /// @param buf option buffer. + /// + /// @return instance of the DHCP option. + /// @throw MalformedOptionDefinition if option definition is invalid. + /// @throw InvalidOptionValue if data for the option is invalid. + OptionPtr optionFactory(Option::Universe u, uint16_t type, + const OptionBuffer& buf = OptionBuffer()) const; + + /// @brief Option factory. + /// + /// This function creates an instance of DHCP option using the vector + /// of strings which carry data values for option data fields. + /// The order of values in the vector corresponds to the order of data + /// fields in the option. The supplied string values are cast to + /// their actual data types which are determined based on the + /// option definition. If cast fails due to type mismatch, an exception + /// is thrown. This factory function can be used to create option + /// instance when user specified option value in the comma separated + /// values format in the configuration database. Provided string + /// must be tokenized into the vector of string values and this vector + /// can be supplied to this function. + /// + /// @param u option universe (V4 or V6). + /// @param type option type. + /// @param values a vector of values to be used to set data for an option. + /// + /// @return instance of the DHCP option. + /// @throw MalformedOptionDefinition if option definition is invalid. + /// @throw InvalidOptionValue if data for the option is invalid. + OptionPtr optionFactory(Option::Universe u, uint16_t type, + const std::vector& values) const; + + /// @brief Factory to create option with address list. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer + /// with a list of IPv4 addresses. + /// @param end iterator pointing to the end of the buffer with + /// a list of IPv4 addresses. + /// + /// @throw isc::OutOfRange if length of the provided option buffer + /// is not multiple of IPV4 address length. + static OptionPtr factoryAddrList4(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory to create option with address list. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer + /// with a list of IPv6 addresses. + /// @param end iterator pointing to the end of the buffer with + /// a list of IPv6 addresses. + /// + /// @throw isc::OutOfaRange if length of provided option buffer + /// is not multiple of IPV6 address length. + static OptionPtr factoryAddrList6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Empty option factory. + /// + /// @param u universe (V6 or V4). + /// @param type option type. + static OptionPtr factoryEmpty(Option::Universe u, uint16_t type); + + /// @brief Factory to create generic option. + /// + /// @param u universe (V6 or V4). + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + static OptionPtr factoryGeneric(Option::Universe u, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory for IA-type of option. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// + /// @throw isc::OutOfRange if provided option buffer is too short or + /// too long. Expected size is 12 bytes. + /// @throw isc::BadValue if specified universe value is not V6. + static OptionPtr factoryIA6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory for IAADDR-type of option. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// + /// @throw isc::OutOfRange if provided option buffer is too short or + /// too long. Expected size is 24 bytes. + /// @throw isc::BadValue if specified universe value is not V6. + static OptionPtr factoryIAAddr6(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// @brief Factory function to create option with integer value. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// @tparam T type of the data field (must be one of the uintX_t or intX_t). + /// + /// @throw isc::OutOfRange if provided option buffer length is invalid. + template + static OptionPtr factoryInteger(Option::Universe, uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + OptionPtr option(new Option6Int(type, begin, end)); + return (option); + } + + /// @brief Factory function to create option with array of integer values. + /// + /// @param type option type. + /// @param begin iterator pointing to the beginning of the buffer. + /// @param end iterator pointing to the end of the buffer. + /// @tparam T type of the data field (must be one of the uintX_t or intX_t). + /// + /// @throw isc::OutOfRange if provided option buffer length is invalid. + template + static OptionPtr factoryIntegerArray(uint16_t type, + OptionBufferConstIter begin, + OptionBufferConstIter end) { + OptionPtr option(new Option6IntArray(type, begin, end)); + return (option); + } + +private: + + /// @brief Check if specified option format is a record with 3 fields + /// where first one is custom, and two others are uint32. + /// + /// This is a helper function for functions that detect IA_NA and IAAddr + /// option formats. + /// + /// @param first_type type of the first data field. + /// + /// @return true if actual option format matches expected format. + bool haveIAx6Format(const OptionDataType first_type) const; + + /// @brief Check if specified type matches option definition type. + /// + /// @return true if specified type matches option definition type. + inline bool haveType(const OptionDataType type) const { + return (type == type_); + } + + /// @brief Perform lexical cast of the value and validate its range. + /// + /// This function performs lexical cast of a string value to integer + /// or boolean value and checks if the resulting value is within a + /// range of a target type. Note that range checks are not performed + /// on boolean values. The target type should be one of the supported + /// integer types or bool. + /// + /// @param value_str input value given as string. + /// @tparam T target type for lexical cast. + /// + /// @return cast value. + /// @throw BadDataTypeCast if cast was not successful. + template + T lexicalCastWithRangeCheck(const std::string& value_str) const; + + /// @brief Write the string value into the provided buffer. + /// + /// This method writes the given value to the specified buffer. + /// The provided string value may represent data of different types. + /// The actual data type is specified with the second argument. + /// Based on a value of this argument, this function will first + /// try to cast the string value to the particular data type and + /// if it is successful it will store the data in the buffer + /// in a binary format. + /// + /// @param value string representation of the value to be written. + /// @param type the actual data type to be stored. + /// @param [in, out] buf buffer where the value is to be stored. + /// + /// @throw BadDataTypeCast if data write was unsuccessful. + void writeToBuffer(const std::string& value, const OptionDataType type, + OptionBuffer& buf) const; + + /// @brief Sanity check universe value. + /// + /// @param expected_universe expected universe value. + /// @param actual_universe actual universe value. + /// + /// @throw isc::BadValue if expected universe and actual universe don't match. + static inline void sanityCheckUniverse(const Option::Universe expected_universe, + const Option::Universe actual_universe); + + /// Option name. + std::string name_; + /// Option code. + uint16_t code_; + /// Option data type. + OptionDataType type_; + /// Indicates wheter option is a single value or array. + bool array_type_; + /// Collection of data fields within the record. + RecordFieldsCollection record_fields_; +}; + + +/// @brief Multi index container for DHCP option definitions. +/// +/// This container allows to search for DHCP option definition +/// using two indexes: +/// - sequenced: used to access elements in the order they have +/// been added to the container +/// - option code: used to search defintions of options +/// with a specified option code (aka option type). +/// Note that this container can hold multiple options with the +/// same code. For this reason, the latter index can be used to +/// obtain a range of options for a particular option code. +/// +/// @todo: need an index to search options using option space name +/// once option spaces are implemented. +typedef boost::multi_index_container< + // Container comprises elements of OptionDefinition type. + OptionDefinitionPtr, + // Here we start enumerating various indexes. + boost::multi_index::indexed_by< + // Sequenced index allows accessing elements in the same way + // as elements in std::list. Sequenced is an index #0. + boost::multi_index::sequenced<>, + // Start definition of index #1. + boost::multi_index::hashed_non_unique< + // Use option type as the index key. The type is held + // in OptionDefinition object so we have to call + // OptionDefinition::getCode to retrieve this key + // for each element. The option code is non-unique so + // multiple elements with the same option code can + // be returned by this index. + boost::multi_index::const_mem_fun< + OptionDefinition, + uint16_t, + &OptionDefinition::getCode + > + > + > +> OptionDefContainer; + +/// Type of the index #1 - option type. +typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex; +/// Pair of iterators to represent the range of options definitions +/// having the same option type value. The first element in this pair +/// represents the begining of the range, the second element +/// represents the end. +typedef std::pair OptionDefContainerTypeRange; + + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION_DEFINITION_H diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc index 405277d6b0..5be82113a9 100644 --- a/src/lib/dhcp/pkt4.cc +++ b/src/lib/dhcp/pkt4.cc @@ -12,11 +12,12 @@ // 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 #include diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h index b72c03eb5b..e09069cc36 100644 --- a/src/lib/dhcp/pkt4.h +++ b/src/lib/dhcp/pkt4.h @@ -15,14 +15,17 @@ #ifndef PKT4_H #define PKT4_H -#include -#include -#include -#include +#include +#include +#include + #include -#include "asiolink/io_address.h" -#include "util/buffer.h" -#include "dhcp/option.h" +#include + +#include +#include + +#include namespace isc { diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc index 330c97f4c4..2c97b07417 100644 --- a/src/lib/dhcp/pkt6.cc +++ b/src/lib/dhcp/pkt6.cc @@ -12,11 +12,11 @@ // 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 @@ -81,13 +81,6 @@ Pkt6::pack() { bool Pkt6::packUDP() { - - // TODO: Once OutputBuffer is used here, some thing like this - // will be used. Yikes! That's ugly. - // bufferOut_.writeData(ciaddr_.getAddress().to_v6().to_bytes().data(), 16); - // It is better to implement a method in IOAddress that extracts - // vector - try { // DHCPv6 header: message-type (1 octect) + transaction id (3 octets) bufferOut_.writeUint8(msg_type_); @@ -172,17 +165,30 @@ Pkt6::toText() { return tmp.str(); } -boost::shared_ptr +OptionPtr Pkt6::getOption(uint16_t opt_type) { isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type); if (x!=options_.end()) { return (*x).second; } - return boost::shared_ptr(); // NULL + return OptionPtr(); // NULL +} + +isc::dhcp::Option::OptionCollection +Pkt6::getOptions(uint16_t opt_type) { + isc::dhcp::Option::OptionCollection found; + + for (Option::OptionCollection::const_iterator x = options_.begin(); + x != options_.end(); ++x) { + if (x->first == opt_type) { + found.insert(make_pair(opt_type, x->second)); + } + } + return (found); } void -Pkt6::addOption(boost::shared_ptr