Browse Source

[2945]Merge branch 'master' into trac2945

fix merge conflicts
Jeremy C. Reed 11 years ago
parent
commit
bd923e85fd
100 changed files with 13539 additions and 807 deletions
  1. 4 0
      .gitignore
  2. 830 25
      ChangeLog
  3. 9 4
      Makefile.am
  4. 468 229
      configure.ac
  5. 23 19
      doc/Doxyfile
  6. 7 0
      doc/Doxyfile-xml
  7. 2 2
      doc/Makefile.am
  8. 1 0
      doc/design/Makefile.am
  9. 3 0
      doc/design/datasrc/.gitignore
  10. 30 0
      doc/design/datasrc/Makefile.am
  11. 142 0
      doc/design/datasrc/auth-local.txt
  12. 99 0
      doc/design/datasrc/auth-mapped.txt
  13. 366 0
      doc/design/datasrc/data-source-classes.txt
  14. 137 0
      doc/design/datasrc/memmgr-mapped-init.txt
  15. 94 0
      doc/design/datasrc/memmgr-mapped-reload.txt
  16. 68 0
      doc/design/datasrc/overview.txt
  17. 2 2
      doc/design/ipc-high.txt
  18. 347 0
      doc/design/resolver/01-scaling-across-cores
  19. 150 0
      doc/design/resolver/02-mixed-recursive-authority-setup
  20. 256 0
      doc/design/resolver/03-cache-algorithm
  21. 5 0
      doc/design/resolver/README
  22. 162 0
      doc/devel/contribute.dox
  23. 61 11
      doc/devel/mainpage.dox
  24. 1 0
      doc/guide/.gitignore
  25. 3 5
      doc/guide/Makefile.am
  26. 756 258
      doc/guide/bind10-guide.xml
  27. 8 0
      ext/Makefile.am
  28. 6 0
      ext/asio/Makefile.am
  29. 11 0
      ext/asio/README
  30. 310 0
      ext/asio/asio/Makefile.am
  31. 56 5
      m4macros/ax_boost_for_bind10.m4
  32. 17 0
      m4macros/ax_python_sqlite3.m4
  33. 19 2
      m4macros/ax_sqlite3_for_bind10.m4
  34. 2 0
      src/Makefile.am
  35. 13 2
      src/bin/Makefile.am
  36. 2 0
      src/bin/auth/.gitignore
  37. 10 4
      src/bin/auth/Makefile.am
  38. 28 0
      src/bin/auth/auth_messages.mes
  39. 73 25
      src/bin/auth/auth_srv.cc
  40. 11 4
      src/bin/auth/auth_srv.h
  41. 24 3
      src/bin/auth/b10-auth.xml.pre
  42. 278 25
      src/bin/auth/datasrc_clients_mgr.h
  43. 5 4
      src/bin/auth/main.cc
  44. 7 1
      src/bin/auth/query.cc
  45. 12 3
      src/bin/auth/statistics.cc.pre
  46. 18 0
      src/bin/auth/statistics.h
  47. 1 0
      src/bin/auth/statistics_msg_items.def
  48. 80 2
      src/bin/auth/tests/auth_srv_unittest.cc
  49. 265 33
      src/bin/auth/tests/datasrc_clients_builder_unittest.cc
  50. 78 9
      src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
  51. 4 5
      src/bin/auth/tests/datasrc_config_unittest.cc
  52. 77 36
      src/bin/auth/tests/query_unittest.cc
  53. 58 0
      src/bin/auth/tests/statistics_unittest.cc.pre
  54. 11 4
      src/bin/auth/tests/test_datasrc_clients_mgr.cc
  55. 25 5
      src/bin/auth/tests/test_datasrc_clients_mgr.h
  56. 97 30
      src/bin/bind10/init.py.in
  57. 7 0
      src/bin/bind10/init_messages.mes
  58. 1 1
      src/bin/bind10/run_bind10.sh.in
  59. 55 2
      src/bin/bind10/tests/init_test.py.in
  60. 5 0
      src/bin/bindctl/bindcmd.py
  61. 5 1
      src/bin/bindctl/bindctl_main.py.in
  62. 2 1
      src/bin/cfgmgr/b10-cfgmgr.py.in
  63. 0 9
      src/bin/cfgmgr/plugins/tests/datasrc_test.py
  64. 11 4
      src/bin/cmdctl/Makefile.am
  65. 0 2
      src/bin/cmdctl/b10-cmdctl.xml
  66. 22 9
      src/bin/cmdctl/cmdctl.py.in
  67. 0 5
      src/bin/cmdctl/cmdctl.spec.pre.in
  68. 2 0
      src/bin/cmdctl/tests/b10-certgen_test.py
  69. 37 16
      src/bin/cmdctl/tests/cmdctl_test.py
  70. 7 0
      src/bin/d2/.gitignore
  71. 89 0
      src/bin/d2/Makefile.am
  72. 121 0
      src/bin/d2/b10-dhcp-ddns.xml
  73. 31 0
      src/bin/d2/d2_asio.h
  74. 213 0
      src/bin/d2/d2_cfg_mgr.cc
  75. 237 0
      src/bin/d2/d2_cfg_mgr.h
  76. 649 0
      src/bin/d2/d2_config.cc
  77. 959 0
      src/bin/d2/d2_config.h
  78. 68 0
      src/bin/d2/d2_controller.cc
  79. 72 0
      src/bin/d2/d2_controller.h
  80. 27 0
      src/bin/d2/d2_log.cc
  81. 32 0
      src/bin/d2/d2_log.h
  82. 449 0
      src/bin/d2/d2_messages.mes
  83. 394 0
      src/bin/d2/d2_process.cc
  84. 333 0
      src/bin/d2/d2_process.h
  85. 263 0
      src/bin/d2/d2_queue_mgr.cc
  86. 354 0
      src/bin/d2/d2_queue_mgr.h
  87. 221 0
      src/bin/d2/d2_update_message.cc
  88. 341 0
      src/bin/d2/d2_update_message.h
  89. 249 0
      src/bin/d2/d2_update_mgr.cc
  90. 256 0
      src/bin/d2/d2_update_mgr.h
  91. 36 0
      src/bin/d2/d2_zone.cc
  92. 117 0
      src/bin/d2/d2_zone.h
  93. 240 0
      src/bin/d2/d_cfg_mgr.cc
  94. 331 0
      src/bin/d2/d_cfg_mgr.h
  95. 423 0
      src/bin/d2/d_controller.cc
  96. 557 0
      src/bin/d2/d_controller.h
  97. 217 0
      src/bin/d2/d_process.h
  98. 212 0
      src/bin/d2/dhcp-ddns.spec
  99. 262 0
      src/bin/d2/dns_client.cc
  100. 0 0
      src/bin/d2/dns_client.h

+ 4 - 0
.gitignore

@@ -11,6 +11,9 @@ Makefile
 Makefile.in
 TAGS
 
+*.log
+*.trs
+config.h.in~
 /aclocal.m4
 /autom4te.cache/
 /config.guess
@@ -30,6 +33,7 @@ TAGS
 /missing
 /py-compile
 /stamp-h1
+/test-driver
 
 /all.info
 /coverage-cpp-html

File diff suppressed because it is too large
+ 830 - 25
ChangeLog


+ 9 - 4
Makefile.am

@@ -2,7 +2,7 @@ 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.
 
-SUBDIRS = compatcheck doc . src tests m4macros
+SUBDIRS = compatcheck doc . src tests m4macros ext
 USE_LCOV=@USE_LCOV@
 LCOV=@LCOV@
 GENHTML=@GENHTML@
@@ -21,11 +21,11 @@ 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
+	-@tput smso  # Start standout mode
 	@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
+	-@tput rmso  # End standout mode
 
 check-valgrind:
 if HAVE_VALGRIND
@@ -110,7 +110,8 @@ report-coverage: report-cpp-coverage report-python-coverage
 
 # for static C++ check using cppcheck (when available)
 cppcheck:
-	cppcheck --enable=all --suppressions src/cppcheck-suppress.lst --inline-suppr \
+	cppcheck -I./src/lib -I./src/bin --enable=all --suppressions \
+		src/cppcheck-suppress.lst --inline-suppr \
 		--quiet --error-exitcode=1 \
 		--template '{file}:{line}: check_fail: {message} ({severity},{id})' \
 		src
@@ -434,6 +435,10 @@ pkgconfig_DATA = dns++.pc
 
 CLEANFILES = $(abs_top_builddir)/logger_lockfile
 
+# config.h may be included by headers supplied for building user-written
+# hooks libraries, so we need to include it in the distribution.
+pkginclude_HEADERS = config.h
+
 if HAVE_GTEST_SOURCE
 noinst_LIBRARIES = libgtest.a
 libgtest_a_CXXFLAGS = $(GTEST_INCLUDES) $(AM_CXXFLAGS)

+ 468 - 229
configure.ac

@@ -2,15 +2,21 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10, 20130503, bind10-dev@isc.org)
+AC_INIT(bind10, 20130529, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
-# serial-tests is not available in automake version before 1.13. In
-# automake 1.13 and higher, AM_PROG_INSTALL is undefined, so we'll check
-# that and conditionally use serial-tests.
-AM_INIT_AUTOMAKE(
-	[foreign]
-	m4_ifndef([AM_PROG_INSTALL], [serial-tests])
-)
+
+# serial-tests is not available in automake version before 1.13, so
+# we'll check that and conditionally use serial-tests. This check is
+# adopted from code by Richard W.M. Jones:
+# https://www.redhat.com/archives/libguestfs/2013-February/msg00102.html
+m4_define([serial_tests], [
+    m4_esyscmd([automake --version |
+                head -1 |
+                awk '{split ($NF,a,"."); if (a[1] == 1 && a[2] >= 12) { print "serial-tests" }}'
+    ])
+])
+AM_INIT_AUTOMAKE(foreign serial_tests)
+
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIR([m4macros])
@@ -68,6 +74,13 @@ AC_CHECK_DECL([__SUNPRO_CC], [SUNCXX="yes"], [SUNCXX="no"])
 AC_CHECK_DECL([__clang__], [CLANGPP="yes"], [CLANGPP="no"])
 AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
 
+dnl Determine if weare using GNU sed
+GNU_SED=no
+$SED --version 2> /dev/null | grep GNU > /dev/null 2>&1
+if test $? -eq 0; then
+  GNU_SED=yes
+fi
+
 # Linker options
 
 # check -R, "-Wl,-R" or -rpath (we share the AX function defined in
@@ -105,9 +118,12 @@ AC_DEFUN([BIND10_CXX_TRY_FLAG], [
   AC_MSG_RESULT([$bind10_cxx_flag])
 ])
 
+CXX_VERSION="unknown"
+
 # SunStudio compiler requires special compiler options for boost
 # (http://blogs.sun.com/sga/entry/boost_mini_howto)
 if test "$SUNCXX" = "yes"; then
+CXX_VERSION=`$CXX -V 2> /dev/null | head -1`
 CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
 MULTITHREADING_FLAG="-mt"
 fi
@@ -120,7 +136,8 @@ fi
 # 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"
+CXX_VERSION=`$CXX --version 2> /dev/null | head -1`
+B10_CXXFLAGS="$B10_CXXFLAGS -Qunused-arguments"
 fi
 
 BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
@@ -129,7 +146,8 @@ 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"
+CXX_VERSION=`$CXX --version 2> /dev/null | head -1`
+B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wnon-virtual-dtor -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
 case "$host" in
 *-solaris*)
 	MULTITHREADING_FLAG=-pthreads
@@ -181,6 +199,7 @@ AC_HELP_STRING([--enable-static-link],
   [build programs with static link [[default=no]]]),
   [enable_static_link=yes], [enable_static_link=no])
 AM_CONDITIONAL(USE_STATIC_LINK, test $enable_static_link = yes)
+AM_COND_IF([USE_STATIC_LINK], [AC_DEFINE([USE_STATIC_LINK], [1], [BIND 10 was statically linked?])])
 
 # Check validity about some libtool options
 if test $enable_static_link = yes -a $enable_static = no; then
@@ -199,6 +218,7 @@ AC_HELP_STRING([--disable-setproctitle-check],
 # OS dependent configuration
 SET_ENV_LIBRARY_PATH=no
 ENV_LIBRARY_PATH=LD_LIBRARY_PATH
+bind10_undefined_pthread_behavior=no
 
 case "$host" in
 *-solaris*)
@@ -210,13 +230,25 @@ case "$host" in
 	# 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?])
+	bind10_undefined_pthread_behavior=yes
 	;;
 *-apple-darwin*)
 	# Starting with OSX 10.7 (Lion) we must choose which IPv6 API to use
 	# (RFC2292 or RFC3542).
 	CPPFLAGS="$CPPFLAGS -D__APPLE_USE_RFC_3542"
 
+	# In OS X 10.9 (and possibly any future versions?) pthread_cond_destroy
+	# doesn't work as documented, which makes some of unit tests fail.
+	# Testing a specific system and version is not a good practice, but
+	# identifying this behavior would be too heavy (running a program
+	# with multiple threads), so this is a compromise.  In general,
+	# it should be avoided to rely on 'osx_version' unless there's no
+	# viable alternative.
+	osx_version=`/usr/bin/sw_vers -productVersion`
+	if [ test $osx_version = "10.9" ]; then
+		bind10_undefined_pthread_behavior=yes
+	fi
+
 	# libtool doesn't work perfectly with Darwin: libtool embeds the
 	# final install path in dynamic libraries and our loadable python
 	# modules always refer to that path even if it's loaded within the
@@ -239,6 +271,9 @@ esac
 AM_CONDITIONAL(SET_ENV_LIBRARY_PATH, test $SET_ENV_LIBRARY_PATH = yes)
 AC_SUBST(SET_ENV_LIBRARY_PATH)
 AC_SUBST(ENV_LIBRARY_PATH)
+if [ test $bind10_undefined_pthread_behavior = "yes" ]; then
+   AC_DEFINE([HAS_UNDEFINED_PTHREAD_BEHAVIOR], [1], [Does this platform have some undefined pthreads behavior?])
+fi
 
 # Our experiments have shown Solaris 10 has broken support for the
 # IPV6_USE_MIN_MTU socket option for getsockopt(); it doesn't return the value
@@ -375,6 +410,24 @@ fi
 AC_SUBST(PYTHON_LIB)
 LDFLAGS=$LDFLAGS_SAVED
 
+# Python 3.2 changed the return type of internal hash function to
+# Py_hash_t and some platforms (such as Solaris) strictly check for long
+# vs Py_hash_t. So we detect and use the appropriate return type.
+# Remove this test (and associated changes in pydnspp_config.h.in) when
+# we require Python 3.2.
+have_py_hash_t=0
+CPPFLAGS_SAVED="$CPPFLAGS"
+CPPFLAGS=${PYTHON_INCLUDES}
+AC_MSG_CHECKING(for Py_hash_t)
+AC_TRY_COMPILE([#include <Python.h>
+                Py_hash_t h;],,
+    [AC_MSG_RESULT(yes)
+     have_py_hash_t=1],
+    [AC_MSG_RESULT(no)])
+CPPFLAGS="$CPPFLAGS_SAVED"
+HAVE_PY_HASH_T=$have_py_hash_t
+AC_SUBST(HAVE_PY_HASH_T)
+
 # Check for the setproctitle module
 if test "$setproctitle_check" = "yes" ; then
     AC_MSG_CHECKING(for setproctitle module)
@@ -392,7 +445,7 @@ fi
 # 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 "X$GXX" = "Xyes" -a $werror_ok = 1; then
+if test "X$GXX" = "Xyes" -a "$werror_ok" = 1; then
 	CPPFLAGS_SAVED="$CPPFLAGS"
 	CPPFLAGS=${PYTHON_INCLUDES}
 	CXXFLAGS_SAVED="$CXXFLAGS"
@@ -430,6 +483,7 @@ AC_SUBST(B10_CXXFLAGS)
 AC_SEARCH_LIBS(inet_pton, [nsl])
 AC_SEARCH_LIBS(recvfrom, [socket])
 AC_SEARCH_LIBS(nanosleep, [rt])
+AC_SEARCH_LIBS(dlsym, [dl])
 
 # Checks for header files.
 
@@ -469,6 +523,17 @@ AM_COND_IF([OS_BSD], [AC_DEFINE([OS_BSD], [1], [Running on BSD?])])
 AM_CONDITIONAL(OS_SOLARIS, test $OS_TYPE = Solaris)
 AM_COND_IF([OS_SOLARIS], [AC_DEFINE([OS_SOLARIS], [1], [Running on Solaris?])])
 
+# Deal with variants
+AM_CONDITIONAL(OS_FREEBSD, test $system = FreeBSD)
+AM_COND_IF([OS_FREEBSD], [AC_DEFINE([OS_FREEBSD], [1], [Running on FreeBSD?])])
+AM_CONDITIONAL(OS_NETBSD, test $system = NetBSD)
+AM_COND_IF([OS_NETBSD], [AC_DEFINE([OS_NETBSD], [1], [Running on NetBSD?])])
+AM_CONDITIONAL(OS_OPENBSD, test $system = OpenBSD)
+AM_COND_IF([OS_OPENBSD], [AC_DEFINE([OS_OPENBSD], [1], [Running on OpenBSD?])])
+AM_CONDITIONAL(OS_OSX, test $system = Darwin)
+AM_COND_IF([OS_OSX], [AC_DEFINE([OS_OSX], [1], [Running on OSX?])])
+
+
 AC_MSG_CHECKING(for sa_len in struct sockaddr)
 AC_TRY_COMPILE([
 #include <sys/types.h>
@@ -705,6 +770,21 @@ then
             BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}"
     fi
 fi
+
+dnl Determine the Botan version
+AC_MSG_CHECKING([Botan version])
+cat > conftest.cpp << EOF
+#include <botan/version.h>
+AUTOCONF_BOTAN_VERSION=BOTAN_VERSION_MAJOR . BOTAN_VERSION_MINOR . BOTAN_VERSION_PATCH
+EOF
+
+BOTAN_VERSION=`$CPP $CPPFLAGS $BOTAN_INCLUDES conftest.cpp | grep '^AUTOCONF_BOTAN_VERSION=' | $SED -e 's/^AUTOCONF_BOTAN_VERSION=//' -e 's/[[ 	]]//g' -e 's/"//g' 2> /dev/null`
+if test -z "$BOTAN_VERSION"; then
+  BOTAN_VERSION="unknown"
+fi
+$RM -f conftest.cpp
+AC_MSG_RESULT([$BOTAN_VERSION])
+
 # botan-config script (and the way we call pkg-config) returns -L and -l
 # as one string, but we need them in separate values
 BOTAN_LDFLAGS=
@@ -742,7 +822,24 @@ CPPFLAGS_SAVED=$CPPFLAGS
 CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
 LIBS_SAVED="$LIBS"
 LIBS="$LIBS $BOTAN_LIBS"
-AC_CHECK_HEADERS([botan/botan.h],,AC_MSG_ERROR([Missing required header files.]))
+
+# ac_header_preproc is an autoconf symbol (undocumented but stable) that
+# is set if the pre-processor phase passes. Thus by adding a custom
+# failure handler we can detect the difference between a header not existing
+# (or not even passing the pre-processor phase) and a header file resulting
+# in compilation failures.
+AC_CHECK_HEADERS([botan/botan.h],,[
+	if test "x$ac_header_preproc" = "xyes"; then
+		AC_MSG_ERROR([
+botan/botan.h was found but is unusable. The most common cause of this problem
+is attempting to use an updated C++ compiler with older C++ libraries, such as
+the version of Botan that comes with your distribution. If you have updated
+your C++ compiler we highly recommend that you use support libraries such as
+Boost and Botan that were compiled with the same compiler version.])
+	else
+		AC_MSG_ERROR([Missing required header files.])
+	fi]
+)
 AC_LINK_IFELSE(
         [AC_LANG_PROGRAM([#include <botan/botan.h>
                           #include <botan/hash.h>
@@ -784,6 +881,7 @@ if test "$MYSQL_CONFIG" != "" ; then
 
     MYSQL_CPPFLAGS=`$MYSQL_CONFIG --cflags`
     MYSQL_LIBS=`$MYSQL_CONFIG --libs`
+    MYSQL_VERSION=`$MYSQL_CONFIG --version`
 
     AC_SUBST(MYSQL_CPPFLAGS)
     AC_SUBST(MYSQL_LIBS)
@@ -862,6 +960,20 @@ AC_LINK_IFELSE(
          AC_MSG_ERROR([Needs log4cplus library])]
 )
 
+dnl Determine the log4cplus version, used mainly for config.report.
+AC_MSG_CHECKING([log4cplus version])
+cat > conftest.cpp << EOF
+#include <log4cplus/version.h>
+AUTOCONF_LOG4CPLUS_VERSION=LOG4CPLUS_VERSION_STR
+EOF
+
+LOG4CPLUS_VERSION=`$CPP $CPPFLAGS conftest.cpp | grep '^AUTOCONF_LOG4CPLUS_VERSION=' | $SED -e 's/^AUTOCONF_LOG4CPLUS_VERSION=//' -e 's/[[ 	]]//g' -e 's/"//g' 2> /dev/null`
+if test -z "$LOG4CPLUS_VERSION"; then
+  LOG4CPLUS_VERSION="unknown"
+fi
+$RM -f conftest.cpp
+AC_MSG_RESULT([$LOG4CPLUS_VERSION])
+
 CPPFLAGS=$CPPFLAGS_SAVED
 LIBS=$LIBS_SAVED
 
@@ -871,10 +983,14 @@ LIBS=$LIBS_SAVED
 AX_BOOST_FOR_BIND10
 # Boost offset_ptr is required in one library and not optional right now, so
 # we unconditionally fail here if it doesn't work.
-if test "$BOOST_OFFSET_PTR_FAILURE" = "yes"; then
+if test "$BOOST_OFFSET_PTR_WOULDFAIL" = "yes" -a "$werror_ok" = 1; then
     AC_MSG_ERROR([Failed to compile a required header file.  Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror.  See the ChangeLog entry for Trac no. 2147 for more details.])
 fi
 
+if test "$BOOST_STATIC_ASSERT_WOULDFAIL" = "yes" -a X"$werror_ok" = X1; then
+    AC_MSG_ERROR([Failed to use Boost static assertions. Try upgrading Boost to 1.54 or higher (when using GCC 4.8) or specifying --without-werror.  See trac ticket no. 3039 for more details.])
+fi
+
 # There's a known bug in FreeBSD ports for Boost that would trigger a false
 # warning in build with g++ and -Werror (we exclude clang++ explicitly to
 # avoid unexpected false positives).
@@ -882,6 +998,19 @@ if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGP
     AC_MSG_ERROR([Failed to compile a required header file.  If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror.  See the ChangeLog entry for Trac no. 1991 for more details.])
 fi
 
+build_experimental_resolver=no
+AC_ARG_ENABLE(experimental-resolver,
+  [AC_HELP_STRING([--enable-experimental-resolver],
+  [enable building of the experimental resolver [default=no]])],
+  [build_experimental_resolver=$enableval])
+AM_CONDITIONAL([BUILD_EXPERIMENTAL_RESOLVER], [test "$build_experimental_resolver" = "yes"])
+if test "$build_experimental_resolver" = "yes"; then
+   BUILD_EXPERIMENTAL_RESOLVER=yes
+else
+   BUILD_EXPERIMENTAL_RESOLVER=no
+fi
+AC_SUBST(BUILD_EXPERIMENTAL_RESOLVER)
+
 use_shared_memory=yes
 AC_ARG_WITH(shared-memory,
     AC_HELP_STRING([--with-shared-memory],
@@ -891,8 +1020,22 @@ if test X$use_shared_memory = Xyes -a "$BOOST_MAPPED_FILE_WOULDFAIL" = "yes"; th
     AC_MSG_ERROR([Boost shared memory does not compile on this system.  If you don't need it (most normal users won't) build without it by rerunning this script with --without-shared-memory; using a different compiler or a different version of Boost may also help.])
 fi
 AM_CONDITIONAL([USE_SHARED_MEMORY], [test x$use_shared_memory = xyes])
+if test "x$use_shared_memory" = "xyes"; then
+    AC_DEFINE(USE_SHARED_MEMORY, 1, [Define to 1 if shared memory support is enabled])
+fi
 AC_SUBST(BOOST_MAPPED_FILE_CXXFLAG)
 
+if test "$BOOST_OFFSET_PTR_OLD" = "yes" -a "$use_shared_memory" = "yes" ; then
+    AC_MSG_ERROR([You're trying to compile against boost older than 1.48 with
+shared memory. Older versions of boost have a bug which causes segfaults in
+offset_ptr implementation when compiled by GCC with optimisations enabled.
+See ticket no. 3025 for details.
+
+Either update boost to newer version or use --without-shared-memory.
+Note that most users likely don't need shared memory support.
+])
+fi
+
 # Add some default CPP flags needed for Boost, identified by the AX macro.
 CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
 
@@ -910,6 +1053,7 @@ AC_SUBST(MULTITHREADING_FLAG)
 GTEST_LDFLAGS=
 GTEST_LDADD=
 DISTCHECK_GTEST_CONFIGURE_FLAG=
+GTEST_VERSION="unknown"
 
 if test "x$enable_gtest" = "xyes" ; then
 
@@ -965,6 +1109,7 @@ if test "$gtest_path" != "no" ; then
         GTEST_INCLUDES=`${GTEST_CONFIG} --cppflags`
         GTEST_LDFLAGS=`${GTEST_CONFIG} --ldflags`
         GTEST_LDADD=`${GTEST_CONFIG} --libs`
+        GTEST_VERSION=`${GTEST_CONFIG} --version`
         GTEST_FOUND="true"
     else
         AC_MSG_WARN([Unable to locate Google Test gtest-config.])
@@ -1050,6 +1195,8 @@ fi
 AX_SQLITE3_FOR_BIND10
 if test "x$have_sqlite" = "xyes" ; then
   enable_features="$enable_features SQLite3"
+
+  AX_PYTHON_SQLITE3
 fi
 
 #
@@ -1113,6 +1260,11 @@ if test "x$enable_generate_docs" != xno ; then
     fi
     AC_MSG_RESULT(yes)
   fi
+
+  AC_PATH_PROG([ELINKS], [elinks])
+  if test -z "$ELINKS"; then
+    AC_MSG_ERROR("elinks not found; it is required for --enable-generate-docs")
+  fi
 fi
 
 
@@ -1129,6 +1281,14 @@ AC_ARG_ENABLE(logger-checks, [AC_HELP_STRING([--enable-logger-checks],
 AM_CONDITIONAL(ENABLE_LOGGER_CHECKS, test x$enable_logger_checks != xno)
 AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Check logger messages?])])
 
+# Check for asciidoc
+AC_PATH_PROG(ASCIIDOC, asciidoc, no)
+AM_CONDITIONAL(HAVE_ASCIIDOC, test "x$ASCIIDOC" != "xno")
+
+# Check for plantuml
+AC_PATH_PROG(PLANTUML, plantuml, no)
+AM_CONDITIONAL(HAVE_PLANTUML, test "x$PLANTUML" != "xno")
+
 # Check for valgrind
 AC_PATH_PROG(VALGRIND, valgrind, no)
 AM_CONDITIONAL(HAVE_VALGRIND, test "x$VALGRIND" != "xno")
@@ -1165,264 +1325,305 @@ AC_TRY_LINK(
 AM_CONDITIONAL(HAVE_OPTRESET, test "x$var_optreset_exists" != "xno")
 AM_COND_IF([HAVE_OPTRESET], [AC_DEFINE([HAVE_OPTRESET], [1], [Check for optreset?])])
 
-AC_CONFIG_FILES([Makefile
-                 doc/Makefile
+AC_CONFIG_FILES([compatcheck/Makefile
+                 dns++.pc
+                 doc/design/datasrc/Makefile
+                 doc/design/Makefile
                  doc/guide/Makefile
-                 compatcheck/Makefile
-                 src/Makefile
-                 src/bin/Makefile
+                 doc/Makefile
+                 doc/version.ent
+                 ext/asio/asio/Makefile
+                 ext/asio/Makefile
+                 ext/Makefile
+                 m4macros/Makefile
+                 Makefile
+                 src/bin/auth/auth.spec.pre
+                 src/bin/auth/benchmarks/Makefile
+                 src/bin/auth/gen-statisticsitems.py.pre
+                 src/bin/auth/Makefile
+                 src/bin/auth/spec_config.h.pre
+                 src/bin/auth/tests/Makefile
+                 src/bin/auth/tests/testdata/example-base.zone
+                 src/bin/auth/tests/testdata/example-nsec3.zone
+                 src/bin/auth/tests/testdata/example.zone
+                 src/bin/auth/tests/testdata/Makefile
                  src/bin/bind10/bind10
+                 src/bin/bind10/init.py
                  src/bin/bind10/Makefile
+                 src/bin/bind10/run_bind10.sh
+                 src/bin/bind10/tests/init_test.py
                  src/bin/bind10/tests/Makefile
-                 src/bin/cmdctl/Makefile
-                 src/bin/cmdctl/tests/Makefile
+                 src/bin/bindctl/bindctl_main.py
                  src/bin/bindctl/Makefile
+                 src/bin/bindctl/run_bindctl.sh
+                 src/bin/bindctl/tests/bindctl_test
                  src/bin/bindctl/tests/Makefile
-                 src/bin/cfgmgr/Makefile
+                 src/bin/cfgmgr/b10-cfgmgr.py
                  src/bin/cfgmgr/local_plugins/Makefile
+                 src/bin/cfgmgr/Makefile
+                 src/bin/cfgmgr/plugins/datasrc.spec.pre
                  src/bin/cfgmgr/plugins/Makefile
                  src/bin/cfgmgr/plugins/tests/Makefile
+                 src/bin/cfgmgr/tests/b10-cfgmgr_test.py
                  src/bin/cfgmgr/tests/Makefile
+                 src/bin/cmdctl/cmdctl.py
+                 src/bin/cmdctl/cmdctl.spec.pre
+                 src/bin/cmdctl/Makefile
+                 src/bin/cmdctl/run_b10-cmdctl.sh
+                 src/bin/cmdctl/tests/cmdctl_test
+                 src/bin/cmdctl/tests/Makefile
+                 src/bin/d2/Makefile
+                 src/bin/d2/spec_config.h.pre
+                 src/bin/d2/tests/Makefile
+                 src/bin/d2/tests/test_data_files_config.h
+                 src/bin/dbutil/dbutil.py
                  src/bin/dbutil/Makefile
+                 src/bin/dbutil/run_dbutil.sh
+                 src/bin/dbutil/tests/dbutil_test.sh
                  src/bin/dbutil/tests/Makefile
                  src/bin/dbutil/tests/testdata/Makefile
-                 src/bin/loadzone/Makefile
-                 src/bin/loadzone/tests/Makefile
-                 src/bin/loadzone/tests/correct/Makefile
-                 src/bin/msgq/Makefile
-                 src/bin/msgq/tests/Makefile
-                 src/bin/auth/Makefile
-                 src/bin/auth/tests/Makefile
-                 src/bin/auth/tests/testdata/Makefile
-                 src/bin/auth/benchmarks/Makefile
+                 src/bin/ddns/ddns.py
                  src/bin/ddns/Makefile
                  src/bin/ddns/tests/Makefile
-                 src/bin/dhcp6/Makefile
-                 src/bin/dhcp6/tests/Makefile
                  src/bin/dhcp4/Makefile
+                 src/bin/dhcp4/spec_config.h.pre
                  src/bin/dhcp4/tests/Makefile
+                 src/bin/dhcp4/tests/marker_file.h
+                 src/bin/dhcp4/tests/test_data_files_config.h
+                 src/bin/dhcp4/tests/test_libraries.h
+                 src/bin/dhcp6/Makefile
+                 src/bin/dhcp6/spec_config.h.pre
+                 src/bin/dhcp6/tests/Makefile
+                 src/bin/dhcp6/tests/marker_file.h
+                 src/bin/dhcp6/tests/test_data_files_config.h
+                 src/bin/dhcp6/tests/test_libraries.h
+                 src/bin/loadzone/loadzone.py
+                 src/bin/loadzone/Makefile
+                 src/bin/loadzone/run_loadzone.sh
+                 src/bin/loadzone/tests/correct/correct_test.sh
+                 src/bin/loadzone/tests/correct/Makefile
+                 src/bin/loadzone/tests/Makefile
+                 src/bin/Makefile
+                 src/bin/memmgr/Makefile
+                 src/bin/memmgr/memmgr.py
+                 src/bin/memmgr/memmgr.spec.pre
+                 src/bin/memmgr/tests/Makefile
+                 src/bin/msgq/Makefile
+                 src/bin/msgq/msgq.py
+                 src/bin/msgq/run_msgq.sh
+                 src/bin/msgq/tests/Makefile
+                 src/bin/resolver/bench/Makefile
                  src/bin/resolver/Makefile
+                 src/bin/resolver/resolver.spec.pre
+                 src/bin/resolver/spec_config.h.pre
                  src/bin/resolver/tests/Makefile
-                 src/bin/resolver/bench/Makefile
-                 src/bin/sysinfo/Makefile
                  src/bin/sockcreator/Makefile
                  src/bin/sockcreator/tests/Makefile
+                 src/bin/stats/Makefile
+                 src/bin/stats/stats_httpd.py
+                 src/bin/stats/stats.py
+                 src/bin/stats/tests/Makefile
+                 src/bin/stats/tests/testdata/Makefile
+                 src/bin/sysinfo/Makefile
+                 src/bin/sysinfo/run_sysinfo.sh
+                 src/bin/sysinfo/sysinfo.py
+                 src/bin/tests/Makefile
+                 src/bin/tests/process_rename_test.py
+                 src/bin/usermgr/b10-cmdctl-usermgr.py
+                 src/bin/usermgr/Makefile
+                 src/bin/usermgr/run_b10-cmdctl-usermgr.sh
+                 src/bin/usermgr/tests/Makefile
                  src/bin/xfrin/Makefile
+                 src/bin/xfrin/run_b10-xfrin.sh
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrin/tests/testdata/Makefile
+                 src/bin/xfrin/tests/xfrin_test
+                 src/bin/xfrin/xfrin.py
                  src/bin/xfrout/Makefile
+                 src/bin/xfrout/run_b10-xfrout.sh
                  src/bin/xfrout/tests/Makefile
+                 src/bin/xfrout/tests/xfrout_test
+                 src/bin/xfrout/tests/xfrout_test.py
+                 src/bin/xfrout/xfrout.py
+                 src/bin/xfrout/xfrout.spec.pre
                  src/bin/zonemgr/Makefile
+                 src/bin/zonemgr/run_b10-zonemgr.sh
                  src/bin/zonemgr/tests/Makefile
-                 src/bin/stats/Makefile
-                 src/bin/stats/tests/Makefile
-                 src/bin/stats/tests/testdata/Makefile
-                 src/bin/usermgr/Makefile
-                 src/bin/usermgr/tests/Makefile
-                 src/bin/tests/Makefile
-                 src/lib/Makefile
-                 src/lib/asiolink/Makefile
-                 src/lib/asiolink/tests/Makefile
+                 src/bin/zonemgr/tests/zonemgr_test
+                 src/bin/zonemgr/zonemgr.py
+                 src/bin/zonemgr/zonemgr.spec.pre
+                 src/hooks/dhcp/Makefile
+                 src/hooks/dhcp/user_chk/Makefile
+                 src/hooks/dhcp/user_chk/tests/Makefile
+                 src/hooks/dhcp/user_chk/tests/test_data_files_config.h
+                 src/hooks/Makefile
+                 src/lib/acl/Makefile
+                 src/lib/acl/tests/Makefile
                  src/lib/asiodns/Makefile
                  src/lib/asiodns/tests/Makefile
-                 src/lib/bench/Makefile
+                 src/lib/asiolink/Makefile
+                 src/lib/asiolink/tests/Makefile
                  src/lib/bench/example/Makefile
+                 src/lib/bench/Makefile
                  src/lib/bench/tests/Makefile
+                 src/lib/cache/Makefile
+                 src/lib/cache/tests/Makefile
                  src/lib/cc/Makefile
+                 src/lib/cc/session_config.h.pre
                  src/lib/cc/tests/Makefile
-                 src/lib/python/Makefile
-                 src/lib/python/isc/Makefile
+                 src/lib/cc/tests/session_unittests_config.h
+                 src/lib/config/Makefile
+                 src/lib/config/tests/data_def_unittests_config.h
+                 src/lib/config/tests/Makefile
+                 src/lib/config/tests/testdata/Makefile
+                 src/lib/cryptolink/Makefile
+                 src/lib/cryptolink/tests/Makefile
+                 src/lib/datasrc/datasrc_config.h.pre
+                 src/lib/datasrc/Makefile
+                 src/lib/datasrc/memory/benchmarks/Makefile
+                 src/lib/datasrc/memory/Makefile
+                 src/lib/datasrc/tests/Makefile
+                 src/lib/datasrc/tests/memory/Makefile
+                 src/lib/datasrc/tests/memory/testdata/Makefile
+                 src/lib/datasrc/tests/testdata/Makefile
+                 src/lib/dhcp_ddns/Makefile
+                 src/lib/dhcp_ddns/tests/Makefile
+                 src/lib/dhcp/Makefile
+                 src/lib/dhcpsrv/Makefile
+                 src/lib/dhcpsrv/tests/Makefile
+                 src/lib/dhcpsrv/tests/test_libraries.h
+                 src/lib/dhcp/tests/Makefile
+                 src/lib/dns/benchmarks/Makefile
+                 src/lib/dns/gen-rdatacode.py
+                 src/lib/dns/Makefile
+                 src/lib/dns/python/Makefile
+                 src/lib/dns/python/pydnspp_config.h
+                 src/lib/dns/python/tests/Makefile
+                 src/lib/dns/tests/Makefile
+                 src/lib/dns/tests/testdata/Makefile
+                 src/lib/exceptions/Makefile
+                 src/lib/exceptions/tests/Makefile
+                 src/lib/hooks/Makefile
+                 src/lib/hooks/tests/Makefile
+                 src/lib/hooks/tests/marker_file.h
+                 src/lib/hooks/tests/test_libraries.h
+                 src/lib/log/compiler/Makefile
+                 src/lib/log/interprocess/Makefile
+                 src/lib/log/interprocess/tests/Makefile
+                 src/lib/log/Makefile
+                 src/lib/log/tests/buffer_logger_test.sh
+                 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/local_file_test.sh
+                 src/lib/log/tests/logger_lock_test.sh
+                 src/lib/log/tests/Makefile
+                 src/lib/log/tests/severity_test.sh
+                 src/lib/log/tests/tempdir.h
+                 src/lib/Makefile
+                 src/lib/nsas/Makefile
+                 src/lib/nsas/tests/Makefile
+                 src/lib/python/bind10_config.py
                  src/lib/python/isc/acl/Makefile
                  src/lib/python/isc/acl/tests/Makefile
-                 src/lib/python/isc/util/Makefile
-                 src/lib/python/isc/util/tests/Makefile
-                 src/lib/python/isc/util/cio/Makefile
-                 src/lib/python/isc/util/cio/tests/Makefile
-                 src/lib/python/isc/datasrc/Makefile
-                 src/lib/python/isc/datasrc/tests/Makefile
-                 src/lib/python/isc/dns/Makefile
-                 src/lib/python/isc/cc/Makefile
+                 src/lib/python/isc/bind10/Makefile
+                 src/lib/python/isc/bind10/tests/Makefile
                  src/lib/python/isc/cc/cc_generated/Makefile
+                 src/lib/python/isc/cc/Makefile
+                 src/lib/python/isc/cc/tests/cc_test
                  src/lib/python/isc/cc/tests/Makefile
                  src/lib/python/isc/config/Makefile
+                 src/lib/python/isc/config/tests/config_test
                  src/lib/python/isc/config/tests/Makefile
+                 src/lib/python/isc/datasrc/Makefile
+                 src/lib/python/isc/datasrc/tests/Makefile
+                 src/lib/python/isc/datasrc/tests/testdata/Makefile
+                 src/lib/python/isc/ddns/Makefile
+                 src/lib/python/isc/ddns/tests/Makefile
+                 src/lib/python/isc/dns/Makefile
                  src/lib/python/isc/log/Makefile
-                 src/lib/python/isc/log/tests/Makefile
                  src/lib/python/isc/log_messages/Makefile
+                 src/lib/python/isc/log_messages/work/__init__.py
                  src/lib/python/isc/log_messages/work/Makefile
+                 src/lib/python/isc/log/tests/log_console.py
+                 src/lib/python/isc/log/tests/Makefile
+                 src/lib/python/isc/Makefile
+                 src/lib/python/isc/memmgr/Makefile
+                 src/lib/python/isc/memmgr/tests/Makefile
+                 src/lib/python/isc/memmgr/tests/testdata/Makefile
                  src/lib/python/isc/net/Makefile
                  src/lib/python/isc/net/tests/Makefile
                  src/lib/python/isc/notify/Makefile
                  src/lib/python/isc/notify/tests/Makefile
-                 src/lib/python/isc/testutils/Makefile
-                 src/lib/python/isc/bind10/Makefile
-                 src/lib/python/isc/bind10/tests/Makefile
-                 src/lib/python/isc/ddns/Makefile
-                 src/lib/python/isc/ddns/tests/Makefile
-                 src/lib/python/isc/xfrin/Makefile
-                 src/lib/python/isc/xfrin/tests/Makefile
+                 src/lib/python/isc/notify/tests/notify_out_test
                  src/lib/python/isc/server_common/Makefile
                  src/lib/python/isc/server_common/tests/Makefile
-                 src/lib/python/isc/sysinfo/Makefile
-                 src/lib/python/isc/sysinfo/tests/Makefile
                  src/lib/python/isc/statistics/Makefile
                  src/lib/python/isc/statistics/tests/Makefile
-                 src/lib/config/Makefile
-                 src/lib/config/tests/Makefile
-                 src/lib/config/tests/testdata/Makefile
-                 src/lib/cryptolink/Makefile
-                 src/lib/cryptolink/tests/Makefile
-                 src/lib/dns/Makefile
-                 src/lib/dns/tests/Makefile
-                 src/lib/dns/tests/testdata/Makefile
-                 src/lib/dns/python/Makefile
-                 src/lib/dns/python/tests/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
-                 src/lib/datasrc/memory/Makefile
-                 src/lib/datasrc/memory/benchmarks/Makefile
-                 src/lib/datasrc/tests/Makefile
-                 src/lib/datasrc/tests/testdata/Makefile
-                 src/lib/datasrc/tests/memory/Makefile
-                 src/lib/datasrc/tests/memory/testdata/Makefile
-                 src/lib/xfr/Makefile
-                 src/lib/xfr/tests/Makefile
-                 src/lib/log/Makefile
-                 src/lib/log/compiler/Makefile
-                 src/lib/log/tests/Makefile
+                 src/lib/python/isc/sysinfo/Makefile
+                 src/lib/python/isc/sysinfo/tests/Makefile
+                 src/lib/python/isc/testutils/Makefile
+                 src/lib/python/isc/util/cio/Makefile
+                 src/lib/python/isc/util/cio/tests/Makefile
+                 src/lib/python/isc/util/Makefile
+                 src/lib/python/isc/util/tests/Makefile
+                 src/lib/python/isc/xfrin/Makefile
+                 src/lib/python/isc/xfrin/tests/Makefile
+                 src/lib/python/Makefile
                  src/lib/resolve/Makefile
                  src/lib/resolve/tests/Makefile
-                 src/lib/testutils/Makefile
-                 src/lib/testutils/testdata/Makefile
-                 src/lib/nsas/Makefile
-                 src/lib/nsas/tests/Makefile
-                 src/lib/cache/Makefile
-                 src/lib/cache/tests/Makefile
                  src/lib/server_common/Makefile
+                 src/lib/server_common/tests/data_path.h
                  src/lib/server_common/tests/Makefile
-                 src/lib/util/Makefile
+                 src/lib/statistics/Makefile
+                 src/lib/statistics/tests/Makefile
+                 src/lib/testutils/Makefile
+                 src/lib/testutils/testdata/Makefile
                  src/lib/util/io/Makefile
-                 src/lib/util/threads/Makefile
-                 src/lib/util/threads/tests/Makefile
-                 src/lib/util/unittests/Makefile
+                 src/lib/util/Makefile
+                 src/lib/util/python/doxygen2pydoc.py
+                 src/lib/util/python/gen_wiredata.py
                  src/lib/util/python/Makefile
+                 src/lib/util/python/mkpywrapper.py
                  src/lib/util/pyunittests/Makefile
                  src/lib/util/tests/Makefile
-                 src/lib/acl/Makefile
-                 src/lib/acl/tests/Makefile
-                 src/lib/statistics/Makefile
-                 src/lib/statistics/tests/Makefile
+                 src/lib/util/threads/Makefile
+                 src/lib/util/threads/tests/Makefile
+                 src/lib/util/unittests/Makefile
+                 src/lib/xfr/Makefile
+                 src/lib/xfr/tests/Makefile
+                 src/Makefile
+                 tests/lettuce/Makefile
+                 tests/lettuce/setup_intree_bind10.sh
                  tests/Makefile
-                 tests/tools/Makefile
                  tests/tools/badpacket/Makefile
                  tests/tools/badpacket/tests/Makefile
+                 tests/tools/Makefile
                  tests/tools/perfdhcp/Makefile
                  tests/tools/perfdhcp/tests/Makefile
                  tests/tools/perfdhcp/tests/testdata/Makefile
-                 m4macros/Makefile
-                 dns++.pc
-               ])
-AC_OUTPUT([doc/version.ent
-           src/bin/cfgmgr/b10-cfgmgr.py
-           src/bin/cfgmgr/tests/b10-cfgmgr_test.py
-           src/bin/cfgmgr/plugins/datasrc.spec.pre
-           src/bin/cmdctl/cmdctl.py
-           src/bin/cmdctl/run_b10-cmdctl.sh
-           src/bin/cmdctl/tests/cmdctl_test
-           src/bin/cmdctl/cmdctl.spec.pre
-           src/bin/dbutil/dbutil.py
-           src/bin/dbutil/run_dbutil.sh
-           src/bin/dbutil/tests/dbutil_test.sh
-           src/bin/ddns/ddns.py
-           src/bin/xfrin/tests/xfrin_test
-           src/bin/xfrin/xfrin.py
-           src/bin/xfrin/run_b10-xfrin.sh
-           src/bin/xfrout/xfrout.py
-           src/bin/xfrout/xfrout.spec.pre
-           src/bin/xfrout/tests/xfrout_test
-           src/bin/xfrout/tests/xfrout_test.py
-           src/bin/xfrout/run_b10-xfrout.sh
-           src/bin/resolver/resolver.spec.pre
-           src/bin/resolver/spec_config.h.pre
-           src/bin/zonemgr/zonemgr.py
-           src/bin/zonemgr/zonemgr.spec.pre
-           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/init.py
-           src/bin/bind10/run_bind10.sh
-           src/bin/bind10/tests/init_test.py
-           src/bin/bindctl/run_bindctl.sh
-           src/bin/bindctl/bindctl_main.py
-           src/bin/bindctl/tests/bindctl_test
-           src/bin/loadzone/run_loadzone.sh
-           src/bin/loadzone/tests/correct/correct_test.sh
-           src/bin/loadzone/loadzone.py
-           src/bin/usermgr/run_b10-cmdctl-usermgr.sh
-           src/bin/usermgr/b10-cmdctl-usermgr.py
-           src/bin/msgq/msgq.py
-           src/bin/msgq/run_msgq.sh
-           src/bin/auth/auth.spec.pre
-           src/bin/auth/spec_config.h.pre
-           src/bin/auth/tests/testdata/example.zone
-           src/bin/auth/tests/testdata/example-base.zone
-           src/bin/auth/tests/testdata/example-nsec3.zone
-           src/bin/auth/gen-statisticsitems.py.pre
-           src/bin/dhcp4/spec_config.h.pre
-           src/bin/dhcp6/spec_config.h.pre
-           src/bin/tests/process_rename_test.py
-           src/lib/config/tests/data_def_unittests_config.h
-           src/lib/python/isc/config/tests/config_test
-           src/lib/python/isc/cc/tests/cc_test
-           src/lib/python/isc/notify/tests/notify_out_test
-           src/lib/python/isc/log/tests/log_console.py
-           src/lib/python/isc/log_messages/work/__init__.py
-           src/lib/dns/gen-rdatacode.py
-           src/lib/python/bind10_config.py
-           src/lib/cc/session_config.h.pre
-           src/lib/cc/tests/session_unittests_config.h
-           src/lib/datasrc/datasrc_config.h.pre
-           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
-           src/lib/log/tests/tempdir.h
-           src/lib/util/python/mkpywrapper.py
-           src/lib/util/python/gen_wiredata.py
-           src/lib/server_common/tests/data_path.h
-           tests/lettuce/setup_intree_bind10.sh
-          ], [
-           chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
-           chmod +x src/bin/xfrin/run_b10-xfrin.sh
-           chmod +x src/bin/xfrout/run_b10-xfrout.sh
-           chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
+])
+
+ AC_CONFIG_COMMANDS([permissions], [
            chmod +x src/bin/bind10/bind10
            chmod +x src/bin/bind10/run_bind10.sh
+           chmod +x src/bin/bindctl/run_bindctl.sh
+           chmod +x src/bin/bindctl/tests/bindctl_test
+           chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/cmdctl/tests/cmdctl_test
            chmod +x src/bin/dbutil/run_dbutil.sh
            chmod +x src/bin/dbutil/tests/dbutil_test.sh
-           chmod +x src/bin/xfrin/tests/xfrin_test
-           chmod +x src/bin/xfrout/tests/xfrout_test
-           chmod +x src/bin/zonemgr/tests/zonemgr_test
-           chmod +x src/bin/bindctl/tests/bindctl_test
-           chmod +x src/bin/bindctl/run_bindctl.sh
            chmod +x src/bin/loadzone/run_loadzone.sh
            chmod +x src/bin/loadzone/tests/correct/correct_test.sh
+           chmod +x src/bin/msgq/run_msgq.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/xfrin/run_b10-xfrin.sh
+           chmod +x src/bin/xfrin/tests/xfrin_test
+           chmod +x src/bin/xfrout/run_b10-xfrout.sh
+           chmod +x src/bin/xfrout/tests/xfrout_test
+           chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
+           chmod +x src/bin/zonemgr/tests/zonemgr_test
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/log/tests/console_test.sh
            chmod +x src/lib/log/tests/destination_test.sh
@@ -1430,10 +1631,12 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/lib/log/tests/local_file_test.sh
            chmod +x src/lib/log/tests/logger_lock_test.sh
            chmod +x src/lib/log/tests/severity_test.sh
-           chmod +x src/lib/util/python/mkpywrapper.py
-           chmod +x src/lib/util/python/gen_wiredata.py
            chmod +x src/lib/python/isc/log/tests/log_console.py
-          ])
+           chmod +x src/lib/util/python/doxygen2pydoc.py
+           chmod +x src/lib/util/python/gen_wiredata.py
+           chmod +x src/lib/util/python/mkpywrapper.py
+])
+
 AC_OUTPUT
 
 dnl Print the results
@@ -1445,39 +1648,69 @@ cat > config.report << END
     -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 
 Package:
-  Name:          $PACKAGE_NAME
-  Version:       $PACKAGE_VERSION
-
-C++ Compiler:    $CXX
-
-Flags:
-  DEFS:          $DEFS
-  CPPFLAGS:      $CPPFLAGS
-  CXXFLAGS:      $CXXFLAGS
-  LDFLAGS:       $LDFLAGS
-  B10_CXXFLAGS:  $B10_CXXFLAGS
-  OS Family:     $OS_TYPE
-dnl includes too
-  Python:        ${PYTHON_INCLUDES}
-                 ${PYTHON_CXXFLAGS}
-                 ${PYTHON_LDFLAGS}
-                 ${PYTHON_LIB}
-  Boost:         ${BOOST_INCLUDES}
-  Botan:         ${BOTAN_INCLUDES}
-                 ${BOTAN_LDFLAGS}
-                 ${BOTAN_LIBS}
-  Log4cplus:     ${LOG4CPLUS_INCLUDES}
-                 ${LOG4CPLUS_LIBS}
-  SQLite:        $SQLITE_CFLAGS
-                 $SQLITE_LIBS
+  Name:            ${PACKAGE_NAME}
+  Version:         ${PACKAGE_VERSION}
+  OS Family:       ${OS_TYPE}
+  Using GNU sed:   ${GNU_SED}
+
+C++ Compiler:
+  CXX:             ${CXX}
+  CXX_VERSION:     ${CXX_VERSION}
+  DEFS:            ${DEFS}
+  CPPFLAGS:        ${CPPFLAGS}
+  CXXFLAGS:        ${CXXFLAGS}
+  LDFLAGS:         ${LDFLAGS}
+  B10_CXXFLAGS:    ${B10_CXXFLAGS}
+
+Python:
+  PYTHON_VERSION:  ${PYTHON_VERSION}
+  PYTHON_INCLUDES: ${PYTHON_INCLUDES}
+  PYTHON_CXXFLAGS: ${PYTHON_CXXFLAGS}
+  PYTHON_LDFLAGS:  ${PYTHON_LDFLAGS}
+  PYTHON_LIB:      ${PYTHON_LIB}
+
+Boost:
+  BOOST_VERSION:   ${BOOST_VERSION}
+  BOOST_INCLUDES:  ${BOOST_INCLUDES}
+
+Botan:
+  BOTAN_VERSION:   ${BOTAN_VERSION}
+  BOTAN_INCLUDES:  ${BOTAN_INCLUDES}
+  BOTAN_LDFLAGS:   ${BOTAN_LDFLAGS}
+  BOTAN_LIBS:      ${BOTAN_LIBS}
+
+Log4cplus:
+  LOG4CPLUS_VERSION: ${LOG4CPLUS_VERSION}
+  LOG4CPLUS_INCLUDES: ${LOG4CPLUS_INCLUDES}
+  LOG4CPLUS_LIBS:  ${LOG4CPLUS_LIBS}
+
+SQLite:
+  SQLITE_VERSION:  ${SQLITE_VERSION}
+  SQLITE_CFLAGS:   ${SQLITE_CFLAGS}
+  SQLITE_LIBS:     ${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
+
+MySQL:
+  MYSQL_VERSION:   ${MYSQL_VERSION}
+  MYSQL_CPPFLAGS:  ${MYSQL_CPPFLAGS}
+  MYSQL_LIBS:      ${MYSQL_LIBS}
+END
+fi
+
+if test "$enable_gtest" != "no"; then
+cat >> config.report << END
+
+GTest:
+  GTEST_VERSION:   ${GTEST_VERSION}
+  GTEST_INCLUDES:  ${GTEST_INCLUDES}
+  GTEST_LDFLAGS:   ${GTEST_LDFLAGS}
+  GTEST_LDADD:     ${GTEST_LDADD}
+  GTEST_SOURCE:    ${GTEST_SOURCE}
 END
 fi
 
@@ -1500,6 +1733,12 @@ END
 cat config.report
 cat <<EOF
 
-  Now you can type "make" to build BIND 10
+  Now you can type "make" to build BIND 10. Note that if you intend to
+  run "make check", you must run "make" first as some files need to be
+  generated by "make" before "make check" can be run.
+
+  When running "make install" do not use any form of parallel or job
+  server options (such as GNU make's -j option). Doing so may cause
+  errors.
 
 EOF

+ 23 - 19
doc/Doxyfile

@@ -661,34 +661,38 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../src/lib/exceptions \
+INPUT                  = ../src/bin/auth \
+                         ../src/bin/d2 \
+                         ../src/bin/dhcp4 \
+                         ../src/bin/dhcp6 \
+                         ../src/bin/resolver \
+                         ../src/bin/sockcreator \
+                         ../src/hooks/dhcp/user_chk \
+                         ../src/lib/acl \
+                         ../src/lib/asiolink \
+                         ../src/lib/bench \
+                         ../src/lib/cache \
                          ../src/lib/cc \
                          ../src/lib/config \
                          ../src/lib/cryptolink \
-                         ../src/lib/dns \
                          ../src/lib/datasrc \
                          ../src/lib/datasrc/memory \
-                         ../src/bin/auth \
-                         ../src/bin/resolver \
-                         ../src/lib/bench \
+                         ../src/lib/dhcp \
+                         ../src/lib/dhcp_ddns \
+                         ../src/lib/dhcpsrv \
+                         ../src/lib/dns \
+                         ../src/lib/exceptions \
+                         ../src/lib/hooks \
                          ../src/lib/log \
                          ../src/lib/log/compiler \
-                         ../src/lib/asiolink/ \
                          ../src/lib/nsas \
-                         ../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/lib/server_common \
                          ../src/lib/statistics \
-                         ../src/bin/dhcp6 \
-                         ../src/lib/dhcp \
-                         ../src/lib/dhcpsrv \
-                         ../src/bin/dhcp4 \
+                         ../src/lib/testutils \
+                         ../src/lib/util \
+                         ../src/lib/util/io \
+                         ../src/lib/util/threads \
                          ../tests/tools/perfdhcp \
                          devel
 
@@ -775,7 +779,7 @@ EXAMPLE_RECURSIVE      = NO
 # directories that contain image that are included in the documentation (see
 # the \image command).
 
-IMAGE_PATH             = ../doc/images
+IMAGE_PATH             = ../doc/images ../src/lib/hooks/images
 
 # The INPUT_FILTER tag can be used to specify a program that doxygen should
 # invoke to filter for each input file. Doxygen will invoke the filter program

+ 7 - 0
doc/Doxyfile-xml

@@ -0,0 +1,7 @@
+# This is a doxygen configuration for generating XML output as well as HTML.
+#
+# Inherit everything from our default Doxyfile except GENERATE_XML, which
+# will be reset to YES
+
+@INCLUDE = Doxyfile
+GENERATE_XML           = YES

+ 2 - 2
doc/Makefile.am

@@ -1,6 +1,6 @@
-SUBDIRS = guide
+SUBDIRS = guide design
 
-EXTRA_DIST = version.ent.in differences.txt
+EXTRA_DIST = version.ent.in differences.txt Doxyfile Doxyfile-xml
 
 devel:
 	mkdir -p html

+ 1 - 0
doc/design/Makefile.am

@@ -0,0 +1 @@
+SUBDIRS = datasrc

+ 3 - 0
doc/design/datasrc/.gitignore

@@ -0,0 +1,3 @@
+/*.html
+/*.png
+/*.xml

+ 30 - 0
doc/design/datasrc/Makefile.am

@@ -0,0 +1,30 @@
+UML_FILES = \
+	overview.txt \
+	auth-local.txt \
+	auth-mapped.txt \
+	memmgr-mapped-init.txt \
+	memmgr-mapped-reload.txt
+
+TEXT_FILES = \
+	data-source-classes.txt
+
+devel: $(patsubst %.txt, %.png, $(UML_FILES)) $(patsubst %.txt, %.html, $(TEXT_FILES))
+
+.txt.html:
+if HAVE_ASCIIDOC
+	$(AM_V_GEN) $(ASCIIDOC) -n $<
+else
+	@echo "*** asciidoc is required to regenerate $(@) ***"; exit 1;
+endif
+
+.txt.png:
+if HAVE_PLANTUML
+	$(AM_V_GEN) $(PLANTUML) $<
+else
+	@echo "*** plantuml is required to regenerate $(@) ***"; exit 1;
+endif
+
+CLEANFILES = \
+	$(patsubst %.txt, %.png, $(UML_FILES)) \
+	$(patsubst %.txt, %.html, $(TEXT_FILES)) \
+	$(patsubst %.txt, %.xml, $(TEXT_FILES))

+ 142 - 0
doc/design/datasrc/auth-local.txt

@@ -0,0 +1,142 @@
+@startuml
+
+participant auth as ":b10-auth"
+[-> auth: new/initial config\n(datasrc cfg)
+activate auth
+
+participant list as ":Configurable\nClientList"
+create list
+auth -> list: <<construct>>
+
+auth -> list: configure(cfg)
+activate list
+
+participant cache_config as ":CacheConfig"
+create cache_config
+list -> cache_config: <<construct>> (cfg)
+
+participant zt_segment as ":ZoneTable\nSegment\n(Local)"
+create zt_segment
+list -> zt_segment: <<construct>>
+activate zt_segment
+
+participant zone_table as ":ZoneTable"
+create zone_table
+zt_segment -> zone_table: <<construct>>
+
+deactivate zt_segment
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Local segments are\nalways writable
+zt_segment --> list: true
+deactivate zt_segment
+
+loop for each zone in cache_config
+list -> cache_config: getLoadAction()
+activate cache_config
+
+participant la1 as "la1:LoadAction"
+create la1
+cache_config -> la1: <<construct>>
+
+participant la2 as "la2:LoadAction"
+
+cache_config --> list : la1
+
+deactivate cache_config
+
+participant w1 as "w1:ZoneWriter"
+create w1
+list -> w1: <<construct>> (la1)
+
+participant w2 as "w2:ZoneWriter"
+
+list -> w1: load()
+activate w1
+w1 -> la1: (funcall)
+activate la1
+
+participant zd1 as "zd1:ZoneData"
+create zd1
+la1 -> zd1: <<construct>> via helpers
+
+participant zd2 as "zd2:ZoneData"
+
+la1 --> w1: zd1
+deactivate la1
+deactivate w1
+
+list -> w1: install()
+activate w1
+
+w1 -> zone_table: addZone(zd1)
+activate zone_table
+zone_table --> w1: NULL (no old data)
+deactivate zone_table
+
+deactivate w1
+
+end
+
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: reload\n(zonename)
+activate auth
+
+auth -> list: getCachedw1\n(zone_name)
+activate list
+
+list -> cache_config: getLoadAction()
+activate cache_config
+
+create la2
+cache_config -> la2: <<construct>>
+
+cache_config --> list : la2
+
+deactivate cache_config
+
+create w2
+list -> w2: <<construct>> (la2)
+
+list --> auth: w2
+
+deactivate list
+
+
+auth -> w2: load()
+activate w2
+w2 -> la2: (funcall)
+activate la2
+
+create zd2
+la2 -> zd2: <<construct>> via helpers
+
+la2 --> w2: zd2
+deactivate la2
+deactivate w2
+
+auth -> w2: install()
+activate w2
+
+w2 -> zone_table: addZone(zd2)
+activate zone_table
+zone_table --> w2: zd1 (old data)
+deactivate zone_table
+
+deactivate w2
+
+auth -> w2: cleanup()
+activate w2
+
+w2 -> zd1: <<destroy>>
+destroy zd1
+deactivate w2
+
+deactivate auth
+
+@enduml

+ 99 - 0
doc/design/datasrc/auth-mapped.txt

@@ -0,0 +1,99 @@
+@startuml
+
+participant auth as ":b10-auth"
+[-> auth: new/initial config\n(datasrc cfg)
+activate auth
+
+participant list as ":Configurable\nClientList"
+create list
+auth -> list: <<construct>>
+
+auth -> list: configure(cfg)
+activate list
+
+participant CacheConfig as ":CacheConfig"
+create CacheConfig
+list -> CacheConfig: <<construct>> (cfg)
+
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
+create zt_segment
+list -> zt_segment: <<construct>>
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nwhen not reset
+zt_segment --> list: false
+deactivate zt_segment
+
+deactivate list
+
+auth -> list: getStatus()
+activate list
+list --> auth: DataSourceStatus[]
+deactivate list
+
+[<- auth: subscribe to\nmemmgr group
+
+deactivate auth
+
+...
+
+[-> auth: command from\nmemmgr\n(datasrc_name,\nsegmentparam)
+activate auth
+
+auth -> list: resetMemorySegment\n(datasrc_name,\nREAD_ONLY,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_ONLY,\nsegmentparam)
+activate zt_segment
+
+participant segment as "seg1:Memory\nSegment\n(Mapped)"
+create segment
+zt_segment -> segment: <<construct>>
+
+deactivate zt_segment
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: command from\nmemmgr\n(datasrc_name,\nsegmentparam)
+activate auth
+
+auth -> list: resetMemorySegment\n(datasrc_name,\nREAD_ONLY,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_ONLY,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+
+participant segment2 as "seg2:Memory\nSegment\n(Mapped)"
+create segment2
+zt_segment -> segment2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: reload\n(zonename)
+activate auth
+
+auth -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nas it is READ_ONLY
+zt_segment --> list: false
+deactivate zt_segment
+
+list --> auth: CACHE_NOT_WRITABLE
+deactivate list
+
+deactivate auth
+
+@enduml

+ 366 - 0
doc/design/datasrc/data-source-classes.txt

@@ -0,0 +1,366 @@
+Data Source Library Classes
+===========================
+
+About this document
+-------------------
+
+This memo describes major classes used in the data source library,
+mainly focusing on handling in-memory cache with consideration of the
+shared memory support.  It will give an overview of the entire design
+architecture and some specific details of how these classes are expected
+to be used.
+
+Before reading, the higher level inter-module protocol should be understood:
+http://bind10.isc.org/wiki/SharedMemoryIPC
+
+Overall relationships between classes
+-------------------------------------
+
+The following diagram shows major classes in the data source library
+related to in-memory caches and their relationship.
+
+image::overview.png[Class diagram showing overview of relationships]
+
+Major design decisions of this architecture are:
+
+* Keep each class as concise as possible, each focusing on one or
+  small set of responsibilities.  Smaller classes are generally easier
+  to understand (at the cost of understanding how they work in the
+  "big picture" of course) and easier to test.
+
+* On a related point, minimize dependency to any single class.  A
+  monolithic class on which many others are dependent is generally
+  difficult to maintain because you'll need to ensure a change to the
+  monolithic class doesn't break anything on any other classes.
+
+* Use polymorphism for any "fluid" behavior, and hide specific details
+  under abstract interfaces so implementation details won't be
+  directly referenced from any other part of the library.
+  Specifically, the underlying memory segment type (local, mapped, and
+  possibly others) and the source of in-memory data (master file or
+  other data source) are hidden via a kind of polymorphism.
+
+* Separate classes directly used by applications from classes that
+  implement details.  Make the former classes as generic as possible,
+  agnostic about implementation specific details such as the memory
+  segment type (or, ideally and where possible, whether it's for
+  in-memory cache or the underlying data source).
+
+The following give a summarized description of these classes.
+
+* `ConfigurableClientList`: The front end to application classes.  An
+  application that uses the data source library generally maintains
+  one or more `ConfigurableClientList` object (usually one per RR
+  class, or when we support views, probably one per view).  This class
+  is a container of sets of data source related classes, providing
+  accessor to these classes and also acting as a factory of other
+  related class objects.  Note: Due to internal implementation
+  reasons, there is a base class for `ConfigurableClientList` named
+  `ClientList` in the C++ version, and applications are expected to
+  use the latter.  But conceptually `ConfigurableClientList` is an
+  independent value class; the inheritance is not for polymorphism.
+  Note also that the Python version doesn't have the base class.
+
+* `DataSourceInfo`: this is a straightforward tuple of set of class
+  objects corresponding to a single data source, including
+  `DataSourceClient`, `CacheConfig`, and `ZoneTableSegment`.
+  `ConfigurableClientList` maintains a list of `DataSourceInfo`, one
+  for each data source specified in its configuration.
+
+* `DataSourceClient`: The front end class to applications for a single
+  data source.  Applications will get a specific `DataSourceClient`
+  object by `ConfigurableClientList::find()`.
+  `DataSourceClient` itself is a set of factories for various
+  operations on the data source such as lookup or update.
+
+* `CacheConfig`: library internal representation of in-memory cache
+  configuration for a data source.  It knows which zones are to be
+  cached and where the zone data (RRs) should come from, either from a
+  master file or other data source.  With this knowledge it will
+  create an appropriate `LoadAction` object.  Note that `CacheConfig`
+  isn't aware of the underlying memory segment type for the in-memory
+  data.  It's intentionally separated from this class (see the
+  conciseness and minimal-dependency design decisions above).
+
+* `ZoneTableSegment`: when in-memory cache is enabled, it provides
+  memory-segment-type independent interface to the in-memory data.
+  This is an abstract base class (see polymorphism in the design
+  decisions) and inherited by segment-type specific subclasses:
+  `ZoneTableSegmentLocal` and `ZoneTableSegmentMapped` (and possibly
+  others).  Any subclass of `ZoneTableSegment` is expected to maintain
+  the specific type of `MemorySegment` object.
+
+* `ZoneWriter`: a frontend utility class for applications to update
+  in-memory zone data (currently it can only load a whole zone and
+  replace any existing zone content with a new one, but this should be
+  extended so it can handle partial updates).
+  Applications will get a specific `ZoneWriter`
+  object by `ConfigurableClientList::getCachedZoneWriter()`.
+  `ZoneWriter` is constructed with `ZoneableSegment` and `LoadAction`.
+  Since these are abstract classes, `ZoneWriter` doesn't have to be
+  aware of "fluid" details.  It's only responsible for "somehow" preparing
+  `ZoneData` for a new version of a specified zone using `LoadAction`,
+  and installing it in the `ZoneTable` (which can be accessed via
+  `ZoneTableSegment`).
+
+* `DataSourceStatus`: created by `ConfigurableClientList::getStatus()`,
+  a straightforward tuple that represents some status information of a
+  specific data source managed in the `ConfigurableClientList`.
+  `getStatus()` generates `DataSourceStatus` for all data sources
+  managed in it, and returns them as a vector.
+
+* `ZoneTableAccessor`, `ZoneTableIterator`: frontend classes to get
+  access to the conceptual "zone table" (a set of zones) stored in a
+  specific data source.  In particular, `ZoneTableIterator` allows
+  applications to iterate over all zones (by name) stored in the
+  specific data source.
+  Applications will get a specific `ZoneTableAccessor`
+  object by `ConfigurableClientList::getZoneTableAccessor()`,
+  and get an iterator object by calling `getIterator` on the accessor.
+  These are abstract classes and provide unified interfaces
+  independent from whether it's for in-memory cached zones or "real"
+  underlying data source.  But the initial implementation only
+  provides the in-memory cache version of subclass (see the next
+  item).
+
+* `ZoneTableAccessorCache`, `ZoneTableIteratorCache`: implementation
+  classes of `ZoneTableAccessor` and `ZoneTableIterator` for in-memory
+  cache.  They refer to `CacheConfig` to get a list of zones to be
+  cached.
+
+* `ZoneTableHeader`, `ZoneTable`: top-level interface to actual
+  in-memory data.  These were separated based on a prior version of
+  the design (http://bind10.isc.org/wiki/ScalableZoneLoadDesign) where
+  `ZoneTableHeader` may contain multiple `ZoneTable`s.  It's
+  one-to-one relationship in the latest version (of implementation),
+  so we could probably unify them as a cleanup.
+
+* `ZoneData`: representing the in-memory content of a single zone.
+  `ZoneTable` contains (zero, one or) multiple `ZoneData` objects.
+
+* `RdataSet`: representing the in-memory content of (data of) a single
+  RRset.
+  `ZoneData` contains `RdataSet`s corresponding to the RRsets stored
+  in the zone.
+
+* `LoadAction`: a "polymorphic" functor that implements loading zone
+  data into memory.  It hides from its user (i.e., `ZoneWriter`)
+  details about the source of the data: master file or other data
+  source (and perhaps some others).  The "polymorphism" is actually
+  realized as different implementations of the functor interface, not
+  class inheritance (but conceptually the effect and goal is the
+  same).  Note: there's a proposal to replace `LoadAction` with
+  a revised `ZoneDataLoader`, although the overall concept doesn't
+  change.  See Trac ticket #2912.
+
+* `ZoneDataLoader` and `ZoneDataUpdater`: helper classes for the
+  `LoadAction` functor(s).  These work independently from the source
+  of data, taking a sequence of RRsets objects, converting them
+  into the in-memory data structures (`RdataSet`), and installing them
+  into a newly created `ZoneData` object.
+
+Sequence for auth module using local memory segment
+---------------------------------------------------
+
+In the remaining sections, we explain how the classes shown in the
+previous section work together through their methods for commonly
+intended operations.
+
+The following sequence diagram shows the case for the authoritative
+DNS server module to maintain "local" in-memory data.  Note that
+"auth" is a conceptual "class" (not actually implemented as a C++
+class) to represent the server application behavior.  For the purpose
+of this document that should be sufficient.  The same note applies to
+all examples below.
+
+image::auth-local.png[Sequence diagram for auth server using local memory segment]
+
+1. On startup, the auth module creates a `ConfigurableClientList`
+   for each RR class specified in the configuration for "data_sources"
+   module.  It then calls `ConfigurableClientList::configure()`
+   for the given configuration of that RR class.
+
+2. For each data source, `ConfigurableClientList` creates a
+   `CacheConfig` object with the corresponding cache related
+   configuration.
+
+3. If in-memory cache is enabled for the data source,
+   `ZoneTableSegment` is also created.  In this scenario the cache
+   type is specified as "local" in the configuration, so a functor
+   creates `ZoneTableSegmentLocal` as the actual instance.
+   In this case its `ZoneTable` is immediately created, too.
+
+4. `ConfigurableClientList` checks if the created `ZoneTableSegment` is
+   writable.  It is always so for "local" type of segments.  So
+   `ConfigurableClientList` immediately loads zones to be cached into
+   memory.  For each such zone, it first gets the appropriate
+   `LoadAction` through `CacheConfig`, then creates `ZoneWriter` with
+   the `LoadAction`, and loads the data using the writer.
+
+5. If the auth module receives a "reload" command for a cached zone
+   from other module (xfrin, an end user, etc), it calls
+   `ConfigurableClientList::getCachedZoneWriter` to load and install
+   the new version of the zone.  The same loading sequence takes place
+   except that the user of the writer is the auth module.
+   Also, the old version of the zone data is destroyed at the end of
+   the process.
+
+Sequence for auth module using mapped memory segment
+----------------------------------------------------
+
+This is an example for the authoritative server module that uses
+mapped type memory segment for in-memory data.
+
+image::auth-mapped.png[Sequence diagram for auth server using mapped memory segment]
+
+1. The sequence is the same to the point of creating `CacheConfig`.
+
+2. But in this case a `ZoneTableSegmentMapped` object is created based
+   on the configuration of the cache type.  This type of
+   `ZoneTableSegment` is initially empty and isn't even associated
+   with a `MemorySegment` (and therefore considered non-writable).
+
+3. `ConfigurableClientList` checks if the zone table segment is
+   writable to know whether to load zones into memory by itself,
+   but as `ZoneTableSegment::isWritable()` returns false, it skips
+   the loading.
+
+4. The auth module gets the status of each data source, and notices
+   there's a `WAITING` state of segment. So it subscribes to the
+   "Memmgr" group on a command session and waits for an update
+   from the memory manager (memmgr) module. (See also the note at the
+   end of the section)
+
+5. When the auth module receives an update command from memmgr, it
+   calls `ConfigurableClientList::resetMemorySegment()` with the command
+   argument and the segment mode of `READ_ONLY`.
+   Note that the auth module handles the command argument as mostly
+   opaque data; it's not expected to deal with details of segment
+   type-specific behavior. If the reset fails, auth aborts (as there's
+   no clear way to handle the failure).
+
+6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
+   `reset()` method on the corresponding `ZoneTableSegment` with the
+   given parameters.
+   In the case of `ZoneTableSegmentMapped`, it creates a new
+   `MemorySegment` object for the mapped type, which internally maps
+   the specific file into memory.
+   memmgr is expected to have prepared all necessary data in the file,
+   so all the data are immediately ready for use (i.e., there
+   shouldn't be any explicit load operation).
+
+7. When a change is made in the mapped data, memmgr will send another
+   update command with parameters for new mapping.  The auth module
+   calls `ConfigurableClientList::resetMemorySegment()`, and the
+   underlying memory segment is swapped with a new one.  The old
+   memory segment object is destroyed.  Note that
+   this "destroy" just means unmapping the memory region; the data
+   stored in the file are intact. Again, if mapping fails, auth
+   aborts.
+
+8. If the auth module happens to receive a reload command from other
+   module, it could call
+   `ConfigurableClientList::getCachedZoneWriter()`
+   to reload the data by itself, just like in the previous section.
+   In this case, however, the writability check of
+   `getCachedZoneWriter()` fails (the segment was created as
+   `READ_ONLY` and is non-writable), so loading won't happen.
+
+NOTE: While less likely in practice, it's possible that the same auth
+module uses both "local" and "mapped" (and even others) type of
+segments for different data sources.  In such cases the sequence is
+either the one in this or previous section depending on the specified
+segment type in the configuration.  The auth module itself isn't aware
+of per segment-type details, but changes the behavior depending on the
+segment state of each data source at step 4 above: if it's `WAITING`,
+it means the auth module needs help from memmgr (that's all the auth
+module should know; it shouldn't be bothered with further details such
+as mapped file names); if it's something else, the auth module doesn't
+have to do anything further.
+
+Sequence for memmgr module initialization using mapped memory segment
+---------------------------------------------------------------------
+
+This sequence shows the common initialization sequence for the
+memory manager (memmgr) module using a mapped type memory segment.
+This is a mixture of the sequences shown in Sections 2 and 3.
+
+image::memmgr-mapped-init.png[]
+
+1. Initial sequence is the same until the application module (memmgr)
+   calls `ConfigurableClientList::getStatus()` as that for the
+   previous section.
+
+2. The memmgr module identifies the data sources whose in-memory cache
+   type is "mapped".  (Unlike other application modules, the memmgr
+   should know what such types means due to its exact responsibility).
+   For each such data source, it calls
+   `ConfigurableClientList::resetMemorySegment` with the READ_WRITE
+   mode and other mapped-type specific parameters.  memmgr should be
+   able to generate the parameters from its own configuration and
+   other data source specific information (such as the RR class and
+   data source name).
+
+3. The `ConfigurableClientList` class calls
+   `ZoneTableSegment::reset()` on the corresponding zone table
+   segment with the given parameters.  In this case, since the mode is
+   READ_WRITE, a new `ZoneTable` will be created (assuming this is a
+   very first time initialization; if there's already a zone table
+   in the segment, it will be used).
+
+4. The memmgr module then calls
+   `ConfigurableClientList::getZoneTableAccessor()`, and calls the
+   `getItertor()` method on it to get a list of zones for which
+   zone data are to be loaded into the memory segment.
+
+5. The memmgr module loads the zone data for each such zone.  This
+   sequence is the same as shown in Section 2.
+
+6. On loading all zone data, the memmgr module sends an update command
+   to all interested modules (such as auth) in the segment, and waits
+   for acknowledgment from all of them.
+
+7. Then it calls `ConfigurableClientList::resetMemorySegment()` for
+   this data source with almost the same parameter as step 2 above,
+   but with a different mapped file name.  This will make a swap of
+   the underlying memory segment with a new mapping.  The old
+   `MemorySegment` object will be destroyed, but as explained in the
+   previous section, it simply means unmapping the file.
+
+8. The memmgr loads the zone data into the newly mapped memory region
+   by repeating the sequence shown in step 5.
+
+9. The memmgr repeats all this sequence for data sources that use
+   "mapped" segment for in-memory cache.  Note: it could handle
+   multiple data sources in parallel, e.g., while waiting for
+   acknowledgment from other modules.
+
+Sequence for memmgr module to reload a zone using mapped memory segment
+-----------------------------------------------------------------------
+
+This example is a continuation of the previous section, describing how
+the memory manager reloads a zone in mapped memory segment.
+
+image::memmgr-mapped-reload.png[]
+
+1. When the memmgr module receives a reload command from other module,
+   it calls `ConfigurableClientList::getCachedZoneWriter()` for the
+   specified zone name.  This method checks the writability of
+   the segment, and since it's writable (as memmgr created it in the
+   READ_WRITE mode), `getCachedZoneWriter()` succeeds and returns
+   a `ZoneWriter`.
+
+2. The memmgr module uses the writer to load the new version of zone
+   data.  There is nothing specific to mapped-type segment here.
+
+3. The memmgr module then sends an update command to other modules
+   that would share this version, and waits for acknowledgment from
+   all of them.
+
+4. On getting acknowledgments, the memmgr module calls
+  `ConfigurableClientList::resetMemorySegment()` with the parameter
+   specifying the other mapped file.  This will swap the underlying
+   `MemorySegment` with a newly created one, mapping the other file.
+
+5. The memmgr updates this segment, too, so the two files will contain
+   the same version of data.

+ 137 - 0
doc/design/datasrc/memmgr-mapped-init.txt

@@ -0,0 +1,137 @@
+@startuml
+
+participant memmgr as ":b10-memmgr"
+[-> memmgr: new/initial config\n(datasrc cfg)
+activate memmgr
+
+participant list as ":Configurable\nClientList"
+create list
+memmgr -> list: <<construct>>
+
+memmgr -> list: configure(cfg)
+activate list
+
+participant CacheConfig as ":CacheConfig"
+create CacheConfig
+list -> CacheConfig: <<construct>> (cfg)
+
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
+create zt_segment
+list -> zt_segment: <<construct>>
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nwhen not reset
+zt_segment --> list: false
+deactivate zt_segment
+
+deactivate list
+
+memmgr -> list: getStatus()
+activate list
+list --> memmgr: DataSourceStatus[]
+deactivate list
+
+loop for each datasrc with mapped segment
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+participant segment as "seg1:Memory\nSegment\n(Mapped)"
+create segment
+zt_segment -> segment: <<construct>>
+
+participant segment.2 as "seg2:Memory\nSegment\n(Mapped)"
+
+participant ZoneTable as ":ZoneTable"
+create ZoneTable
+zt_segment -> ZoneTable: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+memmgr -> list: getZoneTableAccessor\n(datasrc_name,\ncache=true)
+activate list
+list -> memmgr: ZoneTableAccessor
+deactivate list
+
+
+loop for each zone given by ZoneTableIterator
+
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+participant LoadAction as "la:LoadAction"
+create LoadAction
+CacheConfig -> LoadAction: <<construct>>
+
+CacheConfig --> list : la
+
+deactivate CacheConfig
+
+participant ZoneWriter as "zw:ZoneWriter"
+create ZoneWriter
+list -> ZoneWriter: <<construct>> (la)
+
+list --> memmgr: zw
+
+deactivate list
+
+
+memmgr -> ZoneWriter: load()
+activate ZoneWriter
+ZoneWriter -> LoadAction: (funcall)
+activate LoadAction
+
+participant ZoneData as "zd:ZoneData"
+create ZoneData
+LoadAction -> ZoneData: <<construct>> via helpers
+
+LoadAction --> ZoneWriter: zd
+deactivate LoadAction
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: install()
+activate ZoneWriter
+
+ZoneWriter -> ZoneTable: addZone(zd)
+activate ZoneTable
+ZoneTable --> ZoneWriter: NULL (no old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter
+
+end
+
+[<- memmgr: command to\nmodules\n(datasrc_name,\nsegmentparam)
+[--> memmgr: ack from all\nmodules
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+create segment.2
+zt_segment -> segment.2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+note left of memmgr: load zone\nfor each zone\ngiven by\nZoneTableIterator
+
+end
+
+[<-- memmgr
+
+deactivate memmgr
+
+@enduml

+ 94 - 0
doc/design/datasrc/memmgr-mapped-reload.txt

@@ -0,0 +1,94 @@
+@startuml
+
+participant memmgr as ":b10-memmgr"
+[-> memmgr: reload\n(zonename)
+activate memmgr
+
+participant list as ":Configurable\nClientList"
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+participant CacheConfig as ":CacheConfig"
+
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
+participant segment as "existing:Memory\nSegment\n(Mapped)"
+participant segment2 as "new:Memory\nSegment\n(Mapped)"
+
+list -> zt_segment: isWritable()
+activate zt_segment
+zt_segment --> list: true
+deactivate zt_segment
+
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+participant ZoneTable as ":ZoneTable"
+participant ZoneWriter as "zw:ZoneWriter"
+
+participant LoadAction as "la:LoadAction"
+create LoadAction
+CacheConfig -> LoadAction: <<construct>>
+CacheConfig --> list: la
+deactivate CacheConfig
+
+create ZoneWriter
+list -> ZoneWriter: <<construct>> (la)
+list --> memmgr: zw
+deactivate list
+
+memmgr -> ZoneWriter: load()
+activate ZoneWriter
+ZoneWriter -> LoadAction: (funcall)
+activate LoadAction
+
+participant ZoneData as "zd_existing\n:ZoneData"
+participant ZoneData2 as "zd_new\n:ZoneData"
+
+create ZoneData2
+LoadAction -> ZoneData2: <<construct>> via helpers
+
+LoadAction --> ZoneWriter: zd_new
+deactivate LoadAction
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: install()
+activate ZoneWriter
+
+ZoneWriter -> ZoneTable: addZone(zd_new)
+activate ZoneTable
+ZoneTable --> ZoneWriter: zd_existing (old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: cleanup()
+activate ZoneWriter
+
+ZoneWriter -> ZoneData: <<destroy>>
+destroy ZoneData
+deactivate ZoneWriter
+
+[<- memmgr: command to\nmodules\n(datasrc_name,\nsegmentparam)
+[--> memmgr: ack from all\nmodules
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+create segment2
+zt_segment -> segment2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+note left of memmgr: (repeat the\nsame sequence\nfor loading to the\nother segment)
+
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+
+...
+
+@enduml

+ 68 - 0
doc/design/datasrc/overview.txt

@@ -0,0 +1,68 @@
+@startuml
+
+hide members
+
+note "Automatic placement of classes\ndoesn't look good. This diagram\nhas to be improved." as n1
+
+Auth "1" *--> "*" ConfigurableClientList
+Auth --> DataSourceClient
+Auth --> ZoneWriter
+Auth --> ZoneTableAccessor
+Auth --> DataSourceStatus
+Auth --> ZoneTableIterator
+
+ConfigurableClientList "1" *--> "*" DataSourceInfo
+ConfigurableClientList ..> ZoneTableSegment : <<reset>>
+ConfigurableClientList ..> DataSourceStatus : <<create>>
+ConfigurableClientList ..> ZoneWriter : <<create>>
+ConfigurableClientList ..> ZoneTableAccessor : <<create>>
+
+DataSourceInfo "1" *--> "*" DataSourceClient
+DataSourceInfo "1" *--> "*" CacheConfig
+DataSourceInfo "1" *--> "*" ZoneTableSegment
+
+ZoneTableAccessor ..> ZoneTableIterator : <<create>>
+
+ZoneTableAccessorCache --> CacheConfig
+ZoneTableAccessorCache ..> ZoneTableIteratorCache : <<create>>
+ZoneTableAccessorCache --o ZoneTableAccessor
+
+ZoneTableIteratorCache --o ZoneTableIterator
+ZoneTableIteratorCache --> CacheConfig
+
+ZoneWriter --> ZoneTableSegment
+ZoneWriter ..> ZoneData : add/replace
+
+ZoneTableSegment "1" *--> "1" ZoneTableHeader
+ZoneTableSegment "1" *--> "1" MemorySegment
+
+CacheConfig ..> LoadAction
+
+LoadAction ..> ZoneData : create
+LoadAction *--> ZoneDataLoader
+
+ZoneDataLoader --> ZoneData
+ZoneDataLoader *--> ZoneDataUpdater
+ZoneDataLoader --> MemorySegment
+
+ZoneDataUpdater --> ZoneData
+ZoneDataUpdater ..> RdataSet : create
+ZoneDataUpdater ..> RdataSet : add
+
+ZoneTableHeader "1" *--> "1" ZoneTable
+ZoneTable "1" *--> "1" ZoneData
+ZoneData "1" *--> "1" RdataSet
+
+LoadFromFile --o LoadAction
+IteratorLoader --o LoadAction
+
+MemorySegmentMapped --o MemorySegment
+MemorySegmentLocal --o MemorySegment
+
+ZoneTableSegmentMapped --o ZoneTableSegment
+ZoneTableSegmentLocal --o ZoneTableSegment
+
+ZoneTableSegmentMapped *--> MemorySegmentMapped
+ZoneTableSegmentLocal *--> MemorySegmentLocal
+
+@enduml

+ 2 - 2
doc/design/ipc-high.txt

@@ -210,7 +210,7 @@ about changes to zone data, they'd subscribe to the
 `Notifications/ZoneUpdates` group. Then, other client (let's say
 `XfrIn`, with session ID `s12345`) would send something like:
 
-  s12345 -> Notifications/ZoneUpdates
+  s12345 -> notifications/ZoneUpdates
   {"notification": ["zone-update", {
       "class": "IN",
       "origin": "example.org.",
@@ -221,7 +221,7 @@ Both receivers would receive the message and know that the
 `example.org` zone is now at version 123456. Note that multiple users
 may produce the same kind of notification. Also, single group may be
 used to send multiple notification names (but they should be related;
-in our example, the `Notifications/ZoneUpdates` could be used for
+in our example, the `notifications/ZoneUpdates` could be used for
 `zone-update`, `zone-available` and `zone-unavailable` notifications
 for change in zone data, configuration of new zone in the system and
 removal of a zone from configuration).

+ 347 - 0
doc/design/resolver/01-scaling-across-cores

@@ -0,0 +1,347 @@
+Scaling across (many) cores
+===========================
+
+Problem statement
+-----------------
+
+The general issue is how to insure that the resolver scales.
+
+Currently resolvers are CPU bound, and it seems likely that both
+instructions-per-cycle and CPU frequency will not increase radically,
+scaling will need to be across multiple cores.
+
+How can we best scale a recursive resolver across multiple cores?
+
+Image of how resolution looks like
+----------------------------------
+
+                               Receive the query. @# <------------------------\
+                                       |                                      |
+                                       |                                      |
+                                       v                                      |
+                                 Parse it, etc. $                             |
+                                       |                                      |
+                                       |                                      |
+                                       v                                      |
+                              Look into the cache. $#                         |
+       Cry  <---- No <---------- Is it there? -----------> Yes ---------\     |
+        |                            ^                                  |     |
+ Prepare upstream query $            |                                  |     |
+        |                            |                                  |     |
+        v                            |                                  |     |
+  Send an upstream query (#)         |                                  |     |
+        |                            |                                  |     |
+        |                            |                                  |     |
+        v                            |                                  |     |
+    Wait for answer @(#)             |                                  |     |
+        |                            |                                  |     |
+        v                            |                                  |     |
+       Parse $                       |                                  |     |
+        |                            |                                  |     |
+        v                            |                                  |     |
+   Is it enough? $ ----> No ---------/                                  |     |
+        |                                                               |     |
+       Yes                                                              |     |
+        |                                                               |     |
+        \-----------------------> Build answer $ <----------------------/     |
+                                        |                                     |
+                                        |                                     |
+                                        v                                     |
+                                   Send answer # -----------------------------/
+
+This is simplified version, however. There may be other tasks (validation, for
+example), which are not drawn mostly for simplicity, as they don't produce more
+problems. The validation would be done as part of some computational task and
+they could do more lookups in the cache or upstream queries.
+
+Also, multiple queries may generate the same upstream query, so they should be
+aggregated together somehow.
+
+Legend
+~~~~~~
+ * $ - CPU intensive
+ * @ - Waiting for external event
+ * # - Possible interaction with other tasks
+
+Goals
+-----
+ * Run the CPU intensive tasks in multiple threads to allow concurrency.
+ * Minimise waiting for locks.
+ * Don't require too much memory.
+ * Minimise the number of upstream queries (both because they are slow and
+   expensive and also because we don't want to eat too much bandwidth and spam
+   the authoritative servers).
+ * Design simple enough so it can be implemented.
+
+Naïve version
+-------------
+
+Let's look at possible approaches and list their pros and cons. Many of the
+simple versions would not really work, but let's have a look at them anyway,
+because thinking about them might bring some solutions for the real versions.
+
+We take one query, handle it fully, with blocking waits for the answers. After
+this is done, we take another. The cache is private for each one process.
+
+Advantages:
+
+ * Very simple.
+ * No locks.
+
+Disadvantages:
+
+ * To scale across cores, we need to run *a lot* of processes, since they'd be
+   waiting for something most of their time. That means a lot of memory eaten,
+   because each one has its own cache. Also, running so many processes may be
+   problematic, processes are not very cheap.
+ * Many things would be asked multiple times, because the caches are not
+   shared.
+
+Threads
+~~~~~~~
+
+Some of the problems could be solved by using threads, but they'd not improve
+it much, since threads are not really cheap either (starting several hundred
+threads might not be a good idea either).
+
+Also, threads bring other problems. When we still assume separate caches (for
+caches, see below), we need to ensure safe access to logging, configuration,
+network, etc. These could be a bottleneck (eg. if we lock every time we read a
+packet from network, when there are many threads, they'll just fight over the
+lock).
+
+Supercache
+~~~~~~~~~~
+
+The problem with cache could be solved by placing a ``supercache'' between the
+resolvers and the Internet. That one would do almost no processing, it would
+just take the query, looked up in the cache and either answered from the cache
+or forwarded the query to the external world. It would store the answer and
+forward it back.
+
+The cache, if single-threaded, could be a bottle-neck. To solve it, there could
+be several approaches:
+
+Layered cache::
+  Each process has it's own small cache, which catches many queries. Then, a
+  group of processes shares another level of bigger cache, which catches most
+  of the queries that get past the private caches. We further group them and
+  each level handles less queries from each process, so they can keep up.
+  However, with each level, we add some overhead to do another lookup.
+Segmented cache::
+  We have several caches of the same level, in parallel. When we would ask a
+  cache, we hash the query and decide which cache to ask by the hash. Only that
+  cache would have that answer if any and each could run in a separate process.
+  The only problem is, could there be a pattern of queries that would skew to
+  use only one cache while the rest would be idle?
+Shared cache access::
+  A cache would be accessed by multiple processes/threads. See below for
+  details, but there's a risk of lock contention on the cache (it depends on
+  the data structure).
+
+Upstream queries
+~~~~~~~~~~~~~~~~
+
+Before doing an upstream query, we look into the cache to ensure we don't have
+the information yet. When we get the answer, we want to update the cache.
+
+This suggests the upstream queries are tightly coupled with the cache. Now,
+when we have several cache processes/threads, each can have some set of opened
+sockets which are not shared with other caches to do the lookups. This way we
+can avoid locking the upstream network communication.
+
+Also, we can have three conceptual states for data in cache, and act
+differently when it is requested.
+
+Present::
+  If it is available, in positive or negative version, we just provide the
+  answer right away.
+Not present::
+  The continuation of processing is queued somehow (blocked/callback is
+  stored/whatever). An upstream query is sent and we get to the next state.
+Waiting for answer::
+  If another query for the same thing arrives, we just queue it the same way
+  and keep waiting. When the answer comes, all the queued tasks are resumed.
+  If the TTL > 0, we store the answer and set it to ``present''.
+
+We want to do aggregation of upstream queries anyway, using cache for it saves
+some more processing and possibly locks.
+
+Multiple parallel queries
+-------------------------
+
+It seems obvious we can't afford to have a thread or process for each
+outstanding query. We need to handle multiple queries in each one at any given
+time.
+
+Coroutines
+~~~~~~~~~~
+
+The OS-level threads might be too expensive, but coroutines might be cheap
+enough. In that way, we could still write a code that would be easy to read,
+but limit the number of OS threads to reasonable number.
+
+In this model, when a query comes, a new coroutine/user-level thread is created
+for it. We use special reads and writes whenever there's an operation that
+could block. These reads and writes would internally schedule the operation
+and switch to another coroutine (if there's any ready to be executed).
+
+Each thread/process maintains its own set of coroutines and they do not
+migrate. This way, the queue of coroutines is kept lock-less, as well as any
+private caches. Only the shared caches are protected by a lock.
+
+[NOTE]
+The `coro` unit we have in the current code is *not* considered a coroutine
+library here. We would need a coroutine library where we have real stack for
+each coroutine and we switch the stacks on coroutine switch. That is possible
+with reasonable amount of dark magic (see `ucontext.h`, for example, but there
+are surely some higher-level libraries for that).
+
+There are some trouble with multiple coroutines waiting on the same event, like
+the same upstream query (possibly even coroutines from different threads), but
+it should be possible to solve.
+
+Event-based
+~~~~~~~~~~~
+
+We use events (`asio` and stuff) for writing it. Each outstanding query is an
+object with some callbacks on it. When we would do a possibly blocking
+operation, we schedule a callback to happen once the operation finishes.
+
+This is more lightweight than the coroutines (the query objects will be smaller
+than the stacks for coroutines), but it is harder to write and read for.
+
+[NOTE]
+Do not consider cross-breeding the models. That leads to space-time distortions
+and brain damage. Implementing one on top of other is OK, but mixing it in the
+same bit of code is a way do madhouse.
+
+Landlords and peasants
+~~~~~~~~~~~~~~~~~~~~~~
+
+In both the coroutines and event-based models, the cache and other shared
+things are easier to imagine as objects the working threads fight over to hold
+for a short while. In this model, it is easier to imagine each such shared
+object as something owned by a landlord that doesn't let anyone else on it,
+but you can send requests to him.
+
+A query is an object once again, with some kind of state machine.
+
+Then there are two kinds of threads. The peasants are just to do the heavy
+work. There's a global work-queue for peasants. Once a peasant is idle, it
+comes to the queue and picks up a handful of queries from there. It does as
+much on each the query as possible without requiring any shared resource.
+
+The other kind, the landlords, have a resource to watch over each. So we would
+have a cache (or several parts of cache), the sockets for accepting queries and
+answering them, possibly more. Each of these would have a separate landlord
+thread and a queue of tasks to do on the resource (look up something, send an
+answer...).
+
+Similarly, the landlord would take a handful of tasks from its queue and start
+handling them. It would possibly produce some more tasks for the peasants.
+
+The point here is, all the synchronisation is done on the queues, not on the
+shared resources themselves. And, we would append to a queues once the whole
+batch was completed. By tweaking the size of the batch, we could balance the
+lock contention, throughput and RTT. The append/remove would be a quick
+operation, and the cost of locks would amortize in the larger amount of queries
+handled per one lock operation.
+
+The possible downside is, a query needs to travel across several threads during
+its lifetime. It might turn out it is faster to move the query between cores
+than accessing the cache from several threads, since it is smaller, but it
+might be slower as well.
+
+It would be critical to make some kind of queue that is fast to append to and
+fast to take out first n items. Also, the tasks in the queues can be just
+abstract `boost::function<void (Worker&)>` functors, and each worker would just
+iterate through the queue, calling each functor. The parameter would be to
+allow easy generation of more tasks for other queues (they would be stored
+privately first, and appended to remote queues at the end of batch).
+
+Also, if we wanted to generate multiple parallel upstream queries from a single
+query, we would need to be careful. A query object would not have a lock on
+itself and the upstream queries could end up in a different caches/threads. To
+protect the original query, we would add another landlord that would aggregate
+answers together and let the query continue processing once it got enough
+answers. That way, the answers would be pushed all to the same threads and they
+could not fight over the query.
+
+[NOTE]
+This model would work only with threads, not processes.
+
+Shared caches
+-------------
+
+While it seems it is good to have some sort of L1 cache with pre-rendered
+answers (according to measurements in the #2777 ticket), we probably need some
+kind of larger shared cache.
+
+If we had just a single shared cache protected by lock, there'd be a lot of
+lock contention on the lock.
+
+Partitioning the cache
+~~~~~~~~~~~~~~~~~~~~~~
+
+We split the cache into parts, either by the layers or by parallel bits we
+switch between by a hash. If we take it to the extreme, a lock on each hash
+bucket would be this kind, though that might be wasting resources (how
+expensive is it to create a lock?).
+
+Landlords
+~~~~~~~~~
+
+The landlords do synchronizations themselves. Still, the cache would need to be
+partitioned.
+
+RCU
+~~~
+
+The RCU is a lock-less synchronization mechanism. An item is accessed through a
+pointer.  An updater creates a copy of the structure (in our case, it would be
+content of single hash bucket) and then atomically replaces the pointer. The
+readers from before have the old version, the new ones get the new version.
+When all the old readers die out, the old copy is reclaimed. Also, the
+reclamation can AFAIK be postponed for later times when we are slightly more
+idle or to a different thread.
+
+We could use it for cache ‒ in the fast track, we would just read the cache. In
+the slow one, we would have to wait in queue to do the update, in a single
+updater thread (because we don't really want to be updating the same cell twice
+at the same time).
+
+Proposals
+---------
+
+In either case, we would have some kind of L1 cache with pre-rendered answers.
+For these proposals (except the third), we wouldn't care if we split the cache
+into parallel chunks or layers.
+
+Hybrid RCU/Landlord
+~~~~~~~~~~~~~~~~~~~
+
+The landlord approach, just read only accesses to the cache are done directly
+by the peasants. Only if they don't find what they want, they'd append the
+queue to the task of the landlord. The landlord would be doing the RCU updates.
+It could happen that by the time the landlord gets to the task the answer is
+already there, but that would not matter much.
+
+Accessing network would be from landlords.
+
+Coroutines+RCU
+~~~~~~~~~~~~~~
+
+We would do the coroutines, and the reads from shared cache would go without
+locking. When doing write, we would have to lock.
+
+To avoid locking, each worker thread would have its own set of upstream sockets
+and we would dup the sockets from users so we don't have to lock that.
+
+Multiple processes with coroutines and RCU
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This would need the layered cache. The upper caches would be mapped to local
+memory for read-only access. Each cache would be a separate process. The
+process would do the updates ‒ if the answer was not there, the process would
+be asked by some kind of IPC to pull it from upstream cache or network.

+ 150 - 0
doc/design/resolver/02-mixed-recursive-authority-setup

@@ -0,0 +1,150 @@
+Mixed recursive & authoritative setup
+=====================================
+
+Ideally we will run the authoritative server independently of the
+recursive resolver.
+
+We need a way to run both an authoritative and a recursive resolver on
+the same machine and listening on the same IP/port. But we need a way to
+run only one of them as well.
+
+This is mostly the same problem as we have with DDNS packets and xfr-out
+requests, but they aren't that performance sensitive as auth & resolver.
+
+There are a number of possible approaches to this:
+
+One fat module
+--------------
+
+With some build system or dynamic linker tricks, we create three modules:
+
+ * Stand-alone auth
+ * Stand-alone resolver
+ * Compound module containing both
+
+The user then chooses either one stand-alone module, or the compound one,
+depending on the requirements.
+
+Advantages
+~~~~~~~~~~
+
+ * It is easier to switch between processing and ask authoritative questions
+   from within the resolver processing.
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * The code is not separated (one bugs takes down both, admin can't see which
+   one takes how much CPU).
+ * BIND 9 does this and its code is a jungle. Maybe it's not just a
+   coincidence.
+ * Limits flexibility -- for example, we can't then decide to make the resolver
+   threaded (or we would have to make sure the auth processing doesn't break
+   with threads, which will be hard).
+
+There's also the idea of putting the auth into a loadable library and the
+resolver could load and use it somehow. But the advantages and disadvantages
+are probably the same.
+
+Auth first
+----------
+
+We do the same as with xfrout and ddns. When a query comes, it is examined and
+if the `RD` bit is set, it is forwarded to the resolver.
+
+Advantages
+~~~~~~~~~~
+
+ * Separate auth and resolver modules
+ * Minimal changes to auth
+ * No slowdown on the auth side
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Counter-intuitive asymmetric design
+ * Possible slowdown on the resolver side
+ * Resolver needs to know both modes (for running stand-alone too)
+
+There's also the possibility of the reverse -- resolver first. It may make
+more sense for performance (the more usual scenario would probably be a
+high-load resolver with just few low-volume authoritative zones). On the other
+hand, auth already has some forwarding tricks.
+
+Auth with cache
+---------------
+
+This is mostly the same as ``Auth first'', however, the cache is in the auth
+server. If it is in the cache, it is answered right away. If not, it is then
+forwarded to the resolver. The resolver then updates the cache too.
+
+Advantages
+~~~~~~~~~~
+
+ * Probably good performance
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Cache duplication (several auth modules, it doesn't feel like it would work
+   with shared memory without locking).
+ * Cache is probably very different from authoritative zones, it would
+   complicate auth processing.
+ * The resolver needs own copy of cache (to be able to get partial results),
+   probably a different one than the auth server.
+
+Receptionist
+------------
+
+One module does only the listening. It doesn't process the queries itself, it
+only looks into them and forwards them to the processing modules.
+
+Advantages
+~~~~~~~~~~
+
+ * Clean design with separated modules
+ * Easy to run modules stand-alone
+ * Allows for solving the xfrout & ddns forwarding without auth running
+ * Allows for views (different auths with different configurations)
+ * Allows balancing/clustering across multiple machines
+ * Easy to create new modules for different kinds of DNS handling and share
+   port with them too
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Need to set up another module (not a problem if we have inter-module
+   dependencies in b10-init)
+ * Possible performance impact. However, experiments show this is not an issue,
+   and the receptionist can actually increase the throughput with some tuning
+   and the increase in RTT is not big.
+
+Implementation ideas
+~~~~~~~~~~~~~~~~~~~~
+
+ * Let's have a new TCP transport, where we send not only the DNS messages,
+   but also the source and destination ports and addresses (two reasons --
+   ACLs in target module and not keeping state in the receptionist). It would
+   allow for transfer of a batch of messages at once, to save some calls to
+   kernel (like a length of block of messages, it is read at once, then they
+   are all parsed one by one, the whole block of answers is sent back).
+ * A module creates a listening socket (UNIX by default) on startup and
+   contacts all the receptionists. It sends what kind of packets to send
+   to the module and the address of the UNIX socket. All the receptionists
+   connect to the module. This allows for auto-configuring the receptionist.
+ * The queries are sent from the receptionist in batches, the answers are sent
+   back to the receptionist in batches too.
+ * It is possible to fine-tune and use OS-specific tricks (like epoll or
+   sending multiple UDP messages by single call to sendmmsg()).
+
+Proposal
+--------
+
+Implement the receptionist in a way we can still work without it (not throwing
+the current UDPServer and TCPServer in asiodns away).
+
+The way we handle xfrout and DDNS needs some changes, since we can't forward
+sockets for the query. We would implement the receptionist protocol on them,
+which would allow the receptionist to forward messages to them. We would then
+modify auth to be able to forward the queries over the receptionist protocol,
+so ordinary users don't need to start the receptionist.

+ 256 - 0
doc/design/resolver/03-cache-algorithm

@@ -0,0 +1,256 @@
+03-cache-algorithm
+
+Introduction
+------------
+Cache performance may be important for the resolver. It might not be
+critical. We need to research this.
+
+One key question is: given a specific cache hit rate, how much of an
+impact does cache performance have?
+
+For example, if we have 90% cache hit rate, will we still be spending
+most of our time in system calls or in looking things up in our cache?
+
+There are several ways we can consider figuring this out, including
+measuring this in existing resolvers (BIND 9, Unbound) or modeling
+with specific values.
+
+Once we know how critical the cache performance is, we can consider
+which algorithm is best for that. If it is very critical, then a
+custom algorithm designed for DNS caching makes sense. If it is not,
+then we can consider using an STL-based data structure.
+
+Effectiveness of Cache
+----------------------
+
+First, I'll try to answer the introductory questions.
+
+In some simplified model, we can express the amount of running time
+for answering queries directly from the cache in the total running
+time including that used for recursive resolution due to cache miss as
+follows:
+
+A = r*Q2*/(r*Q2+ Q1*(1-r))
+where
+A: amount of time for answering queries from the cache per unit time
+   (such as sec, 0<=A<=1)
+r: cache hit rate (0<=r<=1)
+Q1: max qps of the server with 100% cache hit
+Q2: max qps of the server with 0% cache hit
+
+Q1 can be measured easily for given data set; measuring Q2 is tricky
+in general (it requires many external queries with unreliable
+results), but we can still have some not-so-unrealistic numbers
+through controlled simulation.
+
+As a data point for these values, see a previous experimental results
+of mine:
+https://lists.isc.org/pipermail/bind10-dev/2012-July/003628.html
+
+Looking at the "ideal" server implementation (no protocol overhead)
+with the set up 90% and 85% cache hit rate with 1 recursion on cache
+miss, and with the possible maximum total throughput, we can deduce
+Q1 and Q2, which are: 170591qps and 60138qps respectively.
+
+This means, with 90% cache hit rate (r = 0.9), the server would spend
+76% of its run time for receiving queries and answering responses
+directly from the cache: 0.9*60138/(0.9*60138 + 0.1*170591) = 0.76.
+
+I also ran more realistic experiments: using BIND 9.9.2 and unbound
+1.4.19 in the "forward only" mode with crafted query data and the
+forwarded server to emulate the situation of 100% and 0% cache hit
+rates.  I then measured the max response throughput using a
+queryperf-like tool.  In both cases Q2 is about 28% of Q1 (I'm not
+showing specific numbers to avoid unnecessary discussion about
+specific performance of existing servers; it's out of scope of this
+memo).  Using Q2 = 0.28*Q1, above equation with 90% cache hit rate
+will be: A = 0.9 * 0.28 / (0.9*0.28 + 0.1) = 0.716. So the server will
+spend about 72% of its running time to answer queries directly from
+the cache.
+
+Of course, these experimental results are too simplified.  First, in
+these experiments we assumed only one external query is needed on
+cache miss.  In general it can be more; however, it may not actually
+be too optimistic either: in my another research result:
+http://bind10.isc.org/wiki/ResolverPerformanceResearch
+In the more detailed analysis using real query sample and tracing what
+an actual resolver would do, it looked we'd need about 1.44 to 1.63
+external queries per cache miss in average.
+
+Still, of course, the real world cases are not that simple: in reality
+we'd need to deal with timeouts, slower remote servers, unexpected
+intermediate results, etc.  DNSSEC validating resolvers will clearly
+need to do more work.
+
+So, in the real world deployment Q2 should be much smaller than Q1.
+Here are some specific cases of the relationship between Q1 and Q2 for
+given A (assuming r = 0.9):
+
+70%: Q2 = 0.26 * Q1
+60%: Q2 = 0.17 * Q1
+50%: Q2 = 0.11 * Q1
+
+So, even if "recursive resolution is 10 times heavier" than the cache
+only case, we can assume the server spends a half of its run time for
+answering queries directly from the cache at the cache hit rate of
+90%.  I think this is a reasonably safe assumption.
+
+Now, assuming the number of 50% or more, does this suggest we should
+highly optimize the cache?  Opinions may vary on this point, but I
+personally think the answer is yes.  I've written an experimental
+cache only implementation that employs the idea of fully-rendered
+cached data.  On one test machine (2.20GHz AMD64, using a single
+core), queryperf-like benchmark shows it can handle over 180Kqps,
+while BIND 9.9.2 can just handle 41K qps.  The experimental
+implementation skips some necessary features for a production server,
+and cache management itself is always inevitable bottleneck, so the
+production version wouldn't be that fast, but it still suggests it may
+not be very difficult to reach over 100Kqps in production environment
+including recursive resolution overhead.
+
+Cache Types
+-----------
+
+1. Record cache
+
+Conceptually, any recursive resolver (with cache) implementation would
+have cache for RRs (or RRsets in the modern version of protocol) given
+in responses to its external queries.  In BIND 9, it's called the
+"cached DB", using an in-memory rbt-like tree.  unbound calls it
+"rrset cache", which is implemented as a hash table.
+
+2. Delegation cache
+
+Recursive server implementations would also have cache to determine
+the deepest zone cut for a given query name in the recursion process.
+Neither BIND 9 nor unbound has a separate cache for this purpose;
+basically they try to find an NR RRset from the "record cache" whose
+owner name best matches the given query name.
+
+3. Remote server cache
+
+In addition, a recursive server implementation may maintain a cache
+for information of remote authoritative servers.  Both BIND 9 and
+unbound conceptually have this type of cache, although there are some
+non-negligible differences in details.  BIND 9's implementation of
+this cache is called ADB.  Its a hash table whose key is domain name,
+and each entry stores corresponding IPv6/v4 addresses; another data
+structure for each address stores averaged RTT for the address,
+lameness information, EDNS availability, etc.  unbound's
+implementation is called "infrastructure cache".  It's a hash table
+keyed with IP addresses whose entries store similar information as
+that in BIND 9's per address ADB entry.  In unbound a remote server's
+address must be determined by looking up the record cache (rrset cache
+in unbound terminology); unlike BIND 9's ADB, there's no direct
+shortcut from a server's domain name to IP addresses.
+
+4. Full response cache
+
+unbound has an additional cache layer, called the "message cache".
+It's a hash table whose hash key is query parameter (essentially qname
+and type) and entry is a sequence to record (rrset) cache entries.
+This sequence constructs a complete response to the corresponding
+query, so it would help optimize building a response message skipping
+the record cache for each section (answer/authority/additional) of the
+response message.  PowerDNS recursor has (seemingly) the same concept
+called "packet cache" (but I don't know its implementation details
+very much).
+
+BIND 9 doesn't have this type of cache; it always looks into the
+record cache to build a complete response to a given query.
+
+Miscellaneous General Requirements
+----------------------------------
+
+- Minimize contention between threads (if threaded)
+- Cache purge policy: normally only a very small part of cached DNS
+  information will be reused, and those reused are very heavily
+  reused.  So LRU-like algorithm should generally work well, but we'll
+  also need to honor DNS TTL.
+
+Random Ideas for BIND 10
+------------------------
+
+Below are specific random ideas for BIND 10.  Some are based on
+experimental results with reasonably realistic data; some others are
+mostly a guess.
+
+1. Fully rendered response cache
+
+Some real world query samples show that a very small portion of entire
+queries are very popular and queried very often and many times; the
+rest is rarely reused, if any.  Two different data sets show top
+10,000 queries would cover around 80% of total queries, regardless
+of the size of the total queries.  This suggests an idea of having a
+small, highly optimized full response cache.
+
+I tried this idea in the jinmei-l1cache branch.  It's a hash table
+keyed with a tuple of query name and type whose entry stores fully
+rendered, wire-format response image (answer section only, assuming
+the "minimal-responses" option).  It also maintains offsets to each
+RR, so it can easily update TTLs when necessary or rotate RRs if
+optionally requested.  If neither TTL adjustment nor RR rotation is
+required, query handling is just to lookup the hash table and copy the
+pre-rendered data.  Experimental benchmark showed it ran vary fast;
+more than 4 times faster than BIND 9, and even much faster than other
+implementations that have full response cache (although, as usual, the
+comparison is not entirely fair).
+
+Also, the cache size is quite small; the run time memory footprint of
+this server process was just about 5MB.  So, I think it's reasonable
+to have each process/thread have their own copy of this cache to
+completely eliminate contention.  Also, if we can keep the cache size
+this small, it would be easier to dump it to a file on shutdown and
+reuse it on restart.  This will be quite effective (if the downtime is
+reasonably short) because the cached data are expected to be highly
+popular.
+
+2. Record cache
+
+For the normal record cache, I don't have a particular idea beyond
+something obvious, like a hash table to map from query parameters to
+corresponding RRset (or negative information).  But I guess this cache
+should be shared by multiple threads.  That will help reconstruct the
+full response cache data on TTL expiration more efficiently.  And, if
+shared, the data structure should be chosen so that contention
+overhead can be minimized.  In general, I guess something like hash
+tables is more suitable than tree-like structure in that sense.
+
+There's other points to discuss for this cache related to other types
+of cache (see below).
+
+3. Separate delegation cache
+
+One thing I'm guessing is that it may make sense if we have a separate
+cache structure for delegation data.  It's conceptually a set of NS
+RRs so we can identify the best (longest) matching one for a given
+query name.
+
+Analysis of some sets of query data showed the vast majority of
+end client's queries are for A and AAAA (not surprisingly).  So, even
+if we separate this cache from the record cache, the additional
+overhead (both for memory and fetch) will probably (hopefully) be
+marginal.  Separating caches will also help reduce contention between
+threads.  It *might* also help improve lookup performance because this
+can be optimized for longest match search.
+
+4. Remote server cache without involving the record cache
+
+Likewise, it may make sense to maintain the remote server cache
+separately from the record cache.  I guess these AAAA and A records
+are rarely the queried by end clients, so, like the case of delegation
+cache it's possible that the data sets are mostly disjoint.  Also, for
+this purpose the RRsets don't have to have higher trust rank (per
+RFC2181 5.4.1): glue or additional are okay, and, by separating these
+from the record cache, we can avoid accidental promotion of these data
+to trustworthy answers and returning them to clients (BIND 9 had this
+type of bugs before).
+
+Custom vs Existing Library (STL etc)
+------------------------------------
+
+It may have to be discussed, but I guess in many cases we end up
+introducing custom implementation because these caches should be
+highly performance sensitive, directly related to our core business, and
+also have to be memory efficient.  But in some sub-components we may
+be able to benefit from existing generic libraries.

+ 5 - 0
doc/design/resolver/README

@@ -0,0 +1,5 @@
+This directory contains research and design documents for the BIND 10
+resolver reimplementation.
+
+Each file contains a specific issue and discussion surrounding that
+issue.

+ 162 - 0
doc/devel/contribute.dox

@@ -0,0 +1,162 @@
+// Copyright (C) 2013  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.
+
+/**
+
+ @page contributorGuide BIND10 Contributor's Guide
+
+So you found a bug in BIND10 or plan to develop an extension and want to
+send a patch? Great! This page will explain how to contribute your
+changes smoothly.
+
+@section contributorGuideWritePatch Writing a patch
+
+Before you start working on a patch or a new feature, it is a good idea
+to discuss it first with BIND10 developers. You can post your questions
+to the \c bind10-dev mailing list
+(https://lists.isc.org/mailman/listinfo/bind10-dev) for general BIND10
+stuff, or to the \c bind10-dhcp mailing list
+(https://lists.isc.org/mailman/listinfo/bind10-dhcp) for DHCP specific
+topics. If you prefer to get faster feedback, most BIND10 developers
+hang out in the \c bind10 jabber room
+(xmpp:bind10@conference.jabber.isc.org). Those involved in DHCP also use
+the \c dhcp chatroom (xmpp:dhcp@conference.jabber.isc.org). Feel free to
+join these rooms and talk to us. It is possible that someone else is
+working on your specific issue or perhaps the solution you plan to
+implement is not the best one. Often having a 10 minute talk could save
+many hours of engineering work.
+
+First step would be to get the source code from our Git repository. The
+procedure is very easy and is explained here:
+http://bind10.isc.org/wiki/GitGuidelines.  While it is possible to
+provide a patch against the latest stable release, it makes the review
+process much easier if it is for latest code from the Git \c master
+branch.
+
+Ok, so you have written a patch? Great! Before you submit it, make sure
+that your code compiles. This may seem obvious, but there's more to
+it. You have surely checked that it compiles on your system, but BIND10
+is portable software. Besides Linux, it is compiled and used on
+relatively uncommon systems like OpenBSD and Solaris 11. Will your code
+compile and work there? What about endianess? It is likely that you used
+a regular x86 architecture machine to write your patch, but the software
+is expected to run on many other architectures. You may take a look at
+system specific build notes (http://bind10.isc.org/wiki/SystemSpecificNotes).
+For a complete list of systems we build on, you may take a look at the
+following build farm report: http://git.bind10.isc.org/~tester/builder/builder-new.html .
+
+Does your patch conform to BIND10 coding guidelines
+(http://bind10.isc.org/wiki/CodingGuidelines)? You still can submit a
+patch that does not adhere to it, but that will decrease its chances of
+being accepted. If the deviations are minor, the BIND10 engineer who
+does the review will likely fix the issues. However, if there are lots
+of issues, the reviewer may simply reject the patch and ask you to fix
+it before re-submitting.
+
+@section contributorGuideUnittests Running unit-tests
+
+One of the ground rules in BIND10 development is that every piece of
+code has to be tested. We now have an extensive set of unit-tests for
+almost every line of code. Even if you are fixing something small,
+like a single line fix, it is encouraged to write unit-tests for that
+change. That is even more true for new code. If you write a new
+function, method or a class, you definitely should write unit-tests
+for it.
+
+BIND10 uses the Google C++ Testing Framework (also called googletest or
+gtest) as a base for our C++ unit-tests. See
+http://code.google.com/p/googletest/ for details. For Python unit-tests,
+we use the its \c unittest library which is included in Python. You must
+have \c gtest installed or at least extracted in a directory before
+compiling BIND10 unit-tests. To enable unit-tests in BIND10, use:
+
+@code
+./configure --with-gtest=/path/to/your/gtest/dir
+@endcode
+
+or
+
+@code
+./configure --with-gtest-source=/path/to/your/gtest/dir
+@endcode
+
+Depending on how you compiled or installed \c gtest (e.g. from sources
+or using some package management system) one of those two switches will
+find \c gtest. After that you make run unit-tests:
+
+@code
+make check
+@endcode
+
+If you happen to add new files or have modified any \c Makefile.am
+files, it is also a good idea to check if you haven't broken the
+distribution process:
+
+@code
+make distcheck
+@endcode
+
+There are other useful switches which can be passed to configure. It is
+always a good idea to use \c --enable-logger-checks, which does sanity
+checks on logger parameters. If you happen to modify anything in the
+documentation, use \c --enable-generate-docs. If you are modifying DHCP
+code, you are likely to be interested in enabling the MySQL backend for
+DHCP. Note that if the backend is not enabled, MySQL specific unit-tests
+are skipped. From that perspective, it is useful to use
+\c --with-dhcp-mysql. For a complete list of all switches, use:
+
+@code
+ ./configure --help
+@endcode
+
+@section contributorGuideReview Going through a review
+
+Once all those are checked and working, feel free to create a ticket for
+your patch at http://bind10.isc.org/ or attach your patch to an existing
+ticket if you have fixed it. It would be nice if you also join the
+\c bind10 or \c dhcp chatroom saying that you have submitted a
+patch. Alternatively, you may send a note to the \c bind10-dev or
+\c bind10-dhcp mailing lists.
+
+Here's the tricky part. One of BIND10 developers will review your patch,
+but it may not happen immediately. Unfortunately, developers are usually
+working under a tight schedule, so any extra unplanned review work may
+take a while sometimes. Having said that, we value external
+contributions very much and will do whatever we can to review patches in
+a timely manner. Don't get discouraged if your patch is not accepted
+after first review. To keep the code quality high, we use the same
+review processes for internal code and for external patches. It may take
+some cycles of review/updated patch submissions before the code is
+finally accepted.
+
+Once the process is almost complete, the developer will likely ask you
+how you would like to be credited. The typical answers are by first and
+last name, by nickname, by company name or anonymously. Typically we
+will add a note to the \c ChangeLog and also set you as the author of
+the commit applying the patch. If the contributted feature is big or
+critical for whatever reason, it may also be mentioned in release notes.
+
+@section contributorGuideExtra Extra steps
+
+If you are interested in knowing the results of more in-depth testing,
+you are welcome to visit the BIND10 build farm:
+http://git.bind10.isc.org/~tester/builder/builder-new.html.  This is a
+live result page with all tests being run on various systems.  Besides
+basic unit-tests, we also have reports from Valgrind (memory debugger),
+cppcheck and clang-analyzer (static code analyzers), Lettuce system
+tests and more. Although it is not possible for non ISC employees to run
+tests on that farm, it is possible that your contributed patch will end
+up there sooner or later.
+
+*/

+ 61 - 11
doc/devel/mainpage.dox

@@ -1,44 +1,91 @@
+// Copyright (C) 2012-2013  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.
+
 /**
- *
  * @mainpage BIND10 Developer's Guide
  *
  * Welcome to BIND10 Developer's Guide. This documentation is addressed
- * at existing and prospecting developers and programmers, who would like
- * to gain insight into internal workings of BIND 10. It could also be useful
- * for existing and prospective contributors.
+ * at existing and prospecting developers and programmers and provides
+ * information needed to both extend and maintain BIND 10.
+ *
+ * If you wish to write "hook" code - code that is loaded by BIND 10 at
+ * run-time and modifies its behavior you should read the section
+ * @ref hooksdgDevelopersGuide.
+ *
+ * BIND 10 maintenance information is divided into a number of sections
+ * depending on focus.  DNS-specific issues are covered in the
+ * @ref dnsMaintenanceGuide while information on DHCP-specific topics can
+ * be found in the @ref dhcpMaintenanceGuide.  General BIND 10 topics, not
+ * specific to any protocol, are discussed in @ref miscellaneousTopics.
  *
  * If you are a user or system administrator, rather than software engineer,
- * you should read <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
+ * you should read the
+ * <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
  * Guide (Administrator Reference for BIND10)</a> instead.
  *
- * Regardless of your field of expertise, you are encouraged to visit
+ * Regardless of your field of expertise, you are encouraged to visit the
  * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
  *
- * @section DNS
+ * @section contrib Contributor's Guide
+ * - @subpage contributorGuide
+ *
+ * @section hooksFramework Hooks Framework
+ * - @subpage hooksdgDevelopersGuide
+ * - @subpage dhcpv4Hooks
+ * - @subpage dhcpv6Hooks
+ * - @subpage hooksComponentDeveloperGuide
+ * - @subpage hooksmgMaintenanceGuide
+ * - @subpage libdhcp_user_chk
+ *
+ * @section dnsMaintenanceGuide DNS Maintenance Guide
  * - Authoritative DNS (todo)
  * - Recursive resolver (todo)
  * - @subpage DataScrubbing
  *
- * @section DHCP
+ * @section dhcpMaintenanceGuide DHCP Maintenance Guide
  * - @subpage dhcp4
  *   - @subpage dhcpv4Session
  *   - @subpage dhcpv4ConfigParser
  *   - @subpage dhcpv4ConfigInherit
+ *   - @subpage dhcpv4OptionsParse
+ *   - @subpage dhcpv4DDNSIntegration
+ *   - @subpage dhcpv4Classifier
+ *   - @subpage dhcpv4Other
  * - @subpage dhcp6
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6ConfigParser
  *   - @subpage dhcpv6ConfigInherit
+ *   - @subpage dhcpv6DDNSIntegration
+ *   - @subpage dhcpv6OptionsParse
+ *   - @subpage dhcpv6Classifier
+ *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
+ *   - @subpage libdhcpRelay
  *   - @subpage libdhcpIfaceMgr
+ *   - @subpage libdhcpPktFilter
+ *   - @subpage libdhcpPktFilter6
+ *   - @subpage libdhcpErrorLogging
  * - @subpage libdhcpsrv
  *   - @subpage leasemgr
  *   - @subpage cfgmgr
  *   - @subpage allocengine
  * - @subpage dhcpDatabaseBackends
  * - @subpage perfdhcpInternals
+ * - @subpage libdhcp_ddns
  *
- * @section misc Miscellaneous topics
+ * @section miscellaneousTopics Miscellaneous Topics
  * - @subpage LoggingApi
  *   - @subpage LoggingApiOverview
  *   - @subpage LoggingApiLoggerNames
@@ -46,7 +93,10 @@
  * - @subpage SocketSessionUtility
  * - <a href="./doxygen-error.log">Documentation warnings and errors</a>
  *
- * @todo: Move this logo to the right (and possibly up). Not sure what
- * is the best way to do it in Doxygen, without using CSS hacks.
  * @image html isc-logo.png
  */
+/*
+ * @todo: Move the logo to the right (and possibly up). Not sure what
+ * is the best way to do it in Doxygen, without using CSS hacks.
+ */
+

+ 1 - 0
doc/guide/.gitignore

@@ -1,3 +1,4 @@
 /bind10-guide.html
 /bind10-guide.txt
 /bind10-messages.html
+/bind10-messages.xml

+ 3 - 5
doc/guide/Makefile.am

@@ -17,20 +17,18 @@ bind10-guide.html: bind10-guide.xml
 		-o $@ \
 		--stringparam section.autolabel 1 \
 		--stringparam section.label.includes.component.label 1 \
-		--stringparam html.stylesheet $(srcdir)/bind10-guide.css \
+		--stringparam html.stylesheet bind10-guide.css \
 		http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
 		$(srcdir)/bind10-guide.xml
 
-HTML2TXT = elinks -dump -no-numbering -no-references
-
 bind10-guide.txt: bind10-guide.html
-	$(HTML2TXT) bind10-guide.html > $@
+	@ELINKS@ -dump -no-numbering -no-references bind10-guide.html > $@
 
 bind10-messages.html: bind10-messages.xml
 	@XSLTPROC@ --novalid --xinclude --nonet \
 		--path $(top_builddir)/doc \
 		-o $@ \
-		--stringparam html.stylesheet $(srcdir)/bind10-guide.css \
+		--stringparam html.stylesheet bind10-guide.css \
 		http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
 		bind10-messages.xml
 

File diff suppressed because it is too large
+ 756 - 258
doc/guide/bind10-guide.xml


+ 8 - 0
ext/Makefile.am

@@ -0,0 +1,8 @@
+SUBDIRS = . asio
+
+# As we are copying ASIO headers to the installation directory, copy across
+# the licence file as well.
+asio_datadir = $(pkgincludedir)/asio
+asio_data_DATA = LICENSE_1_0.txt
+
+EXTRA_DIST = LICENSE_1_0.txt

+ 6 - 0
ext/asio/Makefile.am

@@ -0,0 +1,6 @@
+SUBDIRS = . asio
+
+# As we are copying across the ASIO files to the installation directory, copy
+# across the README that tells us where we got them from.
+asio_datadir = $(pkgincludedir)/asio
+asio_data_DATA = README

+ 11 - 0
ext/asio/README

@@ -4,7 +4,18 @@ Downloaded from http://sourceforge.net/projects/asio/files
 Project page: http://think-async.com/Asio
 
 Local modifications:
+
+Imported a kqueue bug fix from Asio 1.5.1.
+git commit e4b2c2633ebb3859286e9a4c19e97e17bcac41b3
+See
+http://sourceforge.net/p/asio/git/ci/a50b53864f77fe762f21e44a281235982dd7e733/
+
 Added ASIO_DECL to a number of function definitions
 git commit c32718be9f5409b6f72d98ddcd0b1ccd4c5c2293
 See also the bug report at:
 http://sourceforge.net/tracker/?func=detail&aid=3291113&group_id=122478&atid=694037
+
+Imported a fix from Asio 1.5.2.
+git commit cf00216570a36d2e3e688b197deea781bfbe7d8d
+See
+http://sourceforge.net/p/asio/git/ci/4820fd6f0d257a6bb554fcd1f97f170330be0448/log/?path=/asio/include/asio/detail/impl/socket_ops.ipp

+ 310 - 0
ext/asio/asio/Makefile.am

@@ -0,0 +1,310 @@
+SUBDIRS = .
+
+# Copy across the BIND 10 copy of ASIO to the installation directory, as some
+# header files used by user-libraries may use parts of it.
+asio_includedir = $(pkgincludedir)/asio
+nobase_asio_include_HEADERS = \
+    basic_datagram_socket.hpp \
+    basic_deadline_timer.hpp \
+    basic_io_object.hpp \
+    basic_raw_socket.hpp \
+    basic_serial_port.hpp \
+    basic_socket.hpp \
+    basic_socket_acceptor.hpp \
+    basic_socket_iostream.hpp \
+    basic_socket_streambuf.hpp \
+    basic_stream_socket.hpp \
+    basic_streambuf.hpp \
+    basic_streambuf_fwd.hpp \
+    buffer.hpp \
+    buffered_read_stream.hpp \
+    buffered_read_stream_fwd.hpp \
+    buffered_stream.hpp \
+    buffered_stream_fwd.hpp \
+    buffered_write_stream.hpp \
+    buffered_write_stream_fwd.hpp \
+    buffers_iterator.hpp \
+    completion_condition.hpp \
+    datagram_socket_service.hpp \
+    deadline_timer.hpp \
+    deadline_timer_service.hpp \
+    detail/array_fwd.hpp \
+    detail/base_from_completion_cond.hpp \
+    detail/bind_handler.hpp \
+    detail/buffer_resize_guard.hpp \
+    detail/buffer_sequence_adapter.hpp \
+    detail/buffered_stream_storage.hpp \
+    detail/call_stack.hpp \
+    detail/completion_handler.hpp \
+    detail/config.hpp \
+    detail/consuming_buffers.hpp \
+    detail/deadline_timer_service.hpp \
+    detail/descriptor_ops.hpp \
+    detail/descriptor_read_op.hpp \
+    detail/descriptor_write_op.hpp \
+    detail/dev_poll_reactor.hpp \
+    detail/dev_poll_reactor_fwd.hpp \
+    detail/epoll_reactor.hpp \
+    detail/epoll_reactor_fwd.hpp \
+    detail/event.hpp \
+    detail/eventfd_select_interrupter.hpp \
+    detail/fd_set_adapter.hpp \
+    detail/fenced_block.hpp \
+    detail/gcc_arm_fenced_block.hpp \
+    detail/gcc_fenced_block.hpp \
+    detail/gcc_hppa_fenced_block.hpp \
+    detail/gcc_sync_fenced_block.hpp \
+    detail/gcc_x86_fenced_block.hpp \
+    detail/handler_alloc_helpers.hpp \
+    detail/handler_invoke_helpers.hpp \
+    detail/hash_map.hpp \
+    detail/impl/descriptor_ops.ipp \
+    detail/impl/dev_poll_reactor.hpp \
+    detail/impl/dev_poll_reactor.ipp \
+    detail/impl/epoll_reactor.hpp \
+    detail/impl/epoll_reactor.ipp \
+    detail/impl/eventfd_select_interrupter.ipp \
+    detail/impl/kqueue_reactor.hpp \
+    detail/impl/kqueue_reactor.ipp \
+    detail/impl/pipe_select_interrupter.ipp \
+    detail/impl/posix_event.ipp \
+    detail/impl/posix_mutex.ipp \
+    detail/impl/posix_thread.ipp \
+    detail/impl/posix_tss_ptr.ipp \
+    detail/impl/reactive_descriptor_service.ipp \
+    detail/impl/reactive_serial_port_service.ipp \
+    detail/impl/reactive_socket_service_base.ipp \
+    detail/impl/resolver_service_base.ipp \
+    detail/impl/select_reactor.hpp \
+    detail/impl/select_reactor.ipp \
+    detail/impl/service_registry.hpp \
+    detail/impl/service_registry.ipp \
+    detail/impl/socket_ops.ipp \
+    detail/impl/socket_select_interrupter.ipp \
+    detail/impl/strand_service.hpp \
+    detail/impl/strand_service.ipp \
+    detail/impl/task_io_service.hpp \
+    detail/impl/task_io_service.ipp \
+    detail/impl/throw_error.ipp \
+    detail/impl/timer_queue.ipp \
+    detail/impl/timer_queue_set.ipp \
+    detail/impl/win_event.ipp \
+    detail/impl/win_iocp_handle_service.ipp \
+    detail/impl/win_iocp_io_service.hpp \
+    detail/impl/win_iocp_io_service.ipp \
+    detail/impl/win_iocp_serial_port_service.ipp \
+    detail/impl/win_iocp_socket_service_base.ipp \
+    detail/impl/win_mutex.ipp \
+    detail/impl/win_thread.ipp \
+    detail/impl/win_tss_ptr.ipp \
+    detail/impl/winsock_init.ipp \
+    detail/io_control.hpp \
+    detail/kqueue_reactor.hpp \
+    detail/kqueue_reactor_fwd.hpp \
+    detail/local_free_on_block_exit.hpp \
+    detail/macos_fenced_block.hpp \
+    detail/mutex.hpp \
+    detail/noncopyable.hpp \
+    detail/null_buffers_op.hpp \
+    detail/null_event.hpp \
+    detail/null_fenced_block.hpp \
+    detail/null_mutex.hpp \
+    detail/null_signal_blocker.hpp \
+    detail/null_thread.hpp \
+    detail/null_tss_ptr.hpp \
+    detail/object_pool.hpp \
+    detail/old_win_sdk_compat.hpp \
+    detail/op_queue.hpp \
+    detail/operation.hpp \
+    detail/pipe_select_interrupter.hpp \
+    detail/pop_options.hpp \
+    detail/posix_event.hpp \
+    detail/posix_fd_set_adapter.hpp \
+    detail/posix_mutex.hpp \
+    detail/posix_signal_blocker.hpp \
+    detail/posix_thread.hpp \
+    detail/posix_tss_ptr.hpp \
+    detail/push_options.hpp \
+    detail/reactive_descriptor_service.hpp \
+    detail/reactive_null_buffers_op.hpp \
+    detail/reactive_serial_port_service.hpp \
+    detail/reactive_socket_accept_op.hpp \
+    detail/reactive_socket_connect_op.hpp \
+    detail/reactive_socket_recv_op.hpp \
+    detail/reactive_socket_recvfrom_op.hpp \
+    detail/reactive_socket_send_op.hpp \
+    detail/reactive_socket_sendto_op.hpp \
+    detail/reactive_socket_service.hpp \
+    detail/reactive_socket_service_base.hpp \
+    detail/reactor.hpp \
+    detail/reactor_fwd.hpp \
+    detail/reactor_op.hpp \
+    detail/reactor_op_queue.hpp \
+    detail/regex_fwd.hpp \
+    detail/resolve_endpoint_op.hpp \
+    detail/resolve_op.hpp \
+    detail/resolver_service.hpp \
+    detail/resolver_service_base.hpp \
+    detail/scoped_lock.hpp \
+    detail/select_interrupter.hpp \
+    detail/select_reactor.hpp \
+    detail/select_reactor_fwd.hpp \
+    detail/service_base.hpp \
+    detail/service_id.hpp \
+    detail/service_registry.hpp \
+    detail/service_registry_fwd.hpp \
+    detail/shared_ptr.hpp \
+    detail/signal_blocker.hpp \
+    detail/signal_init.hpp \
+    detail/socket_holder.hpp \
+    detail/socket_ops.hpp \
+    detail/socket_option.hpp \
+    detail/socket_select_interrupter.hpp \
+    detail/socket_types.hpp \
+    detail/solaris_fenced_block.hpp \
+    detail/strand_service.hpp \
+    detail/task_io_service.hpp \
+    detail/task_io_service_fwd.hpp \
+    detail/task_io_service_operation.hpp \
+    detail/thread.hpp \
+    detail/throw_error.hpp \
+    detail/timer_op.hpp \
+    detail/timer_queue.hpp \
+    detail/timer_queue_base.hpp \
+    detail/timer_queue_fwd.hpp \
+    detail/timer_queue_set.hpp \
+    detail/timer_scheduler.hpp \
+    detail/timer_scheduler_fwd.hpp \
+    detail/tss_ptr.hpp \
+    detail/wait_handler.hpp \
+    detail/weak_ptr.hpp \
+    detail/win_event.hpp \
+    detail/win_fd_set_adapter.hpp \
+    detail/win_fenced_block.hpp \
+    detail/win_iocp_handle_read_op.hpp \
+    detail/win_iocp_handle_service.hpp \
+    detail/win_iocp_handle_write_op.hpp \
+    detail/win_iocp_io_service.hpp \
+    detail/win_iocp_io_service_fwd.hpp \
+    detail/win_iocp_null_buffers_op.hpp \
+    detail/win_iocp_operation.hpp \
+    detail/win_iocp_overlapped_op.hpp \
+    detail/win_iocp_overlapped_ptr.hpp \
+    detail/win_iocp_serial_port_service.hpp \
+    detail/win_iocp_socket_accept_op.hpp \
+    detail/win_iocp_socket_recv_op.hpp \
+    detail/win_iocp_socket_recvfrom_op.hpp \
+    detail/win_iocp_socket_send_op.hpp \
+    detail/win_iocp_socket_service.hpp \
+    detail/win_iocp_socket_service_base.hpp \
+    detail/win_mutex.hpp \
+    detail/win_signal_blocker.hpp \
+    detail/win_thread.hpp \
+    detail/win_tss_ptr.hpp \
+    detail/wince_thread.hpp \
+    detail/winsock_init.hpp \
+    detail/wrapped_handler.hpp \
+    error.hpp \
+    error_code.hpp \
+    handler_alloc_hook.hpp \
+    handler_invoke_hook.hpp \
+    impl/error.ipp \
+    impl/error_code.ipp \
+    impl/io_service.hpp \
+    impl/io_service.ipp \
+    impl/read.hpp \
+    impl/read.ipp \
+    impl/read_at.hpp \
+    impl/read_at.ipp \
+    impl/read_until.hpp \
+    impl/read_until.ipp \
+    impl/serial_port_base.hpp \
+    impl/serial_port_base.ipp \
+    impl/src.cpp \
+    impl/src.hpp \
+    impl/write.hpp \
+    impl/write.ipp \
+    impl/write_at.hpp \
+    impl/write_at.ipp \
+    io_service.hpp \
+    ip/address.hpp \
+    ip/address_v4.hpp \
+    ip/address_v6.hpp \
+    ip/basic_endpoint.hpp \
+    ip/basic_resolver.hpp \
+    ip/basic_resolver_entry.hpp \
+    ip/basic_resolver_iterator.hpp \
+    ip/basic_resolver_query.hpp \
+    ip/detail/endpoint.hpp \
+    ip/detail/impl/endpoint.ipp \
+    ip/detail/socket_option.hpp \
+    ip/host_name.hpp \
+    ip/icmp.hpp \
+    ip/impl/address.hpp \
+    ip/impl/address.ipp \
+    ip/impl/address_v4.hpp \
+    ip/impl/address_v4.ipp \
+    ip/impl/address_v6.hpp \
+    ip/impl/address_v6.ipp \
+    ip/impl/basic_endpoint.hpp \
+    ip/impl/host_name.ipp \
+    ip/multicast.hpp \
+    ip/resolver_query_base.hpp \
+    ip/resolver_service.hpp \
+    ip/tcp.hpp \
+    ip/udp.hpp \
+    ip/unicast.hpp \
+    ip/v6_only.hpp \
+    is_read_buffered.hpp \
+    is_write_buffered.hpp \
+    local/basic_endpoint.hpp \
+    local/connect_pair.hpp \
+    local/datagram_protocol.hpp \
+    local/detail/endpoint.hpp \
+    local/detail/impl/endpoint.ipp \
+    local/stream_protocol.hpp \
+    placeholders.hpp \
+    posix/basic_descriptor.hpp \
+    posix/basic_stream_descriptor.hpp \
+    posix/descriptor_base.hpp \
+    posix/stream_descriptor.hpp \
+    posix/stream_descriptor_service.hpp \
+    raw_socket_service.hpp \
+    read.hpp \
+    read_at.hpp \
+    read_until.hpp \
+    serial_port.hpp \
+    serial_port_base.hpp \
+    serial_port_service.hpp \
+    socket_acceptor_service.hpp \
+    socket_base.hpp \
+    ssl.hpp \
+    ssl/basic_context.hpp \
+    ssl/context.hpp \
+    ssl/context_base.hpp \
+    ssl/context_service.hpp \
+    ssl/detail/openssl_context_service.hpp \
+    ssl/detail/openssl_init.hpp \
+    ssl/detail/openssl_operation.hpp \
+    ssl/detail/openssl_stream_service.hpp \
+    ssl/detail/openssl_types.hpp \
+    ssl/stream.hpp \
+    ssl/stream_base.hpp \
+    ssl/stream_service.hpp \
+    strand.hpp \
+    stream_socket_service.hpp \
+    streambuf.hpp \
+    system_error.hpp \
+    thread.hpp \
+    time_traits.hpp \
+    version.hpp \
+    windows/basic_handle.hpp \
+    windows/basic_random_access_handle.hpp \
+    windows/basic_stream_handle.hpp \
+    windows/overlapped_ptr.hpp \
+    windows/random_access_handle.hpp \
+    windows/random_access_handle_service.hpp \
+    windows/stream_handle.hpp \
+    windows/stream_handle_service.hpp \
+    write.hpp \
+    write_at.hpp

+ 56 - 5
m4macros/ax_boost_for_bind10.m4

@@ -28,6 +28,14 @@ dnl                               cause build failure; otherwise set to "no"
 dnl   BOOST_MAPPED_FILE_CXXFLAG set to the compiler flag that would need to
 dnl                             compile managed_mapped_file (can be empty).
 dnl                             It is of no use if "WOULDFAIL" is yes.
+dnl   BOOST_STATIC_ASSERT_WOULDFAIL set to "yes" if BOOST_STATIC_ASSERT would
+dnl                                 cause build error; otherwise set to "no"
+
+dnl   BOOST_OFFSET_PTR_OLD set to "yes" if the version of boost is older than
+dnl                        1.48. Older versions of boost have a bug which
+dnl                        causes segfaults in offset_ptr implementation when
+dnl                        compiled by GCC with optimisations enabled.
+dnl                        See ticket no. 3025 for details.
 
 AC_DEFUN([AX_BOOST_FOR_BIND10], [
 AC_LANG_SAVE
@@ -62,6 +70,10 @@ fi
 AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
   AC_MSG_ERROR([Missing required header files.]))
 
+# clang can cause false positives with -Werror without -Qunused-arguments.
+# it can be triggered if used with ccache.
+AC_CHECK_DECL([__clang__], [CLANG_CXXFLAGS="-Qunused-arguments"], [])
+
 # Detect whether Boost tries to use threads by default, and, if not,
 # make it sure explicitly.  In some systems the automatic detection
 # may depend on preceding header files, and if inconsistency happens
@@ -78,6 +90,8 @@ AC_TRY_COMPILE([
 
 # Boost offset_ptr is known to not compile on some platforms, depending on
 # boost version, its local configuration, and compiler.  Detect it.
+CXXFLAGS_SAVED="$CXXFLAGS"
+CXXFLAGS="$CXXFLAGS $CLANG_CXXFLAGS -Werror"
 AC_MSG_CHECKING([Boost offset_ptr compiles])
 AC_TRY_COMPILE([
 #include <boost/interprocess/offset_ptr.hpp>
@@ -86,12 +100,13 @@ AC_TRY_COMPILE([
  BOOST_OFFSET_PTR_WOULDFAIL=no],
 [AC_MSG_RESULT(no)
  BOOST_OFFSET_PTR_WOULDFAIL=yes])
+CXXFLAGS="$CXXFLAGS_SAVED"
 
 # Detect build failure case known to happen with Boost installed via
 # FreeBSD ports
 if test "X$GXX" = "Xyes"; then
    CXXFLAGS_SAVED="$CXXFLAGS"
-   CXXFLAGS="$CXXFLAGS -Werror"
+   CXXFLAGS="$CXXFLAGS $CLANG_CXXFLAGS -Werror"
 
    AC_MSG_CHECKING([Boost numeric_cast compiles with -Werror])
    AC_TRY_COMPILE([
@@ -104,9 +119,21 @@ if test "X$GXX" = "Xyes"; then
     BOOST_NUMERIC_CAST_WOULDFAIL=yes])
 
    CXXFLAGS="$CXXFLAGS_SAVED"
+
+   AC_MSG_CHECKING([Boost rbtree is old])
+   AC_TRY_COMPILE([
+   #include <boost/version.hpp>
+   #if BOOST_VERSION < 104800
+   #error Too old
+   #endif
+   ],,[AC_MSG_RESULT(no)
+       BOOST_OFFSET_PTR_OLD=no
+   ],[AC_MSG_RESULT(yes)
+      BOOST_OFFSET_PTR_OLD=yes])
 else
    # This doesn't matter for non-g++
    BOOST_NUMERIC_CAST_WOULDFAIL=no
+   BOOST_OFFSET_PTR_OLD=no
 fi
 
 # Boost interprocess::managed_mapped_file is highly system dependent and
@@ -117,11 +144,9 @@ BOOST_MAPPED_FILE_CXXFLAG=
 CXXFLAGS_SAVED="$CXXFLAGS"
 try_flags="no"
 if test "X$GXX" = "Xyes"; then
-  CXXFLAGS="$CXXFLAGS -Wall -Wextra -Werror"
+  CXXFLAGS="$CXXFLAGS $CLANG_CXXFLAGS -Wall -Wextra -Werror"
   try_flags="$try_flags -Wno-error"
 fi
-# clang can cause false positives with -Werror without -Qunused-arguments
-AC_CHECK_DECL([__clang__], [CXXFLAGS="$CXXFLAGS -Qunused-arguments"], [])
 
 AC_MSG_CHECKING([Boost managed_mapped_file compiles])
 CXXFLAGS_SAVED2="$CXXFLAGS"
@@ -129,7 +154,7 @@ for flag in $try_flags; do
   if test "$flag" != no; then
     BOOST_MAPPED_FILE_CXXFLAG="$flag"
   fi
-  CXXFLAGS="$CXXFLAGS $BOOST_MAPPED_FILE_CXXFLAG"
+  CXXFLAGS="$CXXFLAGS $CLANG_CXXFLAGS $BOOST_MAPPED_FILE_CXXFLAG"
   AC_TRY_COMPILE([
   #include <boost/interprocess/managed_mapped_file.hpp>
   ],[
@@ -146,10 +171,36 @@ if test $BOOST_MAPPED_FILE_WOULDFAIL = yes; then
   AC_MSG_RESULT(no)
 fi
 
+# BOOST_STATIC_ASSERT in versions below Boost 1.54.0 is known to result
+# in warnings with GCC 4.8.  Detect it.
+AC_MSG_CHECKING([BOOST_STATIC_ASSERT compiles])
+AC_TRY_COMPILE([
+#include <boost/static_assert.hpp>
+void testfn(void) { BOOST_STATIC_ASSERT(true); }
+],,
+[AC_MSG_RESULT(yes)
+ BOOST_STATIC_ASSERT_WOULDFAIL=no],
+[AC_MSG_RESULT(no)
+ BOOST_STATIC_ASSERT_WOULDFAIL=yes])
+
 CXXFLAGS="$CXXFLAGS_SAVED"
 
 AC_SUBST(BOOST_INCLUDES)
 
+dnl Determine the Boost version, used mainly for config.report.
+AC_MSG_CHECKING([Boost version])
+cat > conftest.cpp << EOF
+#include <boost/version.hpp>
+AUTOCONF_BOOST_LIB_VERSION=BOOST_LIB_VERSION
+EOF
+
+BOOST_VERSION=`$CPP $CPPFLAGS conftest.cpp | grep '^AUTOCONF_BOOST_LIB_VERSION=' | $SED -e 's/^AUTOCONF_BOOST_LIB_VERSION=//' -e 's/_/./g' -e 's/"//g' 2> /dev/null`
+if test -z "$BOOST_VERSION"; then
+  BOOST_VERSION="unknown"
+fi
+$RM -f conftest.cpp
+AC_MSG_RESULT([$BOOST_VERSION])
+
 CPPFLAGS="$CPPFLAGS_SAVED"
 AC_LANG_RESTORE
 ])dnl AX_BOOST_FOR_BIND10

+ 17 - 0
m4macros/ax_python_sqlite3.m4

@@ -0,0 +1,17 @@
+dnl @synopsis AX_PYTHON_SQLITE3
+dnl
+dnl Test for the Python sqlite3 module used by BIND10's datasource
+dnl
+
+AC_DEFUN([AX_PYTHON_SQLITE3], [
+
+# Check for the python sqlite3 module
+AC_MSG_CHECKING(for python sqlite3 module)
+if "$PYTHON" -c 'import sqlite3' 2>/dev/null ; then
+    AC_MSG_RESULT(ok)
+else
+    AC_MSG_RESULT(missing)
+    AC_MSG_ERROR([Missing sqlite3 python module.])
+fi
+
+])dnl AX_PYTHON_SQLITE3

+ 19 - 2
m4macros/ax_sqlite3_for_bind10.m4

@@ -13,8 +13,25 @@ dnl in PATH.
 AC_DEFUN([AX_SQLITE3_FOR_BIND10], [
 
 PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.3.9,
-    have_sqlite="yes",
-    have_sqlite="no (sqlite3 not detected)")
+    [have_sqlite="yes"
+dnl Determine the SQLite version, used mainly for config.report.
+CPPFLAGS_SAVED="$CPPFLAGS"
+CPPFLAGS="${CPPFLAGS} $SQLITE_CFLAGS"
+AC_MSG_CHECKING([SQLite version])
+cat > conftest.c << EOF
+#include <sqlite3.h>
+AUTOCONF_SQLITE_VERSION=SQLITE_VERSION
+EOF
+
+SQLITE_VERSION=`$CPP $CPPFLAGS conftest.c | grep '^AUTOCONF_SQLITE_VERSION=' | $SED -e 's/^AUTOCONF_SQLITE_VERSION=//' -e 's/"//g' 2> /dev/null`
+if test -z "$SQLITE_VERSION"; then
+  SQLITE_VERSION="unknown"
+fi
+$RM -f conftest.c
+AC_MSG_RESULT([$SQLITE_VERSION])
+
+CPPFLAGS="$CPPFLAGS_SAVED"
+    ],have_sqlite="no (sqlite3 not detected)")
 
 # Check for sqlite3 program
 AC_PATH_PROG(SQLITE3_PROGRAM, sqlite3, no)

+ 2 - 0
src/Makefile.am

@@ -1,4 +1,6 @@
 SUBDIRS = lib bin
+# @todo hooks lib could be a configurable switch
+SUBDIRS += hooks
 
 EXTRA_DIST = \
 	cppcheck-suppress.lst		\

+ 13 - 2
src/bin/Makefile.am

@@ -1,5 +1,16 @@
+if BUILD_EXPERIMENTAL_RESOLVER
+# Build resolver only with --enable-experimental-resolver
+experimental_resolver = resolver
+endif
+
 SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq cmdctl auth xfrin \
-	xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 \
-	dbutil sysinfo
+	xfrout usermgr zonemgr stats tests $(experimental_resolver) \
+	sockcreator dhcp4 dhcp6 d2 dbutil sysinfo
+
+if USE_SHARED_MEMORY
+# Build the memory manager only if we have shared memory.
+# It is useless without it.
+SUBDIRS += memmgr
+endif
 
 check-recursive: all-recursive

+ 2 - 0
src/bin/auth/.gitignore

@@ -11,3 +11,5 @@
 /gen-statisticsitems.py.pre
 /statistics.cc
 /statistics_items.h
+/s-genstats
+/s-messages

+ 10 - 4
src/bin/auth/Makefile.am

@@ -20,7 +20,7 @@ CLEANFILES  = *.gcno *.gcda auth.spec spec_config.h
 CLEANFILES += auth_messages.h auth_messages.cc
 CLEANFILES += gen-statisticsitems.py
 # auto-generated by gen-statisticsitems.py
-CLEANFILES += statistics.cc statistics_items.h b10-auth.xml tests/statistics_unittest.cc
+CLEANFILES += statistics.cc statistics_items.h b10-auth.xml tests/statistics_unittest.cc s-genstats s-messages
 
 man_MANS = b10-auth.8
 DISTCLEANFILES = $(man_MANS)
@@ -45,18 +45,24 @@ statistics_items.h: statistics_items.h.pre statistics_msg_items.def
 statistics.cc: statistics.cc.pre statistics_msg_items.def
 tests/statistics_unittest.cc: tests/statistics_unittest.cc.pre statistics_msg_items.def
 
-gen-statisticsitems.py: gen-statisticsitems.py.pre
+gen-statisticsitems.py: gen-statisticsitems.py.pre Makefile
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" gen-statisticsitems.py.pre >$@
 	chmod +x $@
 
-auth.spec b10-auth.xml statistics_items.h statistics.cc tests/statistics_unittest.cc: Makefile gen-statisticsitems.py
+auth.spec b10-auth.xml statistics_items.h statistics.cc tests/statistics_unittest.cc: s-genstats
+
+s-genstats: gen-statisticsitems.py
 	./gen-statisticsitems.py
+	touch $@
 
 spec_config.h: spec_config.h.pre
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
 
-auth_messages.h auth_messages.cc: auth_messages.mes
+auth_messages.h auth_messages.cc: s-messages
+
+s-messages: auth_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/auth/auth_messages.mes
+	touch $@
 
 BUILT_SOURCES = spec_config.h auth_messages.h auth_messages.cc
 # auto-generated by gen-statisticsitems.py

+ 28 - 0
src/bin/auth/auth_messages.mes

@@ -145,12 +145,40 @@ reconfigure, and has now started this process.
 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_SEGMENT_BAD_CLASS invalid RRclass %1 at segment update
+A memory segment update message was sent to the authoritative
+server. But the class contained there is invalid. This means that the
+system is in an inconsistent state and the authoritative server aborts
+to minimize the problem. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR error updating the memory segment: %1
+The authoritative server tried to update the memory segment, but the update
+failed. The authoritative server aborts to avoid system inconsistency. This is
+likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC there's no data source named %2 in class %1
+The authoritative server was asked to update the memory segment of the
+given data source, but no data source by that name was found. The
+authoritative server aborts because this indicates that the system is in
+an inconsistent state. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS unknown class %1 at segment update
+A memory segment update message was sent to the authoritative
+server. The class name for which the update should happen is valid, but
+no client lists are configured for that class. The system is in an
+inconsistent state and the authoritative server aborts. This may be
+caused by a bug in the code.
+
 % 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_BUILDER_WAKE_ERR failed to wake up main thread: %1
+A low-level error happened when trying to send data to the main thread to wake
+it up. Terminating to prevent inconsistent state and possiblu hang ups.
+
 % 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

+ 73 - 25
src/bin/auth/auth_srv.cc

@@ -70,8 +70,6 @@
 
 using namespace std;
 
-using boost::shared_ptr;
-
 using namespace isc;
 using namespace isc::cc;
 using namespace isc::datasrc;
@@ -277,7 +275,7 @@ public:
     AddressList listen_addresses_;
 
     /// The TSIG keyring
-    const shared_ptr<TSIGKeyRing>* keyring_;
+    const boost::shared_ptr<TSIGKeyRing>* keyring_;
 
     /// The data source client list manager
     auth::DataSrcClientsMgr datasrc_clients_mgr_;
@@ -308,6 +306,8 @@ public:
                       MessageAttributes& stats_attrs,
                       const bool done);
 
+    /// Are we currently subscribed to the SegmentReader group?
+    bool readers_group_subscribed_;
 private:
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
@@ -321,8 +321,10 @@ AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client,
     xfrin_session_(NULL),
     counters_(),
     keyring_(NULL),
+    datasrc_clients_mgr_(io_service_),
     ddns_base_forwarder_(ddns_forwarder),
     ddns_forwarder_(NULL),
+    readers_group_subscribed_(false),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
 {}
@@ -373,27 +375,11 @@ public:
     {}
 };
 
-// This is a derived class of \c SimpleCallback, to serve
-// as a callback in the asiolink module.  It checks for queued
-// configuration messages, and executes them if found.
-class ConfigChecker : public SimpleCallback {
-public:
-    ConfigChecker(AuthSrv* srv) : server_(srv) {}
-    virtual void operator()(const IOMessage&) const {
-        ModuleCCSession* cfg_session = server_->getConfigSession();
-        if (cfg_session != NULL && cfg_session->hasQueuedMsgs()) {
-            cfg_session->checkCommand();
-        }
-    }
-private:
-    AuthSrv* server_;
-};
-
 AuthSrv::AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
-                 isc::util::io::BaseSocketSessionForwarder& ddns_forwarder)
+                 isc::util::io::BaseSocketSessionForwarder& ddns_forwarder) :
+    dnss_(NULL)
 {
     impl_ = new AuthSrvImpl(xfrout_client, ddns_forwarder);
-    checkin_ = new ConfigChecker(this);
     dns_lookup_ = new MessageLookup(this);
     dns_answer_ = new MessageAnswer(this);
 }
@@ -405,7 +391,6 @@ AuthSrv::stop() {
 
 AuthSrv::~AuthSrv() {
     delete impl_;
-    delete checkin_;
     delete dns_lookup_;
     delete dns_answer_;
 }
@@ -523,6 +508,8 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         return;
     }
 
+    stats_attrs.setRequestRD(message.getHeaderFlag(Message::HEADERFLAG_RD));
+
     const Opcode& opcode = message.getOpcode();
     // Get opcode at this point; for all requests regardless of message body
     // sanity check.
@@ -664,7 +651,7 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message,
 
     try {
         const ConstQuestionPtr question = *message.beginQuestion();
-        const shared_ptr<datasrc::ClientList>
+        const boost::shared_ptr<datasrc::ClientList>
             list(datasrc_holder.findClientList(question->getClass()));
         if (list) {
             const RRType& qtype = question->getType();
@@ -779,7 +766,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     bool is_auth = false;
     {
         auth::DataSrcClientsMgr::Holder datasrc_holder(datasrc_clients_mgr_);
-        const shared_ptr<datasrc::ClientList> dsrc_clients =
+        const boost::shared_ptr<datasrc::ClientList> dsrc_clients =
             datasrc_holder.findClientList(question->getClass());
         is_auth = dsrc_clients &&
             dsrc_clients->find(question->getName(), true, false).exact_match_;
@@ -913,7 +900,7 @@ AuthSrv::setDNSService(isc::asiodns::DNSServiceBase& dnss) {
 }
 
 void
-AuthSrv::setTSIGKeyRing(const shared_ptr<TSIGKeyRing>* keyring) {
+AuthSrv::setTSIGKeyRing(const boost::shared_ptr<TSIGKeyRing>* keyring) {
     impl_->keyring_ = keyring;
 }
 
@@ -937,3 +924,64 @@ void
 AuthSrv::setTCPRecvTimeout(size_t timeout) {
     dnss_->setTCPRecvTimeout(timeout);
 }
+
+namespace {
+
+bool
+hasMappedSegment(auth::DataSrcClientsMgr& mgr) {
+    auth::DataSrcClientsMgr::Holder holder(mgr);
+    const std::vector<dns::RRClass>& classes(holder.getClasses());
+    BOOST_FOREACH(const dns::RRClass& rrclass, classes) {
+        const boost::shared_ptr<datasrc::ConfigurableClientList>&
+            list(holder.findClientList(rrclass));
+        const std::vector<DataSourceStatus>& states(list->getStatus());
+        BOOST_FOREACH(const datasrc::DataSourceStatus& status, states) {
+            if (status.getSegmentState() != datasrc::SEGMENT_UNUSED &&
+                status.getSegmentType() == "mapped")
+                // We use some segment and it's not a local one, so it
+                // must be remote.
+                return true;
+        }
+    }
+    // No remote segment found in any of the lists
+    return false;
+}
+
+}
+
+void
+AuthSrv::listsReconfigured() {
+    const bool has_remote = hasMappedSegment(impl_->datasrc_clients_mgr_);
+    if (has_remote && !impl_->readers_group_subscribed_) {
+        impl_->config_session_->subscribe("SegmentReader");
+        impl_->config_session_->
+            setUnhandledCallback(boost::bind(&AuthSrv::foreignCommand, this,
+                                             _1, _2, _3));
+        impl_->readers_group_subscribed_ = true;
+    } else if (!has_remote && impl_->readers_group_subscribed_) {
+        impl_->config_session_->unsubscribe("SegmentReader");
+        impl_->config_session_->
+            setUnhandledCallback(isc::config::ModuleCCSession::
+                                 UnhandledCallback());
+        impl_->readers_group_subscribed_ = false;
+    }
+}
+
+void
+AuthSrv::reconfigureDone(ConstElementPtr params) {
+    // ACK the segment
+    impl_->config_session_->
+        groupSendMsg(isc::config::createCommand("segment_info_update_ack",
+                                                params), "MemMgr");
+}
+
+void
+AuthSrv::foreignCommand(const std::string& command, const std::string&,
+                        const ConstElementPtr& params)
+{
+    if (command == "segment_info_update") {
+        impl_->datasrc_clients_mgr_.
+            segmentInfoUpdate(params, boost::bind(&AuthSrv::reconfigureDone,
+                                                  this, params));
+    }
+}

+ 11 - 4
src/bin/auth/auth_srv.h

@@ -192,9 +192,6 @@ public:
     /// \brief Return pointer to the DNS Answer callback function
     isc::asiodns::DNSAnswer* getDNSAnswerProvider() const { return (dns_answer_); }
 
-    /// \brief Return pointer to the Checkin callback function
-    isc::asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
-
     /// \brief Return data source clients manager.
     ///
     /// \throw None
@@ -275,9 +272,19 @@ public:
     /// open forever.
     void setTCPRecvTimeout(size_t timeout);
 
+    /// \brief Notify the authoritative server that the client lists were
+    ///     reconfigured.
+    ///
+    /// This is to be called when the work thread finishes reconfiguration
+    /// of the data sources. It involeves some book keeping and asking the
+    /// memory manager for segments, if some are remotely mapped.
+    void listsReconfigured();
+
 private:
+    void reconfigureDone(isc::data::ConstElementPtr request);
+    void foreignCommand(const std::string& command, const std::string&,
+                        const isc::data::ConstElementPtr& params);
     AuthSrvImpl* impl_;
-    isc::asiolink::SimpleCallback* checkin_;
     isc::asiodns::DNSLookup* dns_lookup_;
     isc::asiodns::DNSAnswer* dns_answer_;
     isc::asiodns::DNSServiceBase* dnss_;

+ 24 - 3
src/bin/auth/b10-auth.xml.pre

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>February 5, 2013</date>
+    <date>July 16, 2013</date>
   </refentryinfo>
 
   <refmeta>
@@ -81,8 +81,8 @@
       <varlistentry>
         <term><option>-v</option></term>
         <listitem><para>
-	  Enable verbose logging mode. This enables logging of
-	  diagnostic messages at the maximum debug level.
+          Enable verbose logging mode. This enables logging of
+          diagnostic messages at the maximum debug level.
         </para></listitem>
       </varlistentry>
 
@@ -248,6 +248,27 @@
         but remember that if there's any error related to TSIG, some
         of the counted opcode may not be trustworthy.
       </para>
+
+      <para>
+        The <quote>qryrecursion</quote> counter is limited to queries
+        (requests of opcode 0) even though the RD bit is not specific
+        to queries.  In practice, this bit is generally just ignored for
+        other types of requests, while DNS servers behave differently
+        for queries depending on this bit.  It is also known that
+        some authoritative-only servers receive a non negligible
+        number of queries with the RD bit being set, so it would be
+        of particular interest to have a specific counters for such
+        requests.
+      </para>
+
+      <para>
+        There are two request counters related to EDNS:
+        <quote>request.edns0</quote> and <quote>request.badednsver</quote>.
+        The latter is a counter of requests with unsupported EDNS version:
+        other than version 0 in the current implementation. Therefore, total
+        number of requests with EDNS is a sum of <quote>request.edns0</quote>
+        and <quote>request.badednsver</quote>.
+      </para>
     </note>
 
   </refsect1>

+ 278 - 25
src/bin/auth/datasrc_clients_mgr.h

@@ -29,6 +29,9 @@
 #include <datasrc/client_list.h>
 #include <datasrc/memory/zone_writer.h>
 
+#include <asiolink/io_service.h>
+#include <asiolink/local_socket.h>
+
 #include <auth/auth_log.h>
 #include <auth/datasrc_config.h>
 
@@ -36,11 +39,16 @@
 #include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+#include <boost/foreach.hpp>
 
 #include <exception>
 #include <cassert>
+#include <cerrno>
 #include <list>
 #include <utility>
+#include <sys/types.h>
+#include <sys/socket.h>
 
 namespace isc {
 namespace auth {
@@ -73,17 +81,45 @@ enum CommandID {
     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.
+    SEGMENT_INFO_UPDATE, ///< The memory manager sent an update about segments.
     SHUTDOWN,     ///< Shutdown the builder; no argument
     NUM_COMMANDS
 };
 
+/// \brief Callback to be called when the command is completed.
+typedef boost::function<void ()> FinishedCallback;
+
 /// \brief The data type passed from DataSrcClientsMgr to
-/// DataSrcClientsBuilder.
+///     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<CommandID, data::ConstElementPtr> Command;
+/// This just holds the data items together, no logic or protection
+/// is present here.
+struct Command {
+    /// \brief Constructor
+    ///
+    /// It just initializes the member variables of the same names
+    /// as the parameters.
+    Command(CommandID id, const data::ConstElementPtr& params,
+            const FinishedCallback& callback) :
+        id(id),
+        params(params),
+        callback(callback)
+    {}
+    /// \brief The command to execute
+    CommandID id;
+    /// \brief Argument of the command.
+    ///
+    /// If the command takes no argument, it should be null pointer.
+    ///
+    /// This may be a null pointer if the command takes no parameters.
+    data::ConstElementPtr params;
+    /// \brief A callback to be called once the command finishes.
+    ///
+    /// This may be an empty boost::function. In such case, no callback
+    /// will be called after completion.
+    FinishedCallback callback;
+};
+
 } // namespace datasrc_clientmgr_internal
 
 /// \brief Frontend to the manager object for data source clients.
@@ -113,6 +149,24 @@ private:
                      boost::shared_ptr<datasrc::ConfigurableClientList> >
     ClientListsMap;
 
+    class FDGuard : boost::noncopyable {
+    public:
+        FDGuard(DataSrcClientsMgrBase *mgr) :
+            mgr_(mgr)
+        {}
+        ~FDGuard() {
+            if (mgr_->read_fd_ != -1) {
+                close(mgr_->read_fd_);
+            }
+            if (mgr_->write_fd_ != -1) {
+                close(mgr_->write_fd_);
+            }
+        }
+    private:
+        DataSrcClientsMgrBase* mgr_;
+    };
+    friend class FDGuard;
+
 public:
     /// \brief Thread-safe accessor to the data source client lists.
     ///
@@ -159,6 +213,24 @@ public:
                 return (it->second);
             }
         }
+        /// \brief Return list of classes that are present.
+        ///
+        /// Get the list of classes for which there's a client list. It is
+        /// returned in form of a vector, copied from the internals. As the
+        /// number of classes in there is expected to be small, it is not
+        /// a performance issue.
+        ///
+        /// \return The list of classes.
+        /// \throw std::bad_alloc for problems allocating the result.
+        std::vector<dns::RRClass> getClasses() const {
+            std::vector<dns::RRClass> result;
+            for (ClientListsMap::const_iterator it =
+                 mgr_.clients_map_->begin(); it != mgr_.clients_map_->end();
+                 ++it) {
+                result.push_back(it->first);
+            }
+            return (result);
+        }
     private:
         DataSrcClientsMgrBase& mgr_;
         typename MutexType::Locker locker_;
@@ -176,12 +248,20 @@ public:
     ///
     /// \throw std::bad_alloc internal memory allocation failure.
     /// \throw isc::Unexpected general unexpected system errors.
-    DataSrcClientsMgrBase() :
+    DataSrcClientsMgrBase(asiolink::IOService& service) :
         clients_map_(new ClientListsMap),
-        builder_(&command_queue_, &cond_, &queue_mutex_, &clients_map_,
-                 &map_mutex_),
-        builder_thread_(boost::bind(&BuilderType::run, &builder_))
-    {}
+        fd_guard_(new FDGuard(this)),
+        read_fd_(-1), write_fd_(-1),
+        builder_(&command_queue_, &callback_queue_, &cond_, &queue_mutex_,
+                 &clients_map_, &map_mutex_, createFds()),
+        builder_thread_(boost::bind(&BuilderType::run, &builder_)),
+        wakeup_socket_(service, read_fd_)
+    {
+        // Schedule wakeups when callbacks are pushed.
+        wakeup_socket_.asyncRead(
+            boost::bind(&DataSrcClientsMgrBase::processCallbacks, this, _1),
+            buffer, 1);
+    }
 
     /// \brief The destructor.
     ///
@@ -220,6 +300,7 @@ public:
                       AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR);
         }
 
+        processCallbacks(); // Any leftover callbacks
         cleanup();              // see below
     }
 
@@ -234,11 +315,18 @@ public:
     /// \brief std::bad_alloc
     ///
     /// \param config_arg The new data source configuration.  Must not be NULL.
-    void reconfigure(data::ConstElementPtr config_arg) {
+    /// \param callback Called once the reconfigure command completes. It is
+    ///     called in the main thread (not in the work one). It should be
+    ///     exceptionless.
+    void reconfigure(const data::ConstElementPtr& config_arg,
+                     const datasrc_clientmgr_internal::FinishedCallback&
+                     callback = datasrc_clientmgr_internal::FinishedCallback())
+    {
         if (!config_arg) {
             isc_throw(InvalidParameter, "Invalid null config argument");
         }
-        sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg);
+        sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg,
+                    callback);
         reconfigureHook();      // for test's customization
     }
 
@@ -257,12 +345,18 @@ public:
     /// \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)
+    /// \param callback Called once the loadZone command completes. It
+    ///     is called in the main thread, not in the work thread. It should
+    ///     be exceptionless.
     ///
     /// \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) {
+    loadZone(const data::ConstElementPtr& args,
+             const datasrc_clientmgr_internal::FinishedCallback& callback =
+             datasrc_clientmgr_internal::FinishedCallback())
+    {
         if (!args) {
             isc_throw(CommandError, "loadZone argument empty");
         }
@@ -303,7 +397,37 @@ public:
         // 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);
+        sendCommand(datasrc_clientmgr_internal::LOADZONE, args, callback);
+    }
+
+    void segmentInfoUpdate(const data::ConstElementPtr& args,
+                           const datasrc_clientmgr_internal::FinishedCallback&
+                           callback =
+                           datasrc_clientmgr_internal::FinishedCallback()) {
+        // Some minimal validation
+        if (!args) {
+            isc_throw(CommandError, "segmentInfoUpdate argument empty");
+        }
+        if (args->getType() != isc::data::Element::map) {
+            isc_throw(CommandError, "segmentInfoUpdate argument not a map");
+        }
+        const char* params[] = {
+            "data-source-name",
+            "data-source-class",
+            "segment-params",
+            NULL
+        };
+        for (const char** param = params; *param; ++param) {
+            if (!args->contains(*param)) {
+                isc_throw(CommandError,
+                          "segmentInfoUpdate argument has no '" << param <<
+                          "' value");
+            }
+        }
+
+
+        sendCommand(datasrc_clientmgr_internal::SEGMENT_INFO_UPDATE, args,
+                    callback);
     }
 
 private:
@@ -317,30 +441,79 @@ private:
     void reconfigureHook() {}
 
     void sendCommand(datasrc_clientmgr_internal::CommandID command,
-                     data::ConstElementPtr arg)
+                     const data::ConstElementPtr& arg,
+                     const datasrc_clientmgr_internal::FinishedCallback&
+                     callback = datasrc_clientmgr_internal::FinishedCallback())
     {
         // 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));
+            datasrc_clientmgr_internal::Command(command, arg, callback));
         cond_.signal();
     }
 
+    int createFds() {
+        int fds[2];
+        int result = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
+        if (result != 0) {
+            isc_throw(Unexpected, "Can't create socket pair: " <<
+                      strerror(errno));
+        }
+        read_fd_ = fds[0];
+        write_fd_ = fds[1];
+        return write_fd_;
+    }
+
+    void processCallbacks(const std::string& error = std::string()) {
+        // Schedule the next read.
+        wakeup_socket_.asyncRead(
+            boost::bind(&DataSrcClientsMgrBase::processCallbacks, this, _1),
+            buffer, 1);
+        if (!error.empty()) {
+            // Generally, there should be no errors (as we are the other end
+            // as well), but check just in case.
+            isc_throw(Unexpected, error);
+        }
+
+        // Steal the callbacks into local copy.
+        std::list<datasrc_clientmgr_internal::FinishedCallback> queue;
+        {
+            typename MutexType::Locker locker(queue_mutex_);
+            queue.swap(callback_queue_);
+        }
+
+        // Execute the callbacks
+        BOOST_FOREACH(const datasrc_clientmgr_internal::FinishedCallback&
+                      callback, queue) {
+            callback();
+        }
+    }
+
     //
     // The following are shared with the builder.
     //
     // The list is used as a one-way queue: back-in, front-out
     std::list<datasrc_clientmgr_internal::Command> command_queue_;
+    // Similar to above, for the callbacks that are ready to be called.
+    // While the command queue is for sending commands from the main thread
+    // to the work thread, this one is for the other direction. Protected
+    // by the same mutex (queue_mutex_).
+    std::list<datasrc_clientmgr_internal::FinishedCallback> callback_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
+    boost::scoped_ptr<FDGuard> fd_guard_; // A guard to close the fds.
+    int read_fd_, write_fd_;    // Descriptors for wakeup
     MutexType map_mutex_;       // mutex to protect the clients map
 
     BuilderType builder_;
     ThreadType builder_thread_; // for safety this should be placed last
+    isc::asiolink::LocalSocket wakeup_socket_; // For integration of read_fd_
+                                               // to the asio loop
+    char buffer[1];   // Buffer for the wakeup socket.
 };
 
 namespace datasrc_clientmgr_internal {
@@ -385,12 +558,15 @@ public:
     ///
     /// \throw None
     DataSrcClientsBuilderBase(std::list<Command>* command_queue,
+                              std::list<FinishedCallback>* callback_queue,
                               CondVarType* cond, MutexType* queue_mutex,
                               datasrc::ClientListMapPtr* clients_map,
-                              MutexType* map_mutex
+                              MutexType* map_mutex,
+                              int wake_fd
         ) :
-        command_queue_(command_queue), cond_(cond), queue_mutex_(queue_mutex),
-        clients_map_(clients_map), map_mutex_(map_mutex)
+        command_queue_(command_queue), callback_queue_(callback_queue),
+        cond_(cond), queue_mutex_(queue_mutex),
+        clients_map_(clients_map), map_mutex_(map_mutex), wake_fd_(wake_fd)
     {}
 
     /// \brief The main loop.
@@ -450,6 +626,44 @@ private:
         }
     }
 
+    void doSegmentUpdate(const isc::data::ConstElementPtr& arg) {
+        try {
+            const isc::dns::RRClass
+                rrclass(arg->get("data-source-class")->stringValue());
+            const std::string&
+                name(arg->get("data-source-name")->stringValue());
+            const isc::data::ConstElementPtr& segment_params =
+                arg->get("segment-params");
+            typename MutexType::Locker locker(*map_mutex_);
+            const boost::shared_ptr<isc::datasrc::ConfigurableClientList>&
+                list = (**clients_map_)[rrclass];
+            if (!list) {
+                LOG_FATAL(auth_logger,
+                          AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS)
+                    .arg(rrclass);
+                std::terminate();
+            }
+            if (!list->resetMemorySegment(name,
+                    isc::datasrc::memory::ZoneTableSegment::READ_ONLY,
+                    segment_params)) {
+                LOG_FATAL(auth_logger,
+                          AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC)
+                    .arg(rrclass).arg(name);
+                std::terminate();
+            }
+        } catch (const isc::dns::InvalidRRClass& irce) {
+            LOG_FATAL(auth_logger,
+                      AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_BAD_CLASS)
+                .arg(arg->get("data-source-class"));
+            std::terminate();
+        } catch (const isc::Exception& e) {
+            LOG_FATAL(auth_logger,
+                      AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR)
+                .arg(e.what());
+            std::terminate();
+        }
+    }
+
     void doLoadZone(const isc::data::ConstElementPtr& arg);
     boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
         datasrc::ConfigurableClientList& client_list,
@@ -457,10 +671,12 @@ private:
 
     // The following are shared with the manager
     std::list<Command>* command_queue_;
+    std::list<FinishedCallback> *callback_queue_;
     CondVarType* cond_;
     MutexType* queue_mutex_;
     datasrc::ClientListMapPtr* clients_map_;
     MutexType* map_mutex_;
+    int wake_fd_;
 };
 
 // Shortcut typedef for normal use
@@ -494,6 +710,31 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::run() {
                               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR).
                         arg(e.what());
                 }
+                if (current_commands.front().callback) {
+                    // Lock the queue
+                    typename MutexType::Locker locker(*queue_mutex_);
+                    callback_queue_->
+                        push_back(current_commands.front().callback);
+                    // Wake up the other end. If it would block, there are data
+                    // and it'll wake anyway.
+                    int result = send(wake_fd_, "w", 1, MSG_DONTWAIT);
+                    if (result == -1 &&
+                        (errno != EWOULDBLOCK && errno != EAGAIN)) {
+                        // Note: the strerror might not be thread safe, as
+                        // subsequent call to it might change the returned
+                        // string. But that is unlikely and strerror_r is
+                        // not portable and we are going to terminate anyway,
+                        // so that's better than nothing.
+                        //
+                        // Also, this error handler is not tested. It should
+                        // be generally impossible to happen, so it is hard
+                        // to trigger in controlled way.
+                        LOG_FATAL(auth_logger,
+                                  AUTH_DATASRC_CLIENTS_BUILDER_WAKE_ERR).
+                            arg(strerror(errno));
+                        std::terminate();
+                    }
+                }
                 current_commands.pop_front();
             }
         }
@@ -515,23 +756,26 @@ bool
 DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     const Command& command)
 {
-    const CommandID cid = command.first;
+    const CommandID cid = command.id;
     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<const char*, NUM_COMMANDS> command_desc = {
-        {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
+        {"NOOP", "RECONFIGURE", "LOADZONE", "SEGMENT_INFO_UPDATE", "SHUTDOWN"}
     };
     LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
-    switch (command.first) {
+    switch (command.id) {
     case RECONFIGURE:
-        doReconfigure(command.second);
+        doReconfigure(command.params);
         break;
     case LOADZONE:
-        doLoadZone(command.second);
+        doLoadZone(command.params);
+        break;
+    case SEGMENT_INFO_UPDATE:
+        doSegmentUpdate(command.params);
         break;
     case SHUTDOWN:
         return (false);
@@ -623,7 +867,7 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
     datasrc::ConfigurableClientList::ZoneWriterPair writerpair;
     {
         typename MutexType::Locker locker(*map_mutex_);
-        writerpair = client_list.getCachedZoneWriter(origin);
+        writerpair = client_list.getCachedZoneWriter(origin, false);
     }
 
     switch (writerpair.first) {
@@ -639,12 +883,21 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
                   AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE_NOCACHE)
             .arg(origin).arg(rrclass);
         break;                  // return NULL below
+    case datasrc::ConfigurableClientList::CACHE_NOT_WRITABLE:
+        // This is an internal error. Auth server should skip reloading zones
+        // on non writable caches.
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": internal failure, in-memory cache "
+                  "is not writable");
     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");
+    default:                    // other cases can really never happen
+        isc_throw(Unexpected, "Impossible result in getting data source "
+                  "ZoneWriter: " << writerpair.first);
     }
 
     return (boost::shared_ptr<datasrc::memory::ZoneWriter>());

+ 5 - 4
src/bin/auth/main.cc

@@ -109,9 +109,11 @@ datasrcConfigHandler(AuthSrv* server, bool* first_time,
         assert(config_session != NULL);
         *first_time = false;
         server->getDataSrcClientsMgr().reconfigure(
-            config_session->getRemoteConfigValue("data_sources", "classes"));
+            config_session->getRemoteConfigValue("data_sources", "classes"),
+            boost::bind(&AuthSrv::listsReconfigured, server));
     } else if (config->contains("classes")) {
-        server->getDataSrcClientsMgr().reconfigure(config->get("classes"));
+        server->getDataSrcClientsMgr().reconfigure(config->get("classes"),
+            boost::bind(&AuthSrv::listsReconfigured, server));
     }
 }
 
@@ -173,12 +175,11 @@ main(int argc, char* argv[]) {
         auth_server = auth_server_.get();
         LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
 
-        SimpleCallback* checkin = auth_server->getCheckinProvider();
         IOService& io_service = auth_server->getIOService();
         DNSLookup* lookup = auth_server->getDNSLookupProvider();
         DNSAnswer* answer = auth_server->getDNSAnswerProvider();
 
-        DNSService dns_service(io_service, checkin, lookup, answer);
+        DNSService dns_service(io_service, lookup, answer);
         auth_server->setDNSService(dns_service);
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_DNS_SERVICES_CREATED);
 

+ 7 - 1
src/bin/auth/query.cc

@@ -255,7 +255,7 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
     if (nsec->getRdataCount() == 0) {
         isc_throw(BadNSEC, "NSEC for WILDCARD_NXRRSET is empty");
     }
-    
+
     ConstZoneFinderContextPtr fcontext =
         finder.find(*qname_, RRType::NSEC(),
                     dnssec_opt_ | ZoneFinder::NO_WILDCARD);
@@ -386,6 +386,12 @@ Query::process(datasrc::ClientList& client_list,
         response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
         response_->setRcode(Rcode::REFUSED());
         return;
+    } else if (!result.finder_) {
+        // We found a matching zone in a data source but its data are not
+        // available.
+        response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
+        response_->setRcode(Rcode::SERVFAIL());
+        return;
     }
     ZoneFinder& zfinder = *result.finder_;
 

+ 12 - 3
src/bin/auth/statistics.cc.pre

@@ -26,6 +26,8 @@
 
 #include <boost/optional.hpp>
 
+#include <stdint.h>
+
 using namespace isc::dns;
 using namespace isc::auth;
 using namespace isc::statistics;
@@ -53,8 +55,8 @@ fillNodes(const Counter& counter,
             fillNodes(counter, type_tree[i].sub_counters, sub_counters);
         } else {
             trees->set(type_tree[i].name,
-                       Element::create(static_cast<long int>(
-                           counter.get(type_tree[i].counter_id)))
+                       Element::create(static_cast<int64_t>(
+                           counter.get(type_tree[i].counter_id) & 0x7fffffffffffffffLL))
                        );
         }
     }
@@ -138,7 +140,14 @@ Counters::incRequest(const MessageAttributes& msgattrs) {
     // if a short message which does not contain DNS header is received, or
     // a response message (i.e. QR bit is set) is received.
     if (opcode) {
-        server_msg_counter_.inc(opcode_to_msgcounter[opcode.get().getCode()]);
+        server_msg_counter_.inc(opcode_to_msgcounter[opcode->getCode()]);
+
+        if (opcode.get() == Opcode::QUERY()) {
+            // Recursion Desired bit
+            if (msgattrs.requestHasRD()) {
+                server_msg_counter_.inc(MSG_QRYRECURSION);
+            }
+        }
     }
 
     // TSIG

+ 18 - 0
src/bin/auth/statistics.h

@@ -66,6 +66,8 @@ private:
     enum BitAttributes {
         REQ_WITH_EDNS_0,            // request with EDNS ver.0
         REQ_WITH_DNSSEC_OK,         // DNSSEC OK (DO) bit is set in request
+        REQ_WITH_RD,                // Recursion Desired (RD) bit is set in
+                                    // request
         REQ_TSIG_SIGNED,            // request is signed with valid TSIG
         REQ_BADSIG,                 // request is signed but bad signature
         RES_IS_TRUNCATED,           // response is truncated
@@ -170,6 +172,22 @@ public:
         bit_attributes_[REQ_WITH_DNSSEC_OK] = with_dnssec_ok;
     }
 
+    /// \brief Return Recursion Desired (RD) bit of the request.
+    ///
+    /// \return true if Recursion Desired (RD) bit of the request is set
+    /// \throw None
+    bool requestHasRD() const {
+        return (bit_attributes_[REQ_WITH_RD]);
+    }
+
+    /// \brief Set Recursion Desired (RD) bit of the request.
+    ///
+    /// \param with_rd true if Recursion Desired (RD)bit of the request is set
+    /// \throw None
+    void setRequestRD(const bool with_rd) {
+        bit_attributes_[REQ_WITH_RD] = with_rd;
+    }
+
     /// \brief Return whether the request is TSIG signed or not.
     ///
     /// \return true if the request is TSIG signed

+ 1 - 0
src/bin/auth/statistics_msg_items.def

@@ -31,6 +31,7 @@ qrynoauthans	MSG_QRYNOAUTHANS		Number of queries received by the b10-auth server
 qryreferral	MSG_QRYREFERRAL			Number of queries received by the b10-auth server resulted in referral answer.
 qrynxrrset	MSG_QRYNXRRSET			Number of queries received by the b10-auth server resulted in NoError and AA bit is set in the response, but the number of answer RR == 0.
 authqryrej	MSG_QRYREJECT			Number of authoritative queries rejected by the b10-auth server.
+qryrecursion	MSG_QRYRECURSION		Number of queries received by the b10-auth server with "Recursion Desired" (RD) bit was set.
 rcode		msg_counter_rcode	Rcode statistics	=
 	noerror		MSG_RCODE_NOERROR	Number of requests received by the b10-auth server resulted in RCODE = 0 (NoError).
 	formerr		MSG_RCODE_FORMERR	Number of requests received by the b10-auth server resulted in RCODE = 1 (FormErr).

+ 80 - 2
src/bin/auth/tests/auth_srv_unittest.cc

@@ -39,6 +39,9 @@
 #include <auth/statistics_items.h>
 #include <auth/datasrc_config.h>
 
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+
 #include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/dnsmessage_test.h>
@@ -265,14 +268,15 @@ updateDatabase(AuthSrv& server, const char* params) {
 
 void
 updateInMemory(AuthSrv& server, const char* origin, const char* filename,
-               bool with_static = true)
+               bool with_static = true, bool mapped = false)
 {
     string spec_txt = "{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
         "   \"params\": {"
         "       \"" + string(origin) + "\": \"" + string(filename) + "\""
-        "   },"
+        "   }," +
+        string(mapped ? "\"cache-type\": \"mapped\"," : "") +
         "   \"cache-enable\": true"
         "}]";
     if (with_static) {
@@ -1210,6 +1214,38 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
+TEST_F(AuthSrvTest, emptyZone) {
+    // Similar to the previous setup, but the configuration has an error
+    // (zone file doesn't exist) and the query should result in SERVFAIL.
+    // Here we check the rcode other header parameters, and statistics.
+
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {\"example.com\": \"nosuchfile.zone\"},"
+        "   \"cache-enable\": true"
+        "}]}"));
+    installDataSrcClientLists(server, configureDataSource(config));
+    createDataFromFile("examplequery_fromWire.wire");
+    server.processMessage(*io_message, *parse_message, *response_obuffer,
+                          &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+    checkAllRcodeCountersZeroExcept(Rcode::SERVFAIL(), 1);
+    ConstElementPtr stats = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["qrynoauthans"] = 1;
+    expect["rcode.servfail"] = 1;
+    checkStatisticsCounters(stats, expect);
+}
+
 TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
     // In this example, we do simple check that query is handled from the
     // query handler class, and confirm it returns no error and a non empty
@@ -2106,4 +2142,46 @@ TEST_F(AuthSrvTest, loadZoneCommand) {
     sendCommand(server, "loadzone", args, 0);
 }
 
+// Test that the auth server subscribes to the segment readers group when
+// there's a remotely mapped segment.
+#ifdef USE_SHARED_MEMORY
+TEST_F(AuthSrvTest, postReconfigure) {
+#else
+TEST_F(AuthSrvTest, DISABLED_postReconfigure) {
+#endif
+    FakeSession session(ElementPtr(new ListElement),
+                        ElementPtr(new ListElement),
+                        ElementPtr(new ListElement));
+    const string specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec");
+    session.getMessages()->add(isc::config::createAnswer());
+    isc::config::ModuleCCSession mccs(specfile, session, NULL, NULL, false,
+                                      false);
+    server.setConfigSession(&mccs);
+    // First, no lists are there, so no reason to subscribe
+    server.listsReconfigured();
+    EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+    // Enable remote segment
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false, true);
+    {
+        DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+        DataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_EQ(SEGMENT_WAITING, holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentState());
+    }
+    server.listsReconfigured();
+    EXPECT_TRUE(session.haveSubscription("SegmentReader", "*"));
+    // Set the segment to local again
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    {
+        DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+        DataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_EQ(SEGMENT_INUSE, holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentState());
+        EXPECT_EQ("local", holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentType());
+    }
+    server.listsReconfigured();
+    EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+}
+
 }

+ 265 - 33
src/bin/auth/tests/datasrc_clients_builder_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 <config.h>
+
 #include <util/unittests/check_valgrind.h>
 
 #include <dns/name.h>
@@ -34,9 +36,14 @@
 
 #include <boost/function.hpp>
 
+#include <sys/types.h>
+#include <sys/socket.h>
+
 #include <cstdlib>
 #include <string>
 #include <sstream>
+#include <cerrno>
+#include <unistd.h>
 
 using isc::data::ConstElementPtr;
 using namespace isc::dns;
@@ -52,17 +59,29 @@ protected:
     DataSrcClientsBuilderTest() :
         clients_map(new std::map<RRClass,
                     boost::shared_ptr<ConfigurableClientList> >),
-        builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex),
+        write_end(-1), read_end(-1),
+        builder(&command_queue, &callback_queue, &cond, &queue_mutex,
+                &clients_map, &map_mutex, generateSockets()),
         cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()),
-        shutdown_cmd(SHUTDOWN, ConstElementPtr()),
-        noop_cmd(NOOP, ConstElementPtr())
+        shutdown_cmd(SHUTDOWN, ConstElementPtr(), FinishedCallback()),
+        noop_cmd(NOOP, ConstElementPtr(), FinishedCallback())
     {}
+    ~ DataSrcClientsBuilderTest() {
+
+    }
+
+    void TearDown() {
+        // Some tests create this file. Delete it if it exists.
+        unlink(TEST_DATA_BUILDDIR "/test1.zone.image");
+    }
 
     void configureZones();      // used for loadzone related tests
 
     ClientListMapPtr clients_map; // configured clients
     std::list<Command> command_queue; // test command queue
     std::list<Command> delayed_command_queue; // commands available after wait
+    std::list<FinishedCallback> callback_queue; // Callbacks from commands
+    int write_end, read_end;
     TestDataSrcClientsBuilder builder;
     TestCondVar cond;
     TestMutex queue_mutex;
@@ -70,6 +89,15 @@ protected:
     const RRClass rrclass;
     const Command shutdown_cmd;
     const Command noop_cmd;
+private:
+    int generateSockets() {
+        int pair[2];
+        int result = socketpair(AF_UNIX, SOCK_STREAM, 0, pair);
+        assert(result == 0);
+        write_end = pair[0];
+        read_end = pair[1];
+        return write_end;
+    }
 };
 
 TEST_F(DataSrcClientsBuilderTest, runSingleCommand) {
@@ -80,6 +108,45 @@ TEST_F(DataSrcClientsBuilderTest, runSingleCommand) {
     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);
+    // No callback scheduled, none called.
+    EXPECT_TRUE(callback_queue.empty());
+    // Not woken up.
+    char c;
+    int result = recv(read_end, &c, 1, MSG_DONTWAIT);
+    EXPECT_EQ(-1, result);
+    EXPECT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK);
+}
+
+// Just to have a valid function callback to pass
+void emptyCallsback() {}
+
+// Check a command finished callback is passed
+TEST_F(DataSrcClientsBuilderTest, commandFinished) {
+    command_queue.push_back(Command(SHUTDOWN, ConstElementPtr(),
+                                    emptyCallsback));
+    builder.run();
+    EXPECT_EQ(0, cond.wait_count); // no wait because command queue is not empty
+    // Once for picking up data, once for putting the callback there
+    EXPECT_EQ(2, queue_mutex.lock_count);
+    EXPECT_EQ(2, queue_mutex.unlock_count);
+    // There's one callback in the queue
+    ASSERT_EQ(1, callback_queue.size());
+    // Not using EXPECT_EQ, as that produces warning in printing out the result
+    EXPECT_TRUE(emptyCallsback == callback_queue.front());
+    // And we are woken up.
+    char c;
+    int result = recv(read_end, &c, 1, MSG_DONTWAIT);
+    EXPECT_EQ(1, result);
+}
+
+// Test that low-level errors with the synchronization socket
+// (an unexpected condition) is detected and program aborted.
+TEST_F(DataSrcClientsBuilderTest, finishedCrash) {
+    command_queue.push_back(Command(SHUTDOWN, ConstElementPtr(),
+                                    emptyCallsback));
+    // Break the socket
+    close(write_end);
+    EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
 }
 
 TEST_F(DataSrcClientsBuilderTest, runMultiCommands) {
@@ -136,7 +203,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     // the error handling
 
     // A command structure we'll modify to send different commands
-    Command reconfig_cmd(RECONFIGURE, ConstElementPtr());
+    Command reconfig_cmd(RECONFIGURE, ConstElementPtr(), FinishedCallback());
 
     // Initially, no clients should be there
     EXPECT_TRUE(clients_map->empty());
@@ -164,7 +231,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
         "}"
     );
 
-    reconfig_cmd.second = good_config;
+    reconfig_cmd.params = good_config;
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_EQ(1, clients_map->size());
     EXPECT_EQ(1, map_mutex.lock_count);
@@ -175,7 +242,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     // 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\" }");
+    reconfig_cmd.params = 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
@@ -183,7 +250,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
 
     // The same for a configuration that has bad data for the type it
     // specifies
-    reconfig_cmd.second = bad_config;
+    reconfig_cmd.params = bad_config;
     builder.handleCommand(reconfig_cmd);
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_EQ(working_config_clients, clients_map);
@@ -192,21 +259,21 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
 
     // The same goes for an empty parameter (it should at least be
     // an empty map)
-    reconfig_cmd.second = ConstElementPtr();
+    reconfig_cmd.params = 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;
+    reconfig_cmd.params = 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();
+    reconfig_cmd.params = Element::createMap();
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_EQ(0, clients_map->size());
     EXPECT_EQ(3, map_mutex.lock_count);
@@ -222,7 +289,8 @@ TEST_F(DataSrcClientsBuilderTest, shutdown) {
 TEST_F(DataSrcClientsBuilderTest, badCommand) {
     // out-of-range command ID
     EXPECT_THROW(builder.handleCommand(Command(NUM_COMMANDS,
-                                               ConstElementPtr())),
+                                               ConstElementPtr(),
+                                               FinishedCallback())),
                  isc::Unexpected);
 }
 
@@ -306,7 +374,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZone) {
 
     const Command loadzone_cmd(LOADZONE, Element::fromJSON(
                                    "{\"class\": \"IN\","
-                                   " \"origin\": \"test1.example\"}"));
+                                   " \"origin\": \"test1.example\"}"),
+                               FinishedCallback());
     EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
 
     // loadZone involves two critical sections: one for getting the zone
@@ -367,7 +436,8 @@ TEST_F(DataSrcClientsBuilderTest,
     // Now send the command to reload it
     const Command loadzone_cmd(LOADZONE, Element::fromJSON(
                                    "{\"class\": \"IN\","
-                                   " \"origin\": \"example.org\"}"));
+                                   " \"origin\": \"example.org\"}"),
+                               FinishedCallback());
     EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
     // And now it should be present too.
     EXPECT_EQ(ZoneFinder::SUCCESS,
@@ -378,7 +448,8 @@ TEST_F(DataSrcClientsBuilderTest,
     // An error case: the zone has no configuration. (note .com here)
     const Command nozone_cmd(LOADZONE, Element::fromJSON(
                                  "{\"class\": \"IN\","
-                                 " \"origin\": \"example.com\"}"));
+                                 " \"origin\": \"example.com\"}"),
+                             FinishedCallback());
     EXPECT_THROW(builder.handleCommand(nozone_cmd),
                  TestDataSrcClientsBuilder::InternalCommandError);
     // The previous zone is not hurt in any way
@@ -401,11 +472,29 @@ TEST_F(DataSrcClientsBuilderTest,
     builder.handleCommand(
                      Command(LOADZONE, Element::fromJSON(
                                  "{\"class\": \"IN\","
-                                 " \"origin\": \"example.org\"}")));
+                                 " \"origin\": \"example.org\"}"),
+                             FinishedCallback()));
     // 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);
 
+    // zone doesn't exist in the data source
+    const ConstElementPtr config_nozone(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": true,"
+        "    \"cache-zones\": [\"nosuchzone.example\"]"
+        "}]}"));
+    clients_map = configureDataSource(config_nozone);
+    EXPECT_THROW(
+        builder.handleCommand(
+            Command(LOADZONE, Element::fromJSON(
+                        "{\"class\": \"IN\","
+                        " \"origin\": \"nosuchzone.example\"}"),
+                    FinishedCallback())),
+        TestDataSrcClientsBuilder::InternalCommandError);
+
     // basically impossible case: in-memory cache is completely disabled.
     // In this implementation of manager-builder, this should never happen,
     // but it catches it like other configuration error and keeps going.
@@ -423,7 +512,8 @@ TEST_F(DataSrcClientsBuilderTest,
     EXPECT_THROW(builder.handleCommand(
                      Command(LOADZONE, Element::fromJSON(
                                  "{\"class\": \"IN\","
-                                 " \"origin\": \"example.org\"}"))),
+                                 " \"origin\": \"example.org\"}"),
+                             FinishedCallback())),
                  TestDataSrcClientsBuilder::InternalCommandError);
 }
 
@@ -436,13 +526,21 @@ TEST_F(DataSrcClientsBuilderTest, loadBrokenZone) {
     // 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\"}"));
+                                   " \"origin\": \"test1.example\"}"),
+                               FinishedCallback());
     EXPECT_THROW(builder.handleCommand(loadzone_cmd),
                  TestDataSrcClientsBuilder::InternalCommandError);
     zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
 }
 
 TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
+    // If the test is run as the root user, it will fail as insufficient
+    // permissions will not stop the root user from using a file.
+    if (getuid() == 0) {
+        std::cerr << "Skipping test as it's run as the root user" << std::endl;
+        return;
+    }
+
     configureZones();
 
     // install the zone file as unreadable
@@ -451,7 +549,8 @@ TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
                              TEST_DATA_BUILDDIR "/test1.zone.copied"));
     const Command loadzone_cmd(LOADZONE, Element::fromJSON(
                                    "{\"class\": \"IN\","
-                                   " \"origin\": \"test1.example\"}"));
+                                   " \"origin\": \"test1.example\"}"),
+                               FinishedCallback());
     EXPECT_THROW(builder.handleCommand(loadzone_cmd),
                  TestDataSrcClientsBuilder::InternalCommandError);
     zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
@@ -464,7 +563,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneWithoutDataSrc) {
                      Command(LOADZONE,
                              Element::fromJSON(
                                  "{\"class\": \"IN\", "
-                                 " \"origin\": \"test1.example\"}"))),
+                                 " \"origin\": \"test1.example\"}"),
+                             FinishedCallback())),
                  TestDataSrcClientsBuilder::InternalCommandError);
 }
 
@@ -474,7 +574,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
     if (!isc::util::unittests::runningOnValgrind()) {
         // null arg: this causes assertion failure
         EXPECT_DEATH_IF_SUPPORTED({
-                builder.handleCommand(Command(LOADZONE, ElementPtr()));
+                builder.handleCommand(Command(LOADZONE, ElementPtr(),
+                                              FinishedCallback()));
             }, "");
     }
 
@@ -483,7 +584,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
                      Command(LOADZONE,
                              Element::fromJSON(
                                  "{\"origin\": \"test1.example\","
-                                 " \"class\": \"no_such_class\"}"))),
+                                 " \"class\": \"no_such_class\"}"),
+                             FinishedCallback())),
                  InvalidRRClass);
 
     // not a string
@@ -491,7 +593,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
                      Command(LOADZONE,
                              Element::fromJSON(
                                  "{\"origin\": \"test1.example\","
-                                 " \"class\": 1}"))),
+                                 " \"class\": 1}"),
+                             FinishedCallback())),
                  isc::data::TypeError);
 
     // class or origin is missing: result in assertion failure
@@ -499,29 +602,158 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
         EXPECT_DEATH_IF_SUPPORTED({
                 builder.handleCommand(Command(LOADZONE,
                                               Element::fromJSON(
-                                                  "{\"class\": \"IN\"}")));
+                                                  "{\"class\": \"IN\"}"),
+                                              FinishedCallback()));
             }, "");
     }
 
-    // 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\": \"...\"}"))),
+                                 "{\"class\": \"IN\", \"origin\": \"...\"}"),
+                             FinishedCallback())),
                  EmptyLabel);
     EXPECT_THROW(builder.handleCommand(
                      Command(LOADZONE,
                              Element::fromJSON(
-                                 "{\"origin\": 10, \"class\": 1}"))),
+                                 "{\"origin\": 10, \"class\": 1}"),
+                             FinishedCallback())),
                  isc::data::TypeError);
 }
 
+// This works only if mapped memory segment is compiled.
+// Note also that this test case may fail as we make b10-auth more aware
+// of shared-memory cache.
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_SHARED_MEMORY
+       loadInNonWritableCache
+#else
+       DISABLED_loadInNonWritableCache
+#endif
+    )
+{
+    const ConstElementPtr config = Element::fromJSON(
+        "{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {"
+        "       \"test1.example\": \"" +
+        std::string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\"},"
+        "   \"cache-enable\": true,"
+        "   \"cache-type\": \"mapped\""
+        "}]}");
+    clients_map = configureDataSource(config);
+
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": \"test1.example\","
+                                 " \"class\": \"IN\"}"),
+                             FinishedCallback())),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+// Test the SEGMENT_INFO_UPDATE command. This test is little bit
+// indirect. It doesn't seem possible to fake the client list inside
+// easily. So we create a real image to load and load it. Then we check
+// the segment is used.
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_SHARED_MEMORY
+       segmentInfoUpdate
+#else
+       DISABLED_segmentInfoUpdate
+#endif
+      )
+{
+    // First, prepare the file image to be mapped
+    const ConstElementPtr config = Element::fromJSON(
+        "{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {"
+        "       \"test1.example\": \""
+        TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
+        "   \"cache-enable\": true,"
+        "   \"cache-type\": \"mapped\""
+        "}]}");
+    const ConstElementPtr segment_config = Element::fromJSON(
+        "{"
+        "  \"mapped-file\": \""
+        TEST_DATA_BUILDDIR "/test1.zone.image" "\"}");
+    clients_map = configureDataSource(config);
+    {
+        const boost::shared_ptr<ConfigurableClientList> list =
+            (*clients_map)[RRClass::IN()];
+        list->resetMemorySegment("MasterFiles",
+                                 memory::ZoneTableSegment::CREATE,
+                                 segment_config);
+        const ConfigurableClientList::ZoneWriterPair result =
+            list->getCachedZoneWriter(isc::dns::Name("test1.example"), false,
+                                      "MasterFiles");
+        ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+        result.second->load();
+        result.second->install();
+        // not absolutely necessary, but just in case
+        result.second->cleanup();
+    } // Release this list. That will release the file with the image too,
+      // so we can map it read only from somewhere else.
+
+    // Create a new map, with the same configuration, but without the segments
+    // set
+    clients_map = configureDataSource(config);
+    const boost::shared_ptr<ConfigurableClientList> list =
+        (*clients_map)[RRClass::IN()];
+    EXPECT_EQ(SEGMENT_WAITING, list->getStatus()[0].getSegmentState());
+    // Send the command
+    const ElementPtr command_args = Element::fromJSON(
+        "{"
+        "  \"data-source-name\": \"MasterFiles\","
+        "  \"data-source-class\": \"IN\""
+        "}");
+    command_args->set("segment-params", segment_config);
+    builder.handleCommand(Command(SEGMENT_INFO_UPDATE, command_args,
+                                  FinishedCallback()));
+    // The segment is now used.
+    EXPECT_EQ(SEGMENT_INUSE, list->getStatus()[0].getSegmentState());
+
+    // Some invalid inputs (wrong class, different name of data source, etc).
+
+    // Copy the confing and modify
+    const ElementPtr bad_name = Element::fromJSON(command_args->toWire());
+    // Set bad name
+    bad_name->set("data-source-name", Element::create("bad"));
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_name,
+                                      FinishedCallback()));
+    }, "");
+
+    // Another copy with wrong class
+    const ElementPtr bad_class = Element::fromJSON(command_args->toWire());
+    // Set bad class
+    bad_class->set("data-source-class", Element::create("bad"));
+    // Aborts (we are out of sync somehow).
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+
+    // Class CH is valid, but not present.
+    bad_class->set("data-source-class", Element::create("CH"));
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+
+    // And break the segment params
+    const ElementPtr bad_params = Element::fromJSON(command_args->toWire());
+    bad_params->set("segment-params",
+                    Element::fromJSON("{\"mapped-file\": \"/bad/file\"}"));
+
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+}
+
 } // unnamed namespace

+ 78 - 9
src/bin/auth/tests/datasrc_clients_mgr_unittest.cc

@@ -38,13 +38,13 @@ 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);
+    // the builder. It should check again for the callback queue, with the lock
+    EXPECT_EQ(2, 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
+    EXPECT_EQ(SHUTDOWN, cmd.id);
+    EXPECT_FALSE(cmd.params);   // no argument
 
     // Finally, the manager should wait for the thread to terminate.
     EXPECT_TRUE(FakeDataSrcClientsBuilder::thread_waited);
@@ -130,8 +130,8 @@ TEST(DataSrcClientsMgrTest, reconfigure) {
     // 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);
+    EXPECT_EQ(RECONFIGURE, cmd1.id);
+    EXPECT_EQ(reconfigure_arg, cmd1.params);
 
     // Non-null, but semantically invalid argument.  The manager doesn't do
     // this check, so it should result in the same effect.
@@ -140,8 +140,8 @@ TEST(DataSrcClientsMgrTest, reconfigure) {
     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);
+    EXPECT_EQ(RECONFIGURE, cmd2.id);
+    EXPECT_EQ(reconfigure_arg, cmd2.params);
 
     // Passing NULL argument is immediately rejected
     EXPECT_THROW(mgr.reconfigure(ConstElementPtr()), isc::InvalidParameter);
@@ -156,6 +156,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_FALSE(holder.findClientList(RRClass::IN()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+        EXPECT_TRUE(holder.getClasses().empty());
         // map should be protected here
         EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->lock_count);
         EXPECT_EQ(0, FakeDataSrcClientsBuilder::map_mutex->unlock_count);
@@ -174,6 +175,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_TRUE(holder.findClientList(RRClass::CH()));
+        EXPECT_EQ(2, holder.getClasses().size());
     }
     // We need to clear command queue by hand
     FakeDataSrcClientsBuilder::command_queue->clear();
@@ -188,6 +190,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+        EXPECT_EQ(RRClass::IN(), holder.getClasses()[0]);
     }
 
     // Duplicate lock acquisition is prohibited (only test mgr can detect
@@ -245,10 +248,76 @@ TEST(DataSrcClientsMgrTest, reload) {
     EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
 }
 
+TEST(DataSrcClientsMgrTest, segmentUpdate) {
+    TestDataSrcClientsMgr mgr;
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::started);
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty());
+
+    isc::data::ElementPtr args =
+        isc::data::Element::fromJSON("{\"data-source-class\": \"IN\","
+                                     " \"data-source-name\": \"sqlite3\","
+                                     " \"segment-params\": {}}");
+    mgr.segmentInfoUpdate(args);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+    // Some invalid inputs
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-class\": \"IN\","
+        " \"data-source-name\": \"sqlite3\"}")), CommandError);
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-name\": \"sqlite3\","
+        " \"segment-params\": {}}")), CommandError);
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-class\": \"IN\","
+        " \"segment-params\": {}}")), CommandError);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+}
+
+void
+callback(bool* called, int *tag_target, int tag_value) {
+    *called = true;
+    *tag_target = tag_value;
+}
+
+// Test we can wake up the main thread by writing to the file descriptor and
+// that the callbacks are executed and removed when woken up.
+TEST(DataSrcClientsMgrTest, wakeup) {
+    bool called = false;
+    int tag;
+    {
+        TestDataSrcClientsMgr mgr;
+        // There's some real file descriptor (or something that looks so)
+        ASSERT_GT(FakeDataSrcClientsBuilder::wakeup_fd, 0);
+        // Push a callback in and wake the manager
+        FakeDataSrcClientsBuilder::callback_queue->
+            push_back(boost::bind(callback, &called, &tag, 1));
+        EXPECT_EQ(1, write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1));
+        mgr.run_one();
+        EXPECT_TRUE(called);
+        EXPECT_EQ(1, tag);
+        EXPECT_TRUE(FakeDataSrcClientsBuilder::callback_queue->empty());
+
+        called = false;
+        // If we wake up and don't push anything, it doesn't break.
+        EXPECT_EQ(1, write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1));
+        mgr.run_one();
+        EXPECT_FALSE(called);
+
+        // When we terminate, it should process whatever is left
+        // of the callbacks. So push and terminate (and don't directly
+        // wake).
+        FakeDataSrcClientsBuilder::callback_queue->
+            push_back(boost::bind(callback, &called, &tag, 2));
+    }
+    EXPECT_TRUE(called);
+    EXPECT_EQ(2, tag);
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::callback_queue->empty());
+}
+
 TEST(DataSrcClientsMgrTest, realThread) {
     // Using the non-test definition with a real thread.  Just checking
     // no disruption happens.
-    DataSrcClientsMgr mgr;
+    isc::asiolink::IOService service;
+    DataSrcClientsMgr mgr(service);
 }
 
 } // unnamed namespace

+ 4 - 5
src/bin/auth/tests/datasrc_config_unittest.cc

@@ -30,7 +30,6 @@ using namespace isc::config;
 using namespace isc::data;
 using namespace isc::dns;
 using namespace std;
-using namespace boost;
 
 namespace {
 
@@ -57,7 +56,7 @@ private:
     ConstElementPtr configuration_;
 };
 
-typedef shared_ptr<FakeList> ListPtr;
+typedef boost::shared_ptr<FakeList> ListPtr;
 
 // Forward declaration.  We need precise definition of DatasrcConfigTest
 // to complete this function.
@@ -77,8 +76,8 @@ datasrcConfigHandler(DatasrcConfigTest* fake_server, const std::string&,
 
 class DatasrcConfigTest : public ::testing::Test {
 public:
-    void setDataSrcClientLists(shared_ptr<std::map<dns::RRClass, ListPtr> >
-                               new_lists)
+    void setDataSrcClientLists(boost::shared_ptr<std::map<dns::RRClass,
+                               ListPtr> > new_lists)
     {
         lists_.clear();         // first empty it
 
@@ -159,7 +158,7 @@ testConfigureDataSource(DatasrcConfigTest& test,
 {
     // We use customized (faked lists) for the List type.  This makes it
     // possible to easily look that they were called.
-    shared_ptr<std::map<dns::RRClass, ListPtr> > lists =
+    boost::shared_ptr<std::map<dns::RRClass, ListPtr> > lists =
         configureDataSourceGeneric<FakeList>(config);
     test.setDataSrcClientLists(lists);
 }

+ 77 - 36
src/bin/auth/tests/query_unittest.cc

@@ -80,6 +80,12 @@ public:
                 return (FindResult());
         }
     }
+    virtual ConstZoneTableAccessorPtr
+    getZoneTableAccessor(const std::string&, bool) const {
+        isc_throw(isc::NotImplemented,
+                  "getZoneTableAccessor not implemented for SingletonList");
+    }
+
 private:
     DataSourceClient& client_;
 };
@@ -133,6 +139,12 @@ const char* const unsigned_delegation_nsec3_txt =
     "q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
     "aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
 
+// Name of an "empty" zone: used to simulate the case of
+// configured-but-available zone (due to load errors, etc).
+// Each tested data source client is expected to have this zone (SQLite3
+// currently doesn't have this concept so it's skipped)
+const char* const EMPTY_ZONE_NAME = "empty.example.org";
+
 // A helper function that generates a textual representation of RRSIG RDATA
 // for the given covered type.  The resulting RRSIG may not necessarily make
 // sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -799,11 +811,14 @@ createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
         return (boost::shared_ptr<ClientList>(new SingletonList(client)));
     case INMEMORY:
         list.reset(new ConfigurableClientList(RRClass::IN()));
+        // Configure one normal zone and one "empty" zone.
         list->configure(isc::data::Element::fromJSON(
                             "[{\"type\": \"MasterFiles\","
                             "  \"cache-enable\": true, "
                             "  \"params\": {\"example.com\": \"" +
-                            string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+                            string(TEST_OWN_DATA_BUILDDIR "/example.zone\",") +
+                            + "\"" + EMPTY_ZONE_NAME + "\": \"" +
+                            string(TEST_OWN_DATA_BUILDDIR "/nosuchfile.zone") +
                             "\"}}]"), true);
         return (list);
     case SQLITE3:
@@ -834,39 +849,38 @@ createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
 class MockClient : public DataSourceClient {
 public:
     virtual FindResult findZone(const isc::dns::Name& origin) const {
-        const Name r_origin(origin.reverse());
-        std::map<Name, ZoneFinderPtr>::const_iterator it =
-            zone_finders_.lower_bound(r_origin);
-
-        if (it != zone_finders_.end()) {
-            const NameComparisonResult result =
-                origin.compare((it->first).reverse());
-            if (result.getRelation() == NameComparisonResult::EQUAL) {
-                return (FindResult(result::SUCCESS, it->second));
-            } else if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
-                return (FindResult(result::PARTIALMATCH, it->second));
-            }
-        }
+        // Identify the next (strictly) larger name than the given 'origin' in
+        // the map.  Its predecessor (if any) is the longest matching name
+        // if it's either an exact match or a super domain; otherwise there's
+        // no match in the map.  See also datasrc/tests/mock_client.cc.
 
-        // If it is at the beginning of the map, then the name was not
-        // found (we have already handled the element the iterator
-        // points to).
-        if (it == zone_finders_.begin()) {
+        // Eliminate the case of empty map to simply the rest of the code
+        if (zone_finders_.empty()) {
             return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
         }
 
-        // Check if the previous element is a partial match.
-        --it;
-        const NameComparisonResult result =
-            origin.compare((it->first).reverse());
-        if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
-            return (FindResult(result::PARTIALMATCH, it->second));
+        std::map<Name, ZoneFinderPtr>::const_iterator it =
+            zone_finders_.upper_bound(origin);
+        if (it == zone_finders_.begin()) { // no predecessor
+            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
         }
 
-        return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        --it;                   // get the predecessor
+        const result::ResultFlags flags =
+            it->second ? result::FLAGS_DEFAULT : result::ZONE_EMPTY;
+        const NameComparisonResult compar(it->first.compare(origin));
+        switch (compar.getRelation()) {
+        case NameComparisonResult::EQUAL:
+            return (FindResult(result::SUCCESS, it->second, flags));
+        case NameComparisonResult::SUPERDOMAIN:
+            return (FindResult(result::PARTIALMATCH, it->second, flags));
+        default:
+            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
     }
 
-    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool) const {
+    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool) const
+    {
         isc_throw(isc::NotImplemented,
                   "Updater isn't supported in the MockClient");
     }
@@ -878,18 +892,21 @@ public:
     }
 
     result::Result addZone(ZoneFinderPtr finder) {
-        // Use the reverse of the name as the key, so we can quickly
-        // find partial matches in the map.
-        zone_finders_[finder->getOrigin().reverse()] = finder;
+        zone_finders_[finder->getOrigin()] = finder;
+        return (result::SUCCESS);
+    }
+
+    // "configure" a zone with no data.  This will cause the ZONE_EMPTY flag
+    // on in finZone().
+    result::Result addEmptyZone(const Name& zone_name) {
+        zone_finders_[zone_name] = ZoneFinderPtr();
         return (result::SUCCESS);
     }
 
 private:
     // Note that because we no longer have the old RBTree, and the new
     // in-memory DomainTree is not useful as it returns const nodes, we
-    // use a std::map instead. In this map, the key is a name stored in
-    // reverse order of labels to aid in finding partial matches
-    // quickly.
+    // use a std::map instead.
     std::map<Name, ZoneFinderPtr> zone_finders_;
 };
 
@@ -916,9 +933,10 @@ protected:
 
         response.setRcode(Rcode::NOERROR());
         response.setOpcode(Opcode::QUERY());
-        // create and add a matching zone.
+        // create and add a matching zone.  One is a "broken, empty" zone.
         mock_finder = new MockZoneFinder();
         mock_client.addZone(ZoneFinderPtr(mock_finder));
+        mock_client.addEmptyZone(Name(EMPTY_ZONE_NAME));
     }
 
     virtual void SetUp() {
@@ -949,6 +967,12 @@ protected:
         setNSEC3HashCreator(NULL);
     }
 
+    bool isEmptyZoneSupported() const {
+        // Not all data sources support the concept of empty zones.
+        // Specifically for this test, SQLite3-based data source doesn't.
+        return (GetParam() != SQLITE3);
+    }
+
     void enableNSEC3(const vector<string>& rrsets_to_add) {
         boost::shared_ptr<ConfigurableClientList> new_list;
         switch (GetParam()) {
@@ -1144,11 +1168,29 @@ TEST_P(QueryTest, noZone) {
     // REFUSED.
     MockClient empty_mock_client;
     SingletonList empty_list(empty_mock_client);
-    EXPECT_NO_THROW(query.process(empty_list, qname, qtype,
-                                  response));
+    EXPECT_NO_THROW(query.process(empty_list, qname, qtype, response));
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
 }
 
+TEST_P(QueryTest, emptyZone) {
+    // Query for an "empty (broken)" zone.  If the concept is supported by
+    // the underlying data source, the result should be SERVFAIL; otherwise
+    // it would be handled as a nonexistent zone, resulting in REFUSED.
+    const Rcode expected_rcode =
+        isEmptyZoneSupported() ? Rcode::SERVFAIL() : Rcode::REFUSED();
+
+    query.process(*list_, Name(EMPTY_ZONE_NAME), qtype, response);
+    responseCheck(response, expected_rcode, 0, 0, 0, 0, NULL, NULL, NULL);
+
+    // Same for the partial match case
+    response.clear(isc::dns::Message::RENDER);
+    response.setRcode(Rcode::NOERROR());
+    response.setOpcode(Opcode::QUERY());
+    query.process(*list_, Name(string("www.") + EMPTY_ZONE_NAME), qtype,
+                  response);
+    responseCheck(response, expected_rcode, 0, 0, 0, 0, NULL, NULL, NULL);
+}
+
 TEST_P(QueryTest, exactMatch) {
     EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
     // find match rrset
@@ -1400,7 +1442,6 @@ TEST_F(QueryTestForMockOnly, badSecureDelegation) {
                                   qtype, response));
 }
 
-
 TEST_P(QueryTest, nxdomain) {
     EXPECT_NO_THROW(query.process(*list_,
                                   Name("nxdomain.example.com"), qtype,

+ 58 - 0
src/bin/auth/tests/statistics_unittest.cc.pre

@@ -361,6 +361,64 @@ TEST_F(CountersTest, incrementTSIG) {
     }
 }
 
+TEST_F(CountersTest, incrementRD) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test these patterns:
+    //     OpCode         Recursion Desired
+    //    ---------------------------
+    //     0 (Query)      false
+    //     0 (Query)      true
+    //     2 (Status)     false
+    //     2 (Status)     true
+    //  Make sure the counter will be incremented only for the requests with
+    //  OpCode=Query and Recursion Desired (RD) bit=1.
+    int count_opcode_query = 0;
+    int count_opcode_status = 0;
+    for (int i = 0; i < 4; ++i) {
+        const bool is_recursion_desired = i & 1;
+        const uint8_t opcode_code = i & 0x2;
+        const Opcode opcode(opcode_code);
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestRD(is_recursion_desired);
+        msgattrs.setRequestOpCode(opcode);
+
+        response.setRcode(Rcode::REFUSED());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        counters.inc(msgattrs, response, true);
+
+        if (opcode == Opcode::QUERY()) {
+            ++count_opcode_query;
+        } else {
+            ++count_opcode_status;
+        }
+
+        expect.clear();
+        expect["opcode.query"] = count_opcode_query;
+        expect["opcode.status"] = count_opcode_status;
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["request.edns0"] = i+1;
+        expect["request.dnssec_ok"] = i+1;
+        expect["responses"] = i+1;
+        // qryrecursion will (only) be incremented if i == 1: OpCode=Query and
+        // RD bit=1
+        expect["qryrecursion"] = (i == 0) ? 0 : 1;
+        expect["rcode.refused"] = i+1;
+        // these counters are for queries; the value will be equal to the
+        // number of requests with OpCode=Query
+        expect["qrynoauthans"] = count_opcode_query;
+        expect["authqryrej"] = count_opcode_query;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
 TEST_F(CountersTest, incrementOpcode) {
     Message response(Message::RENDER);
     MessageAttributes msgattrs;

+ 11 - 4
src/bin/auth/tests/test_datasrc_clients_mgr.cc

@@ -26,7 +26,9 @@ namespace datasrc_clientmgr_internal {
 // Define static DataSrcClientsBuilder member variables.
 bool FakeDataSrcClientsBuilder::started = false;
 std::list<Command>* FakeDataSrcClientsBuilder::command_queue = NULL;
+std::list<FinishedCallback>* FakeDataSrcClientsBuilder::callback_queue = NULL;
 std::list<Command> FakeDataSrcClientsBuilder::command_queue_copy;
+std::list<FinishedCallback> FakeDataSrcClientsBuilder::callback_queue_copy;
 TestCondVar* FakeDataSrcClientsBuilder::cond = NULL;
 TestCondVar FakeDataSrcClientsBuilder::cond_copy;
 TestMutex* FakeDataSrcClientsBuilder::queue_mutex = NULL;
@@ -38,6 +40,7 @@ bool FakeDataSrcClientsBuilder::thread_waited = false;
 FakeDataSrcClientsBuilder::ExceptionFromWait
 FakeDataSrcClientsBuilder::thread_throw_on_wait =
     FakeDataSrcClientsBuilder::NOTHROW;
+int FakeDataSrcClientsBuilder::wakeup_fd = -1;
 
 template<>
 void
@@ -58,7 +61,7 @@ TestDataSrcClientsBuilder::doNoop() {
 
 template<>
 void
-TestDataSrcClientsMgr::cleanup() {
+TestDataSrcClientsMgrBase::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
@@ -73,17 +76,21 @@ TestDataSrcClientsMgr::cleanup() {
     FakeDataSrcClientsBuilder::cond_copy = cond_;
     FakeDataSrcClientsBuilder::cond =
         &FakeDataSrcClientsBuilder::cond_copy;
+    FakeDataSrcClientsBuilder::callback_queue_copy =
+        *FakeDataSrcClientsBuilder::callback_queue;
+    FakeDataSrcClientsBuilder::callback_queue =
+        &FakeDataSrcClientsBuilder::callback_queue_copy;
 }
 
 template<>
 void
-TestDataSrcClientsMgr::reconfigureHook() {
+TestDataSrcClientsMgrBase::reconfigureHook() {
     using namespace datasrc_clientmgr_internal;
 
     // Simply replace the local map, ignoring bogus config value.
-    assert(command_queue_.front().first == RECONFIGURE);
+    assert(command_queue_.front().id == RECONFIGURE);
     try {
-        clients_map_ = configureDataSource(command_queue_.front().second);
+        clients_map_ = configureDataSource(command_queue_.front().params);
     } catch (...) {}
 }
 

+ 25 - 5
src/bin/auth/tests/test_datasrc_clients_mgr.h

@@ -20,6 +20,8 @@
 #include <auth/datasrc_clients_mgr.h>
 #include <datasrc/datasrc_config.h>
 
+#include <asiolink/io_service.h>
+
 #include <boost/function.hpp>
 
 #include <list>
@@ -131,15 +133,18 @@ public:
     // true iff a builder has started.
     static bool started;
 
-    // These three correspond to the resource shared with the manager.
+    // These five 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>* command_queue;
+    static std::list<FinishedCallback>* callback_queue;
     static TestCondVar* cond;
     static TestMutex* queue_mutex;
+    static int wakeup_fd;
     static isc::datasrc::ClientListMapPtr* clients_map;
     static TestMutex* map_mutex;
     static std::list<Command> command_queue_copy;
+    static std::list<FinishedCallback> callback_queue_copy;
     static TestCondVar cond_copy;
     static TestMutex queue_mutex_copy;
 
@@ -153,15 +158,18 @@ public:
 
     FakeDataSrcClientsBuilder(
         std::list<Command>* command_queue,
+        std::list<FinishedCallback>* callback_queue,
         TestCondVar* cond,
         TestMutex* queue_mutex,
         isc::datasrc::ClientListMapPtr* clients_map,
-        TestMutex* map_mutex)
+        TestMutex* map_mutex, int wakeup_fd)
     {
         FakeDataSrcClientsBuilder::started = false;
         FakeDataSrcClientsBuilder::command_queue = command_queue;
+        FakeDataSrcClientsBuilder::callback_queue = callback_queue;
         FakeDataSrcClientsBuilder::cond = cond;
         FakeDataSrcClientsBuilder::queue_mutex = queue_mutex;
+        FakeDataSrcClientsBuilder::wakeup_fd = wakeup_fd;
         FakeDataSrcClientsBuilder::clients_map = clients_map;
         FakeDataSrcClientsBuilder::map_mutex = map_mutex;
         FakeDataSrcClientsBuilder::thread_waited = false;
@@ -201,18 +209,30 @@ typedef DataSrcClientsMgrBase<
     datasrc_clientmgr_internal::TestThread,
     datasrc_clientmgr_internal::FakeDataSrcClientsBuilder,
     datasrc_clientmgr_internal::TestMutex,
-    datasrc_clientmgr_internal::TestCondVar> TestDataSrcClientsMgr;
+    datasrc_clientmgr_internal::TestCondVar> TestDataSrcClientsMgrBase;
 
 // 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();
+TestDataSrcClientsMgrBase::cleanup();
 
 template<>
 void
-TestDataSrcClientsMgr::reconfigureHook();
+TestDataSrcClientsMgrBase::reconfigureHook();
+
+// A (hackish) trick how to not require the IOService to be passed from the
+// tests. We can't create the io service as a member, because it would
+// get initialized too late.
+class TestDataSrcClientsMgr :
+    public asiolink::IOService,
+    public TestDataSrcClientsMgrBase {
+public:
+    TestDataSrcClientsMgr() :
+        TestDataSrcClientsMgrBase(*static_cast<asiolink::IOService*>(this))
+    {}
+};
 } // namespace auth
 } // namespace isc
 

+ 97 - 30
src/bin/bind10/init.py.in

@@ -78,6 +78,7 @@ from isc.log_messages.init_messages import *
 import isc.bind10.component
 import isc.bind10.special_component
 import isc.bind10.socket_cache
+import isc.util.traceback_handler
 import libutil_io_python
 import tempfile
 
@@ -89,7 +90,8 @@ logger = isc.log.Logger("init")
 DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
 DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
 
-# Messages sent over the unix domain socket to indicate if it is followed by a real socket
+# Messages sent over the unix domain socket to indicate if it is followed by a
+# real socket
 CREATOR_SOCKET_OK = b"1\n"
 CREATOR_SOCKET_UNAVAILABLE = b"0\n"
 
@@ -200,7 +202,8 @@ class Init:
                  verbose=False, nokill=False, setuid=None, setgid=None,
                  username=None, cmdctl_port=None, wait_time=10):
         """
-            Initialize the Init of BIND. This is a singleton (only one can run).
+            Initialize the Init 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 b10-init reports
@@ -223,12 +226,13 @@ class Init:
         self.component_config = {}
         # Some time in future, it may happen that a single component has
         # multple processes (like a pipeline-like component). If so happens,
-        # name "components" may be inappropriate. But as the code isn't probably
-        # completely ready for it, we leave it at components for now. We also
-        # want to support multiple instances of a single component. If it turns
-        # out that we'll have a single component with multiple same processes
-        # or if we start multiple components with the same configuration (we do
-        # this now, but it might change) is an open question.
+        # name "components" may be inappropriate. But as the code isn't
+        # probably completely ready for it, we leave it at components for
+        # now. We also want to support multiple instances of a single
+        # component. If it turns out that we'll have a single component with
+        # multiple same processes or if we start multiple components with the
+        # same configuration (we do this now, but it might change) is an open
+        # question.
         self.components = {}
         # Simply list of components that died and need to wait for a
         # restart. Components manage their own restart schedule now
@@ -333,6 +337,7 @@ class Init:
                 self.__propagate_component_config(new_config['components'])
             return isc.config.ccsession.create_answer(0)
         except Exception as e:
+            logger.error(BIND10_RECONFIGURE_ERROR, e)
             return isc.config.ccsession.create_answer(1, str(e))
 
     def get_processes(self):
@@ -351,7 +356,8 @@ class Init:
 
     def command_handler(self, command, args):
         logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
-        answer = isc.config.ccsession.create_answer(1, "command not implemented")
+        answer = isc.config.ccsession.create_answer(1,
+                                                    "command not implemented")
         if type(command) != str:
             answer = isc.config.ccsession.create_answer(1, "bad command")
         else:
@@ -440,7 +446,8 @@ class Init:
         if pid is None:
             logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
         else:
-            logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
+            logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc,
+                         pid)
 
     def process_running(self, msg, who):
         """
@@ -483,7 +490,7 @@ class Init:
         self.log_starting("b10-msgq")
         msgq_proc = self._make_process_info("b10-msgq", ["b10-msgq"],
                                             self.c_channel_env,
-                                            True, not self.verbose)
+                                            not self.verbose, not self.verbose)
         msgq_proc.spawn()
         self.log_started(msgq_proc.pid)
 
@@ -499,7 +506,8 @@ class Init:
                 if msgq_proc.process:
                     msgq_proc.process.kill()
                 logger.error(BIND10_CONNECTING_TO_CC_FAIL)
-                raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+                raise CChannelConnectError("Unable to connect to c-channel " +
+                                           "after 5 seconds")
 
             # try to connect, and if we can't wait a short while
             try:
@@ -507,13 +515,43 @@ class Init:
             except isc.cc.session.SessionError:
                 time.sleep(0.1)
 
-        # Subscribe to the message queue.  The only messages we expect to receive
-        # on this channel are once relating to process startup.
+        # Subscribe to the message queue.  The only messages we expect to
+        # receive on this channel are once relating to process startup.
         if self.cc_session is not None:
             self.cc_session.group_subscribe("Init")
 
         return msgq_proc
 
+    def wait_msgq(self):
+        """
+            Wait for the message queue to fully start. It does so only after
+            the config manager connects to it. We know it is ready when it
+            starts answering commands.
+
+            We don't add a specific command for it here, an error response is
+            as good as positive one to know it is alive.
+        """
+        # We do 10 times shorter sleep here (since the start should be fast
+        # now), so we have 10 times more attempts.
+        time_remaining = self.wait_time * 10
+        retry = True
+        while time_remaining > 0 and retry:
+            try:
+                self.ccs.rpc_call('AreYouThere?', 'Msgq')
+                # We don't expect this to succeed. If it does, it's programmer
+                # error
+                raise Exception("Non-existing RPC call succeeded")
+            except isc.config.RPCRecipientMissing:
+                retry = True # Not there yet
+                time.sleep(0.1)
+                time_remaining -= 1
+            except isc.config.RPCError:
+                retry = False # It doesn't like the RPC, so it's alive now
+
+        if retry: # Still not started
+            raise ProcessStartError("Msgq didn't complete the second stage " +
+                                    "of startup")
+
     def start_cfgmgr(self):
         """
             Starts the configuration manager process
@@ -536,14 +574,16 @@ class Init:
         # 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"):
+        while time_remaining > 0 and not self.process_running(msg,
+                                                              "ConfigManager"):
             logger.debug(DBG_PROCESS, BIND10_WAIT_CFGMGR)
             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")
+            raise ProcessStartError("Configuration manager process has not " +
+                                    "started")
 
         return bind_cfgd
 
@@ -558,6 +598,13 @@ class Init:
             process, the log_starting/log_started methods are not used.
         """
         logger.info(BIND10_STARTING_CC)
+
+        # Unsubscribe from the other CC session first, because we only
+        # monitor one and msgq expects all data sent to us to be read,
+        # or it will close its side of the socket.
+        if self.cc_session is not None:
+            self.cc_session.group_unsubscribe("Init")
+
         self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
                                       self.config_handler,
                                       self.command_handler,
@@ -567,7 +614,8 @@ class Init:
 
     # A couple of utility methods for starting processes...
 
-    def start_process(self, name, args, c_channel_env, port=None, address=None):
+    def start_process(self, name, args, c_channel_env, port=None,
+                      address=None):
         """
             Given a set of command arguments, start the process and output
             appropriate log messages.  If the start is successful, the process
@@ -612,9 +660,9 @@ class Init:
 
     # The next few methods start up the rest of the BIND-10 processes.
     # Although many of these methods are little more than a call to
-    # start_simple, they are retained (a) for testing reasons and (b) as a place
-    # where modifications can be made if the process start-up sequence changes
-    # for a given process.
+    # start_simple, they are retained (a) for testing reasons and (b) as a
+    # place where modifications can be made if the process start-up sequence
+    # changes for a given process.
 
     def start_auth(self):
         """
@@ -666,6 +714,10 @@ class Init:
         # inside the configurator.
         self.start_ccsession(self.c_channel_env)
 
+        # Make sure msgq is fully started before proceeding to the rest
+        # of the components.
+        self.wait_msgq()
+
         # Extract the parameters associated with Init.  This can only be
         # done after the CC Session is started.  Note that the logging
         # configuration may override the "-v" switch set on the command line.
@@ -689,7 +741,12 @@ class Init:
         try:
             self.cc_session = isc.cc.Session(self.msgq_socket_file)
             logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
-            return "b10-msgq already running, or socket file not cleaned , cannot start"
+            if self.msgq_socket_file is not None:
+              socket_name = "socket file '" + self.msg_socket_file + "'"
+            else:
+              socket_name = "default socket file"
+            return "b10-msgq already running, or " + socket_name +\
+                " not cleaned - cannot start"
         except isc.cc.session.SessionError:
             # this is the case we want, where the msgq is not running
             pass
@@ -719,9 +776,14 @@ class Init:
         it might want to choose if it is for this one).
         """
         logger.info(BIND10_STOP_PROCESS, process)
-        self.cc_session.group_sendmsg(isc.config.ccsession.
-                                      create_command('shutdown', {'pid': pid}),
-                                      recipient, recipient)
+        try:
+            self.cc_session.group_sendmsg(isc.config.ccsession.
+                                          create_command('shutdown',
+                                                         {'pid': pid}),
+                                          recipient, recipient)
+        except:
+            logger.error(BIND10_COMPONENT_SHUTDOWN_ERROR, process)
+            raise
 
     def component_shutdown(self, exitcode=0):
         """
@@ -948,8 +1010,8 @@ class Init:
 
     def set_creator(self, creator):
         """
-        Registeres a socket creator into the b10-init. The socket creator is not
-        used directly, but through a cache. The cache is created in this
+        Registeres a socket creator into the b10-init. The socket creator is
+        not used directly, but through a cache. The cache is created in this
         method.
 
         If called more than once, it raises a ValueError.
@@ -1121,9 +1183,12 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
     parser = Parser(version=VERSION)
     parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
                       type="string", default=None,
-                      help="UNIX domain socket file the b10-msgq daemon will use")
+                      help="UNIX domain socket file the b10-msgq daemon " +
+                      "will use")
     parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
-                      default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
+                      default=False,
+                      help="do not send SIGTERM and SIGKILL signals to " +
+                      "modules during shutdown")
     parser.add_option("-u", "--user", dest="user", type="string", default=None,
                       help="Change user after startup (must run as root)")
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
@@ -1147,7 +1212,9 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
                       default=None,
                       help="file to dump the PID of the BIND 10 process")
     parser.add_option("-w", "--wait", dest="wait_time", type="int",
-                      default=10, help="Time (in seconds) to wait for config manager to start up")
+                      default=10,
+                      help="Time (in seconds) to wait for config manager to "
+                      "start up")
 
     (options, args) = parser.parse_args(args)
 
@@ -1319,4 +1386,4 @@ def main():
     sys.exit(b10_init.exitcode)
 
 if __name__ == "__main__":
-    main()
+    isc.util.traceback_handler.traceback_handler(main)

+ 7 - 0
src/bin/bind10/init_messages.mes

@@ -325,3 +325,10 @@ the configuration manager to start up.  The total length of time Init
 will wait for the configuration manager before reporting an error is
 set with the command line --wait switch, which has a default value of
 ten seconds.
+
+% BIND10_RECONFIGURE_ERROR Error applying new config: %1
+A new configuration was received, but there was an error doing the
+re-configuration.
+
+% BIND10_COMPONENT_SHUTDOWN_ERROR An error occured stopping component %1
+An attempt to gracefully shutdown a component failed.

+ 1 - 1
src/bin/bind10/run_bind10.sh.in

@@ -20,7 +20,7 @@ export PYTHON_EXEC
 
 BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:@abs_top_builddir@/src/bin/memmgr:$PATH
 export PATH
 
 PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs

+ 55 - 2
src/bin/bind10/tests/init_test.py.in

@@ -16,7 +16,8 @@
 # Most of the time, we omit the "init" for brevity. Sometimes,
 # we want to be explicit about what we do, like when hijacking a library
 # call used by the b10-init.
-from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, \
+    _BASETIME
 import init
 
 # XXX: environment tests are currently disabled, due to the preprocessor
@@ -941,6 +942,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
         init.start_ccsession = lambda _: start_ccsession()
         # We need to return the original _read_bind10_config
         init._read_bind10_config = lambda: Init._read_bind10_config(init)
+        init.wait_msgq = lambda: None
         init.start_all_components()
         self.check_started(init, True, start_auth, start_resolver)
         self.check_environment_unchanged()
@@ -967,6 +969,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
         init = MockInit()
         self.check_preconditions(init)
 
+        init.wait_msgq = lambda: None
         init.start_all_components()
         init.runnable = True
         init.config_handler(self.construct_config(False, False))
@@ -1028,6 +1031,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
         init = MockInit()
         self.check_preconditions(init)
 
+        init.wait_msgq = lambda: None
         init.start_all_components()
 
         init.runnable = True
@@ -1066,6 +1070,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
         init = MockInit()
         self.check_preconditions(init)
 
+        init.wait_msgq = lambda: None
         init.start_all_components()
         init.config_handler(self.construct_config(False, False))
         self.check_started_dhcp(init, False, False)
@@ -1075,6 +1080,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
         init = MockInit()
         self.check_preconditions(init)
         # v6 only enabled
+        init.wait_msgq = lambda: None
         init.start_all_components()
         init.runnable = True
         init._Init_started = True
@@ -1347,6 +1353,7 @@ class TestInitComponents(unittest.TestCase):
         # Start it
         orig = init._component_configurator.startup
         init._component_configurator.startup = self.__unary_hook
+        init.wait_msgq = lambda: None
         init.start_all_components()
         init._component_configurator.startup = orig
         self.__check_core(self.__param)
@@ -1499,6 +1506,7 @@ class TestInitComponents(unittest.TestCase):
             pass
         init.ccs = CC()
         init.ccs.get_full_config = lambda: {'components': self.__compconfig}
+        init.wait_msgq = lambda: None
         init.start_all_components()
         self.__check_extended(self.__param)
 
@@ -1643,7 +1651,7 @@ class TestInitComponents(unittest.TestCase):
         pi = init.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_stdout, not verbose)
         self.assertEqual(pi.dev_null_stderr, not verbose)
         self.assertEqual({'FOO': 'an env string'}, pi.env)
 
@@ -1768,6 +1776,51 @@ class TestInitComponents(unittest.TestCase):
         # this is set by ProcessInfo.spawn()
         self.assertEqual(42147, pi.pid)
 
+    def test_wait_msgq(self):
+        """
+        Test we can wait for msgq to provide its own alias.
+
+        It is not available the first time, the second it is.
+        """
+        class RpcSession:
+            def __init__(self):
+                # Not yet called
+                self.called = 0
+
+            def rpc_call(self, command, recipient):
+                self.called += 1
+                if self.called == 1:
+                    raise isc.config.RPCRecipientMissing("Not yet")
+                elif self.called == 2:
+                    raise isc.config.RPCError(1, "What?")
+                else:
+                    raise Exception("Called too many times")
+
+        init = MockInitSimple()
+        init.wait_time = 1
+        init.ccs = RpcSession()
+        init.wait_msgq()
+        self.assertEqual(2, init.ccs.called)
+
+    def test_wait_msgq_fail(self):
+        """
+        Test the wait_msgq fails in case the msgq does not appear
+        after so many attempts.
+        """
+        class RpcSession:
+            def __init__(self):
+                self.called = 0
+
+            def rpc_call(self, command, recipient):
+                self.called += 1
+                raise isc.config.RPCRecipientMissing("Not yet")
+
+        b10init = MockInitSimple()
+        b10init.wait_time = 1
+        b10init.ccs = RpcSession()
+        self.assertRaises(init.ProcessStartError, b10init.wait_msgq)
+        self.assertEqual(10, b10init.ccs.called)
+
     def test_start_cfgmgr(self):
         '''Test that b10-cfgmgr is started.'''
         class DummySession():

+ 5 - 0
src/bin/bindctl/bindcmd.py

@@ -272,6 +272,11 @@ WARNING: The Python readline module isn't available, so some command line
         else:
             self._print('Login failed: either the user name or password is '
                         'invalid.\n')
+
+        # If this was not an interactive session do not prompt for login info.
+        if not sys.stdin.isatty():
+            return False
+
         while True:
             count = count + 1
             if count > 3:

+ 5 - 1
src/bin/bindctl/bindctl_main.py.in

@@ -26,6 +26,7 @@ from bindctl import command_sets
 import pprint
 from optparse import OptionParser, OptionValueError
 import isc.util.process
+import isc.util.traceback_handler
 
 isc.util.process.rename()
 
@@ -150,7 +151,7 @@ def set_bindctl_options(parser):
                       default=None, action='store',
                       help='Directory to store the password CSV file')
 
-if __name__ == '__main__':
+def main():
     parser = OptionParser(version = VERSION)
     set_bindctl_options(parser)
     (options, args) = parser.parse_args()
@@ -161,3 +162,6 @@ if __name__ == '__main__':
     command_sets.prepare_execute_commands(tool)
     result = tool.run()
     sys.exit(result)
+
+if __name__ == '__main__':
+    isc.util.traceback_handler.traceback_handler(main)

+ 2 - 1
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -30,6 +30,7 @@ import isc.log
 isc.log.init("b10-cfgmgr", buffer=True)
 from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError, logger
 from isc.log_messages.cfgmgr_messages import *
+import isc.util.traceback_handler
 
 isc.util.process.rename()
 
@@ -128,4 +129,4 @@ def main():
     return 0
 
 if __name__ == "__main__":
-    sys.exit(main())
+    sys.exit(isc.util.traceback_handler.traceback_handler(main))

+ 0 - 9
src/bin/cfgmgr/plugins/tests/datasrc_test.py

@@ -96,15 +96,6 @@ class DatasrcTest(unittest.TestCase):
             "params": {}
         }]})
 
-    def test_dstype_bad(self):
-        """
-        The configuration is correct by the spec, but it would be rejected
-        by the client list. Check we reject it.
-        """
-        self.reject({"IN": [{
-            "type": "No such type"
-        }]})
-
     def test_invalid_mem_params(self):
         """
         The client list skips in-memory sources. So we check it locally that

+ 11 - 4
src/bin/cmdctl/Makefile.am

@@ -57,12 +57,19 @@ 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
 
+# This is a hack, as b10-certgen creates both cmdctl-keyfile.pem and
+# cmdctl-certfile.pem, and in a parallel make, making these targets
+# simultaneously may result in corrupted files. With GNU make, there is
+# a non-portable way of working around this with pattern rules, but we
+# adopt this hack instead. The downside is that cmdctl-certfile.pem will
+# not be re-generated if cmdctl-keyfile.pem exists and is older. See
+# Trac ticket #2962.
+cmdctl-certfile.pem: cmdctl-keyfile.pem
+	touch $(builddir)/cmdctl-keyfile.pem
+
 if INSTALL_CONFIGURATIONS
 
 # Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
@@ -71,7 +78,7 @@ install-data-local:
 	$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
 	for f in $(CERTFILES) ; do	\
 	  if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then	\
-	    ${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ;	\
+	    ${INSTALL} -m 640 $$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ;	\
 	  fi ;	\
 	done
 

+ 0 - 2
src/bin/cmdctl/b10-cmdctl.xml

@@ -169,8 +169,6 @@
       The configuration command is:
     </para>
 
-<!-- NOTE: print_settings is not documented since I think will be removed -->
-
     <para>
       <command>shutdown</command> exits <command>b10-cmdctl</command>.
       This has an optional <varname>pid</varname> argument to

+ 22 - 9
src/bin/cmdctl/cmdctl.py.in

@@ -36,11 +36,11 @@ import re
 import ssl, socket
 import isc
 import pprint
-import select
 import csv
 import random
 import time
 import signal
+import errno
 from isc.config import ccsession
 import isc.cc.proto_defs
 import isc.util.process
@@ -49,6 +49,7 @@ from optparse import OptionParser, OptionValueError
 from hashlib import sha1
 from isc.util import socketserver_mixin
 from isc.log_messages.cmdctl_messages import *
+import isc.util.traceback_handler
 
 isc.log.init("b10-cmdctl", buffer=True)
 logger = isc.log.Logger("cmdctl")
@@ -371,8 +372,6 @@ class CommandControl():
             self._httpserver.shutdown()
             self._serving = False
 
-        elif command == 'print_settings':
-            answer = ccsession.create_answer(0, self._cmdctl_config_data)
         else:
             answer = ccsession.create_answer(1, 'unknown command: ' + command)
 
@@ -525,7 +524,17 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
             logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_STARTED,
                          server_address[0], server_address[1])
         except socket.error as err:
-            raise CmdctlException("Error creating server, because: %s \n" % str(err))
+            if err.errno == errno.EADDRINUSE:
+                raise CmdctlException(("Error creating server, because " +\
+                                       "port %d on address %s is " +\
+                                       "already in use. Please stop " +\
+                                       "the application that uses it or " +\
+                                       "see the guide about using a " +\
+                                       "different port for b10-cmdctl.") % \
+                                      (server_address[1], \
+                                       server_address[0]))
+            else:
+                raise CmdctlException("Error creating server, because: %s" % str(err))
 
         self.user_sessions = {}
         self.idle_timeout = idle_timeout
@@ -602,12 +611,13 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
             # error)
             return ssl_sock
         except ssl.SSLError as err:
+            self.close_request(sock)
             logger.error(CMDCTL_SSL_SETUP_FAILURE_USER_DENIED, err)
-        except (CmdctlException, IOError) as cce:
+            raise
+        except (CmdctlException, IOError, socket.error) as cce:
+            self.close_request(sock)
             logger.error(CMDCTL_SSL_SETUP_FAILURE_READING_CERT, cce)
-        self.close_request(sock)
-        # raise socket error to finish the request
-        raise socket.error
+            raise
 
     def get_request(self):
         '''Get client request socket and wrap it in SSL context. '''
@@ -677,7 +687,7 @@ def set_cmd_options(parser):
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False,
             help="display more about what is going on")
 
-if __name__ == '__main__':
+def main():
     set_signal_handler()
     parser = OptionParser(version = __version__)
     set_cmd_options(parser)
@@ -703,3 +713,6 @@ if __name__ == '__main__':
     logger.info(CMDCTL_EXITING)
 
     sys.exit(result)
+
+if __name__ == '__main__':
+    isc.util.traceback_handler.traceback_handler(main)

+ 0 - 5
src/bin/cmdctl/cmdctl.spec.pre.in

@@ -24,11 +24,6 @@
     ],
     "commands": [
       {
-        "command_name": "print_settings",
-        "command_description": "Print some_string and some_int to stdout",
-        "command_args": []
-      },
-      {
         "command_name": "shutdown",
         "command_description": "shutdown cmdctl",
         "command_args": [

+ 2 - 0
src/bin/cmdctl/tests/b10-certgen_test.py

@@ -200,6 +200,8 @@ class TestCertGenTool(unittest.TestCase):
         # No such file
         self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
 
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
     def test_permissions(self):
         """
         Test some combinations of correct and bad permissions.

+ 37 - 16
src/bin/cmdctl/tests/cmdctl_test.py

@@ -15,7 +15,7 @@
 
 
 import unittest
-import socket
+import ssl, socket
 import tempfile
 import time
 import stat
@@ -462,10 +462,22 @@ class TestCommandControl(unittest.TestCase):
         answer = self.cmdctl.command_handler('unknown-command', None)
         self._check_answer(answer, 1, 'unknown command: unknown-command')
 
-        answer = self.cmdctl.command_handler('print_settings', None)
+        # Send a real command. Mock stuff so the shutdown command doesn't
+        # cause an exception.
+        class ModuleCC:
+            def send_stopping():
+                pass
+        self.cmdctl._module_cc = ModuleCC
+        called = []
+        class Server:
+            def shutdown():
+                called.append('shutdown')
+        self.cmdctl._httpserver = Server
+        answer = self.cmdctl.command_handler('shutdown', None)
         rcode, msg = ccsession.parse_answer(answer)
         self.assertEqual(rcode, 0)
-        self.assertTrue(msg != None)
+        self.assertIsNone(msg)
+        self.assertEqual(['shutdown'], called)
 
     def test_command_handler_spec_update(self):
         # Should not be present
@@ -543,10 +555,10 @@ class TestCommandControl(unittest.TestCase):
         self.assertEqual(1, rcode)
 
         # Send a command to cmdctl itself.  Should be the same effect.
-        rcode, value = self.cmdctl.send_command('Cmdctl', 'print_settings',
+        rcode, value = self.cmdctl.send_command('Cmdctl', 'shutdown',
                                                 None)
         self.assertEqual(2, len(self.cmdctl.sent_messages))
-        self.assertEqual(({'command': ['print_settings']}, 'Cmdctl'),
+        self.assertEqual(({'command': ['shutdown']}, 'Cmdctl'),
                          self.cmdctl.sent_messages[-1])
         self.assertEqual(1, rcode)
 
@@ -680,11 +692,15 @@ class TestSecureHTTPServer(unittest.TestCase):
         # Just some file that we know exists
         file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
         check_file(file_name)
-        with UnreadableFile(file_name):
-            self.assertRaises(CmdctlException, check_file, file_name)
         self.assertRaises(CmdctlException, check_file, '/local/not-exist')
         self.assertRaises(CmdctlException, check_file, '/')
 
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
+    def test_check_file_for_unreadable(self):
+        file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+        with UnreadableFile(file_name):
+            self.assertRaises(CmdctlException, check_file, file_name)
 
     def test_check_key_and_cert(self):
         keyfile = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
@@ -702,6 +718,15 @@ class TestSecureHTTPServer(unittest.TestCase):
         self.assertRaises(CmdctlException, self.server._check_key_and_cert,
                          '/', certfile)
 
+        # All OK (also happens to check the context code above works)
+        self.server._check_key_and_cert(keyfile, certfile)
+
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
+    def test_check_key_and_cert_for_unreadable(self):
+        keyfile = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+        certfile = BUILD_FILE_PATH + 'cmdctl-certfile.pem'
+
         # no read permission
         with UnreadableFile(certfile):
             self.assertRaises(CmdctlException,
@@ -713,21 +738,17 @@ class TestSecureHTTPServer(unittest.TestCase):
                               self.server._check_key_and_cert,
                               keyfile, certfile)
 
-        # All OK (also happens to check the context code above works)
-        self.server._check_key_and_cert(keyfile, certfile)
-
     def test_wrap_sock_in_ssl_context(self):
         sock = socket.socket()
 
-        # Bad files should result in a socket.error raised by our own
-        # code in the basic file checks
-        self.assertRaises(socket.error,
+        # Bad files should result in a CmdctlException in the basic file
+        # checks
+        self.assertRaises(CmdctlException,
                           self.server._wrap_socket_in_ssl_context,
                           sock,
                           'no_such_file', 'no_such_file')
 
-        # Using a non-certificate file would cause an SSLError, which
-        # is caught by our code which then raises a basic socket.error
+        # Using a non-certificate file would cause an SSLError
         self.assertRaises(socket.error,
                           self.server._wrap_socket_in_ssl_context,
                           sock,
@@ -747,7 +768,7 @@ class TestSecureHTTPServer(unittest.TestCase):
         orig_check_func = self.server._check_key_and_cert
         try:
             self.server._check_key_and_cert = lambda x,y: None
-            self.assertRaises(socket.error,
+            self.assertRaises(IOError,
                               self.server._wrap_socket_in_ssl_context,
                               sock,
                               'no_such_file', 'no_such_file')

+ 7 - 0
src/bin/d2/.gitignore

@@ -0,0 +1,7 @@
+/b10-dhcp-ddns
+/b10-dhcp-ddns.8
+/d2_messages.cc
+/d2_messages.h
+/spec_config.h
+/spec_config.h.pre
+/s-messages

+ 89 - 0
src/bin/d2/Makefile.am

@@ -0,0 +1,89 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some Boost headers when compiling with clang
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES  = *.gcno *.gcda spec_config.h d2_messages.h d2_messages.cc s-messages
+
+man_MANS = b10-dhcp-ddns.8
+DISTCLEANFILES = $(man_MANS)
+EXTRA_DIST = $(man_MANS) b10-dhcp-ddns.xml dhcp-ddns.spec
+
+if GENERATE_DOCS
+b10-dhcp-ddns.8: b10-dhcp-ddns.xml
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ \
+        http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \
+	$(srcdir)/b10-dhcp-ddns.xml
+
+else
+
+$(man_MANS):
+	@echo Man generation disabled.  Creating dummy $@.  Configure with --enable-generate-docs to enable it.
+	@echo Man generation disabled.  Remove this file, configure with --enable-generate-docs, and rebuild BIND 10 > $@
+
+endif
+
+spec_config.h: spec_config.h.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
+
+d2_messages.h d2_messages.cc: s-messages
+
+s-messages: d2_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/d2/d2_messages.mes
+	touch $@
+
+BUILT_SOURCES = spec_config.h d2_messages.h d2_messages.cc
+
+pkglibexec_PROGRAMS = b10-dhcp-ddns
+
+b10_dhcp_ddns_SOURCES  = main.cc
+b10_dhcp_ddns_SOURCES += d2_asio.h
+b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
+b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
+b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
+b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
+b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
+b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
+b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
+b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
+b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
+b10_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
+b10_dhcp_ddns_SOURCES += nc_remove.cc nc_remove.h
+b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
+b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
+
+nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
+EXTRA_DIST += d2_messages.mes
+
+b10_dhcp_ddns_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+
+b10_dhcp_ddnsdir = $(pkgdatadir)
+b10_dhcp_ddns_DATA = dhcp-ddns.spec

+ 121 - 0
src/bin/d2/b10-dhcp-ddns.xml

@@ -0,0 +1,121 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - Copyright (C) 2013  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.
+-->
+
+<refentry>
+
+  <refentryinfo>
+    <date>May 15, 2013</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-dhcp-ddns</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-dhcp-ddns</refname>
+    <refpurpose>DHCP-DDNS process in BIND 10 architecture</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2013</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-dhcp-ddns</command>
+      <arg><option>-v</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-dhcp-ddns</command>
+      <arg><option>-s</option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>
+      The <command>b10-dhcp-ddns</command> service processes requests to
+      to update DNS mapping based on DHCP lease change events. The service
+      may run either as a BIND10 module (integrated mode) or as a individual
+      process (stand-alone mode) dependent upon command line arguments. The
+      default is integrated mode.  Stand alone operation is strictly for
+      development purposes and is not suited for production.
+    </para>
+
+  </refsect1>
+
+  <refsect1>
+    <title>ARGUMENTS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term><option>-v</option></term>
+        <listitem><para>
+          Verbose mode sets the logging level to debug. This is primarily
+          for development purposes in stand-alone mode.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-s</option></term>
+        <listitem><para>
+          Causes the process to run without attempting to connect to the
+          BIND10 message queue.  This is for development purposes.
+        </para></listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>b10-dhcp-ddns</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-dhcp-ddns</command> process was first coded in
+      May 2013 by the ISC Kea/Dhcp team.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 31 - 0
src/bin/d2/d2_asio.h

@@ -0,0 +1,31 @@
+// Copyright (C) 2013  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 D2_ASIO_H
+#define D2_ASIO_H
+
+#include <asiolink/asiolink.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines a smart pointer to an IOService instance.
+typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

+ 213 - 0
src/bin/d2/d2_cfg_mgr.cc

@@ -0,0 +1,213 @@
+// Copyright (C) 2013 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 <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <util/encode/hex.h>
+
+#include <boost/foreach.hpp>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+typedef std::vector<uint8_t> ByteAddress;
+
+} // end of unnamed namespace
+
+// *********************** D2CfgContext  *************************
+
+D2CfgContext::D2CfgContext()
+    : forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
+      reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")),
+      keys_(new TSIGKeyInfoMap()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
+    if (rhs.forward_mgr_) {
+        forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+        forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+    }
+
+    if (rhs.reverse_mgr_) {
+        reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+        reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+    }
+
+    keys_ = rhs.keys_;
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+// *********************** D2CfgMgr  *************************
+
+const char* D2CfgMgr::IPV4_REV_ZONE_SUFFIX = "in-addr.arpa.";
+
+const char* D2CfgMgr::IPV6_REV_ZONE_SUFFIX = "ip6.arpa.";
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
+    // TSIG keys need to parse before the Domains, so we can catch Domains
+    // that specify undefined keys. Create the necessary parsing order now.
+    addToParseOrder("interface");
+    addToParseOrder("ip_address");
+    addToParseOrder("port");
+    addToParseOrder("tsig_keys");
+    addToParseOrder("forward_ddns");
+    addToParseOrder("reverse_ddns");
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+    if (fqdn.empty()) {
+        // This is a programmatic error and should not happen.
+        isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+    }
+
+    // Fetch the forward manager from the D2 context.
+    DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+    // Call the manager's match method and return the result.
+    return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& ip_address, DdnsDomainPtr& domain) {
+    // Note, reverseIpAddress will throw if the ip_address is invalid.
+    std::string reverse_address = reverseIpAddress(ip_address);
+
+    // Fetch the reverse manager from the D2 context.
+    DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+    return (mgr->matchDomain(reverse_address, domain));
+}
+
+std::string
+D2CfgMgr::reverseIpAddress(const std::string& address) {
+    try {
+        // Convert string address into an IOAddress and invoke the
+        // appropriate reverse method.
+        isc::asiolink::IOAddress ioaddr(address);
+        if (ioaddr.isV4()) {
+            return (reverseV4Address(ioaddr));
+        }
+
+        return (reverseV6Address(ioaddr));
+
+    } catch (const isc::Exception& ex) {
+        isc_throw(D2CfgError, "D2CfgMgr cannot reverse address: "
+                               << address << " : " << ex.what());
+    }
+}
+
+std::string
+D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
+    if (!ioaddr.isV4()) {
+        isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
+                  << ioaddr);
+    }
+
+    // Get the address in byte vector form.
+    const ByteAddress bytes = ioaddr.toBytes();
+
+    // Walk backwards through vector outputting each octet and a dot.
+    std::ostringstream stream;
+
+    // We have to set the following variable to get
+    // const_reverse_iterator type of rend(), otherwise Solaris GCC
+    // complains on operator!= by trying to use the non-const variant.
+    const ByteAddress::const_reverse_iterator end = bytes.rend();
+
+    for (ByteAddress::const_reverse_iterator rit = bytes.rbegin();
+         rit != end;
+         ++rit)
+    {
+        stream << static_cast<unsigned int>(*rit) << ".";
+    }
+
+    // Tack on the suffix and we're done.
+    stream << IPV4_REV_ZONE_SUFFIX;
+    return(stream.str());
+}
+
+std::string
+D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
+    if (!ioaddr.isV6()) {
+        isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: " << ioaddr);
+    }
+
+    // Turn the address into a string of digits.
+    const ByteAddress bytes = ioaddr.toBytes();
+    const std::string digits = isc::util::encode::encodeHex(bytes);
+
+    // Walk backwards through string outputting each digits and a dot.
+    std::ostringstream stream;
+
+    // We have to set the following variable to get
+    // const_reverse_iterator type of rend(), otherwise Solaris GCC
+    // complains on operator!= by trying to use the non-const variant.
+    const std::string::const_reverse_iterator end = digits.rend();
+
+    for (std::string::const_reverse_iterator rit = digits.rbegin();
+         rit != end;
+         ++rit)
+    {
+        stream << static_cast<char>(*rit) << ".";
+    }
+
+    // Tack on the suffix and we're done.
+    stream << IPV6_REV_ZONE_SUFFIX;
+    return(stream.str());
+}
+
+
+isc::dhcp::ParserPtr
+D2CfgMgr::createConfigParser(const std::string& config_id) {
+    // Get D2 specific context.
+    D2CfgContextPtr context = getD2CfgContext();
+
+    // Create parser instance based on element_id.
+    isc::dhcp::DhcpConfigParser* parser = NULL;
+    if ((config_id == "interface")  ||
+        (config_id == "ip_address")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             context->getStringStorage());
+    } else if (config_id == "port") {
+        parser = new isc::dhcp::Uint32Parser(config_id,
+                                             context->getUint32Storage());
+    } else if (config_id ==  "forward_ddns") {
+        parser = new DdnsDomainListMgrParser("forward_mgr",
+                                             context->getForwardMgr(),
+                                             context->getKeys());
+    } else if (config_id ==  "reverse_ddns") {
+        parser = new DdnsDomainListMgrParser("reverse_mgr",
+                                             context->getReverseMgr(),
+                                             context->getKeys());
+    } else if (config_id ==  "tsig_keys") {
+        parser = new TSIGKeyInfoListParser("tsig_key_list", context->getKeys());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: D2CfgMgr parameter not supported: "
+                  << config_id);
+    }
+
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 237 - 0
src/bin/d2/d2_cfg_mgr.h

@@ -0,0 +1,237 @@
+// Copyright (C) 2013 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 D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
+#include <d2/d_cfg_mgr.h>
+#include <d2/d2_config.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief  DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, DCfgContextBase.
+class D2CfgContext : public DCfgContextBase {
+public:
+    /// @brief Constructor
+    D2CfgContext();
+
+    /// @brief Destructor
+    virtual ~D2CfgContext();
+
+    /// @brief Creates a clone of this context object.
+    ///
+    /// @return returns a pointer to the new clone.
+    virtual DCfgContextBasePtr clone() {
+        return (DCfgContextBasePtr(new D2CfgContext(*this)));
+    }
+
+    /// @brief Fetches the forward DNS domain list manager.
+    ///
+    /// @return returns a pointer to the forward manager.
+    DdnsDomainListMgrPtr getForwardMgr() {
+        return (forward_mgr_);
+    }
+
+    /// @brief Fetches the reverse DNS domain list manager.
+    ///
+    /// @return returns a pointer to the reverse manager.
+    DdnsDomainListMgrPtr getReverseMgr() {
+        return (reverse_mgr_);
+    }
+
+    /// @brief Fetches the map of TSIG keys.
+    ///
+    /// @return returns a pointer to the key map.
+    TSIGKeyInfoMapPtr getKeys() {
+        return (keys_);
+    }
+
+protected:
+    /// @brief Copy constructor for use by derivations in clone().
+    D2CfgContext(const D2CfgContext& rhs);
+
+private:
+    /// @brief Private assignment operator to avoid potential for slicing.
+    D2CfgContext& operator=(const D2CfgContext& rhs);
+
+    /// @brief Forward domain list manager.
+    DdnsDomainListMgrPtr forward_mgr_;
+
+    /// @brief Reverse domain list manager.
+    DdnsDomainListMgrPtr reverse_mgr_;
+
+    /// @brief Storage for the map of TSIGKeyInfos
+    TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration.  This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public DCfgMgrBase {
+public:
+    /// @brief Reverse zone suffix added to IPv4 addresses for reverse lookups
+    /// @todo This should be configurable.
+    static const char* IPV4_REV_ZONE_SUFFIX;
+
+    /// @brief Reverse zone suffix added to IPv6 addresses for reverse lookups
+    /// @todo This should be configurable.
+    static const char* IPV6_REV_ZONE_SUFFIX;
+
+    /// @brief Constructor
+    D2CfgMgr();
+
+    /// @brief Destructor
+    virtual ~D2CfgMgr();
+
+    /// @brief Convenience method that returns the D2 configuration context.
+    ///
+    /// @return returns a pointer to the configuration context.
+    D2CfgContextPtr getD2CfgContext() {
+        return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+    }
+
+    /// @brief Matches a given FQDN to a forward domain.
+    ///
+    /// This calls the matchDomain method of the forward domain manager to
+    /// match the given FQDN to a forward domain.
+    ///
+    /// @param fqdn is the name for which to look.
+    /// @param domain receives the matching domain. Note that it will be reset
+    /// upon entry and only set if a match is subsequently found.
+    ///
+    /// @return returns true if a match is found, false otherwise.
+    /// @throw throws D2CfgError if given an invalid fqdn.
+    bool matchForward(const std::string& fqdn, DdnsDomainPtr& domain);
+
+    /// @brief Matches a given IP address to a reverse domain.
+    ///
+    /// This calls the matchDomain method of the reverse domain manager to
+    /// match the given IPv4 or IPv6 address to a reverse domain.
+    ///
+    /// @param ip_address is the name for which to look.
+    /// @param domain receives the matching domain. Note that it will be reset
+    /// upon entry and only set if a match is subsequently found.
+    ///
+    /// @return returns true if a match is found, false otherwise.
+    /// @throw throws D2CfgError if given an invalid fqdn.
+    bool matchReverse(const std::string& ip_address, DdnsDomainPtr& domain);
+
+    /// @brief Generate a reverse order string for the given IP address
+    ///
+    /// This method creates a string containing the given IP address
+    /// contents in reverse order.  This format is used for matching
+    /// against reverse DDNS domains in DHCP_DDNS configuration.
+    /// After reversing the syllables of the address, it appends the
+    /// appropriate suffix.
+    ///
+    /// @param address string containing a valid IPv4 or IPv6 address.
+    ///
+    /// @return a std::string containing the reverse order address.
+    ///
+    /// @throw D2CfgError if given an invalid address.
+    static std::string reverseIpAddress(const std::string& address);
+
+    /// @brief Generate a reverse order string for the given IP address
+    ///
+    /// This method creates a string containing the given IP address
+    /// contents in reverse order.  This format is used for matching
+    /// against reverse DDNS domains in DHCP_DDNS configuration.
+    /// After reversing the syllables of the address, it appends the
+    /// appropriate suffix.
+    ///
+    /// Example:
+    ///   input:  192.168.1.15
+    ///  output:  15.1.168.192.in-addr.arpa.
+    ///
+    /// @param ioaddr is the IPv4 IOaddress to convert
+    ///
+    /// @return a std::string containing the reverse order address.
+    ///
+    /// @throw D2CfgError if not given an IPv4  address.
+    static std::string reverseV4Address(const isc::asiolink::IOAddress& ioaddr);
+
+    /// @brief Generate a reverse order string for the given IP address
+    ///
+    /// This method creates a string containing the given IPv6 address
+    /// contents in reverse order.  This format is used for matching
+    /// against reverse DDNS domains in DHCP_DDNS configuration.
+    /// After reversing the syllables of the address, it appends the
+    /// appropriate suffix.
+    ///
+    /// IPv6 example:
+    /// input:  2001:db8:302:99::
+    /// output:
+    ///0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.
+    ///
+    /// @param ioaddr string containing a valid IPv6 address.
+    ///
+    /// @return a std::string containing the reverse order address.
+    ///
+    /// @throw D2CfgError if not given an IPv6 address.
+    static std::string reverseV6Address(const isc::asiolink::IOAddress& ioaddr);
+
+protected:
+    /// @brief Given an element_id returns an instance of the appropriate
+    /// parser.
+    ///
+    /// It is responsible for top-level or outermost DHCP-DDNS configuration
+    /// elements (see dhcp-ddns.spec):
+    ///     1. interface
+    ///     2. ip_address
+    ///     3. port
+    ///     4. forward_ddns
+    ///     5. reverse_ddns
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    ///
+    /// @return returns a ParserPtr to the parser instance.
+    /// @throw throws DCfgMgrBaseError if an error occurs.
+    virtual isc::dhcp::ParserPtr
+    createConfigParser(const std::string& element_id);
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CFG_MGR_H

+ 649 - 0
src/bin/d2/d2_config.cc

@@ -0,0 +1,649 @@
+// Copyright (C) 2013 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 <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+// *********************** TSIGKeyInfo  *************************
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+                         const std::string& secret)
+    :name_(name), algorithm_(algorithm), secret_(secret) {
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+
+// *********************** DnsServerInfo  *************************
+
+const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";
+
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+                             isc::asiolink::IOAddress ip_address, uint32_t port,
+                             bool enabled)
+    :hostname_(hostname), ip_address_(ip_address), port_(port),
+    enabled_(enabled) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+std::string
+DnsServerInfo::toText() const {
+    std::ostringstream stream;
+    stream << (getIpAddress().toText()) << " port:" << getPort();
+    return (stream.str());
+}
+
+
+std::ostream&
+operator<<(std::ostream& os, const DnsServerInfo& server) {
+    os << server.toText();
+    return (os);
+}
+
+// *********************** DdnsDomain  *************************
+
+DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
+                       DnsServerInfoStoragePtr servers)
+    : name_(name), key_name_(key_name), servers_(servers) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+// *********************** DdnsDomainLstMgr  *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+    domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+    if (!domains) {
+        isc_throw(D2CfgError,
+                  "DdnsDomainListMgr::setDomains: Domain list may not be null");
+    }
+
+    domains_ = domains;
+
+    // Look for the wild card domain. If present, set the member variable
+    // to remember it.  This saves us from having to look for it every time
+    // we attempt a match.
+    DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+    if (gotit != domains_->end()) {
+            wildcard_domain_ = gotit->second;
+    }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+    // First check the case of one domain to rule them all.
+    if ((size() == 1) && (wildcard_domain_)) {
+        domain = wildcard_domain_;
+        return (true);
+    }
+
+    // Iterate over the domain map looking for the domain which matches
+    // the longest portion of the given fqdn.
+
+    size_t req_len = fqdn.size();
+    size_t match_len = 0;
+    DdnsDomainMapPair map_pair;
+    DdnsDomainPtr best_match;
+    BOOST_FOREACH (map_pair, *domains_) {
+        std::string domain_name = map_pair.first;
+        size_t dom_len = domain_name.size();
+
+        // If the domain name is longer than the fqdn, then it cant be match.
+        if (req_len < dom_len) {
+            continue;
+        }
+
+        // If the lengths are identical and the names match we're done.
+        if (req_len == dom_len) {
+            if (fqdn == domain_name) {
+                // exact match, done
+                domain = map_pair.second;
+                return (true);
+            }
+        } else {
+            // The fqdn is longer than the domain name.  Adjust the start
+            // point of comparison by the excess in length.  Only do the
+            // comparison if the adjustment lands on a boundary. This
+            // prevents "onetwo.net" from matching "two.net".
+            size_t offset = req_len - dom_len;
+            if ((fqdn[offset - 1] == '.')  &&
+               (fqdn.compare(offset, std::string::npos, domain_name) == 0)) {
+                // Fqdn contains domain name, keep it if its better than
+                // any we have matched so far.
+                if (dom_len > match_len) {
+                    match_len = dom_len;
+                    best_match = map_pair.second;
+                }
+            }
+        }
+    }
+
+    if (!best_match) {
+        // There's no match. If they specified a wild card domain use it
+        // otherwise there's no domain for this entry.
+        if (wildcard_domain_) {
+            domain = wildcard_domain_;
+            return (true);
+        }
+
+        LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+        return (false);
+    }
+
+    domain = best_match;
+    return (true);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser  *************************
+
+TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
+                                     TSIGKeyInfoMapPtr keys)
+    : entry_name_(entry_name), keys_(keys), local_scalars_() {
+    if (!keys_) {
+        isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
+                  " key storage cannot be null");
+    }
+}
+
+TSIGKeyInfoParser::~TSIGKeyInfoParser() {
+}
+
+void
+TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
+    isc::dhcp::ConfigPair config_pair;
+    // For each element in the key configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    BOOST_FOREACH (config_pair, key_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+}
+
+isc::dhcp::ParserPtr
+TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "name")  ||
+        (config_id == "algorithm") ||
+        (config_id == "secret")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: TSIGKeyInfo parameter not supported: "
+                  << config_id);
+    }
+
+    // Return the new parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+TSIGKeyInfoParser::commit() {
+    std::string name;
+    std::string algorithm;
+    std::string secret;
+
+    // Fetch the key configuration's parsed scalar values from parser's
+    // local storage.
+    local_scalars_.getParam("name", name);
+    local_scalars_.getParam("algorithm", algorithm);
+    local_scalars_.getParam("secret", secret);
+
+    // @todo Validation here is very superficial. This will expand as TSIG
+    // Key use is more fully implemented.
+
+    // Name cannot be blank.
+    if (name.empty()) {
+        isc_throw(D2CfgError, "TSIG Key Info must specify name");
+    }
+
+    // Algorithm cannot be blank.
+    if (algorithm.empty()) {
+        isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
+    }
+
+    // Secret cannot be blank.
+    if (secret.empty()) {
+        isc_throw(D2CfgError, "TSIG Key Info must specify secret");
+    }
+
+    // Currently, the premise is that key storage is always empty prior to
+    // parsing so we are always adding keys never replacing them. Duplicates
+    // are not allowed and should be flagged as a configuration error.
+    if (keys_->find(name) != keys_->end()) {
+        isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
+    }
+
+    TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+
+    // Add the new TSIGKeyInfo to the key storage.
+    (*keys_)[name]=key_info;
+}
+
+// *********************** TSIGKeyInfoListParser  *************************
+
+TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
+                                       TSIGKeyInfoMapPtr keys)
+    :list_name_(list_name), keys_(keys), local_keys_(new TSIGKeyInfoMap()),
+     parsers_() {
+    if (!keys_) {
+        isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
+                  " key storage cannot be null");
+    }
+}
+
+TSIGKeyInfoListParser::~TSIGKeyInfoListParser(){
+}
+
+void
+TSIGKeyInfoListParser::
+build(isc::data::ConstElementPtr key_list){
+    int i = 0;
+    isc::data::ConstElementPtr key_config;
+    // For each key element in the key list:
+    // 1. Create a parser for the key element.
+    // 2. Invoke the parser's build method passing in the key's
+    // configuration.
+    // 3. Add the parser to a local collection of parsers.
+    BOOST_FOREACH(key_config, key_list->listValue()) {
+        // Create a name for the parser based on its position in the list.
+        std::string entry_name = boost::lexical_cast<std::string>(i++);
+        isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
+                                                            local_keys_));
+        parser->build(key_config);
+        parsers_.push_back(parser);
+    }
+}
+
+void
+TSIGKeyInfoListParser::commit() {
+    // Invoke commit on each server parser. This will cause each one to
+    // create it's server instance and commit it to storage.
+    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+
+    // Now that we know we have a valid list, commit that list to the
+    // area given to us during construction (i.e. to the d2 context).
+    *keys_ = *local_keys_;
+}
+
+// *********************** DnsServerInfoParser  *************************
+
+DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
+    DnsServerInfoStoragePtr servers)
+    : entry_name_(entry_name), servers_(servers), local_scalars_() {
+    if (!servers_) {
+        isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
+                  " server storage cannot be null");
+    }
+}
+
+DnsServerInfoParser::~DnsServerInfoParser() {
+}
+
+void
+DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
+    isc::dhcp::ConfigPair config_pair;
+    // For each element in the server configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    BOOST_FOREACH (config_pair, server_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+
+}
+
+isc::dhcp::ParserPtr
+DnsServerInfoParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "hostname")  ||
+        (config_id == "ip_address")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else if (config_id == "port") {
+        parser = new isc::dhcp::Uint32Parser(config_id,
+                                             local_scalars_.getUint32Storage());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: DnsServerInfo parameter not supported: "
+                  << config_id);
+    }
+
+    // Return the new parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DnsServerInfoParser::commit() {
+    std::string hostname;
+    std::string ip_address;
+    uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
+
+    // Fetch the server configuration's parsed scalar values from parser's
+    // local storage.
+    local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
+    local_scalars_.getParam("ip_address", ip_address,
+                            DCfgContextBase::OPTIONAL);
+    local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
+
+    // The configuration must specify one or the other.
+    if (hostname.empty() == ip_address.empty()) {
+        isc_throw(D2CfgError, "Dns Server must specify one or the other"
+                  " of hostname and IP address");
+    }
+
+    DnsServerInfoPtr serverInfo;
+    if (!hostname.empty()) {
+        // When  hostname is specified, create a valid, blank IOAddress and
+        // then create the DnsServerInfo.
+        isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
+        serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+    } else {
+        try {
+            // Create an IOAddress from the IP address string given and then
+            // create the DnsServerInfo.
+            isc::asiolink::IOAddress io_addr(ip_address);
+            serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+        } catch (const isc::asiolink::IOError& ex) {
+            isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
+        }
+    }
+
+    // Add the new DnsServerInfo to the server storage.
+    servers_->push_back(serverInfo);
+}
+
+// *********************** DnsServerInfoListParser  *************************
+
+DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
+                                       DnsServerInfoStoragePtr servers)
+    :list_name_(list_name), servers_(servers), parsers_() {
+    if (!servers_) {
+        isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
+                  " server storage cannot be null");
+    }
+}
+
+DnsServerInfoListParser::~DnsServerInfoListParser(){
+}
+
+void
+DnsServerInfoListParser::
+build(isc::data::ConstElementPtr server_list){
+    int i = 0;
+    isc::data::ConstElementPtr server_config;
+    // For each server element in the server list:
+    // 1. Create a parser for the server element.
+    // 2. Invoke the parser's build method passing in the server's
+    // configuration.
+    // 3. Add the parser to a local collection of parsers.
+    BOOST_FOREACH(server_config, server_list->listValue()) {
+        // Create a name for the parser based on its position in the list.
+        std::string entry_name = boost::lexical_cast<std::string>(i++);
+        isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
+                                                            servers_));
+        parser->build(server_config);
+        parsers_.push_back(parser);
+    }
+}
+
+void
+DnsServerInfoListParser::commit() {
+    // Domains must have at least one server.
+    if (parsers_.size() == 0) {
+        isc_throw (D2CfgError, "Server List must contain at least one server");
+    }
+
+    // Invoke commit on each server parser. This will cause each one to
+    // create it's server instance and commit it to storage.
+    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+}
+
+// *********************** DdnsDomainParser  *************************
+
+DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
+                                   DdnsDomainMapPtr domains,
+                                   TSIGKeyInfoMapPtr keys)
+    : entry_name_(entry_name), domains_(domains), keys_(keys),
+    local_servers_(new DnsServerInfoStorage()), local_scalars_() {
+    if (!domains_) {
+        isc_throw(D2CfgError,
+                  "DdnsDomainParser ctor, domain storage cannot be null");
+    }
+}
+
+
+DdnsDomainParser::~DdnsDomainParser() {
+}
+
+void
+DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
+    // For each element in the domain configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    isc::dhcp::ConfigPair config_pair;
+    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "name")  ||
+        (config_id == "key_name")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else if (config_id == "dns_servers") {
+       // Server list parser is given in our local server storage. It will pass
+       // this down to its server parsers and is where they will write their
+       // server instances upon commit.
+       parser = new DnsServerInfoListParser(config_id, local_servers_);
+    } else {
+       isc_throw(NotImplemented,
+                "parser error: DdnsDomain parameter not supported: "
+                << config_id);
+    }
+
+    // Return the new domain parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainParser::commit() {
+    std::string name;
+    std::string key_name;
+
+    // Domain name is not optional. The get will throw if its not there.
+    local_scalars_.getParam("name", name);
+
+    // Blank domain names are not allowed.
+    if (name.empty()) {
+        isc_throw(D2CfgError, "Domain name cannot be blank");
+    }
+
+    // Currently, the premise is that domain storage is always empty
+    // prior to parsing so always adding domains never replacing them.
+    // Duplicates are not allowed and should be flagged as a configuration
+    // error.
+    if (domains_->find(name) != domains_->end()) {
+        isc_throw(D2CfgError, "Duplicate domain specified:" << name);
+    }
+
+    // Key name is optional. If it is not blank, then validate it against
+    // the defined list of keys.
+    local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
+    if (!key_name.empty()) {
+        if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
+            isc_throw(D2CfgError, "DdnsDomain :" << name <<
+                     " specifies and undefined key:" << key_name);
+        }
+    }
+
+    // Instantiate the new domain and add it to domain storage.
+    DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
+
+    // Add the new domain to the domain storage.
+    (*domains_)[name]=domain;
+}
+
+// *********************** DdnsDomainListParser  *************************
+
+DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
+                                           DdnsDomainMapPtr domains,
+                                           TSIGKeyInfoMapPtr keys)
+    :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
+    if (!domains_) {
+        isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
+                  " domain storage cannot be null");
+    }
+}
+
+DdnsDomainListParser::~DdnsDomainListParser(){
+}
+
+void
+DdnsDomainListParser::
+build(isc::data::ConstElementPtr domain_list){
+    // For each domain element in the domain list:
+    // 1. Create a parser for the domain element.
+    // 2. Invoke the parser's build method passing in the domain's
+    // configuration.
+    // 3. Add the parser to the local collection of parsers.
+    int i = 0;
+    isc::data::ConstElementPtr domain_config;
+    BOOST_FOREACH(domain_config, domain_list->listValue()) {
+        std::string entry_name = boost::lexical_cast<std::string>(i++);
+        isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
+                                                         domains_, keys_));
+        parser->build(domain_config);
+        parsers_.push_back(parser);
+    }
+}
+
+void
+DdnsDomainListParser::commit() {
+    // Invoke commit on each server parser. This will cause each one to
+    // create it's server instance and commit it to storage.
+    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+}
+
+
+// *********************** DdnsDomainListMgrParser  *************************
+
+DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
+                              DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
+    : entry_name_(entry_name), mgr_(mgr), keys_(keys),
+    local_domains_(new DdnsDomainMap()), local_scalars_() {
+}
+
+
+DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
+}
+
+void
+DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
+    // For each element in the domain manager configuration:
+    // 1. Create a parser for the element.
+    // 2. Invoke the parser's build method passing in the element's
+    // configuration.
+    // 3. Invoke the parser's commit method to store the element's parsed
+    // data to the parser's local storage.
+    isc::dhcp::ConfigPair config_pair;
+    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+        parser->build(config_pair.second);
+        parser->commit();
+    }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    if (config_id == "ddns_domains") {
+       // Domain list parser is given our local domain storage. It will pass
+       // this down to its domain parsers and is where they will write their
+       // domain instances upon commit.
+       parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
+    } else {
+       isc_throw(NotImplemented, "parser error: "
+                 "DdnsDomainListMgr parameter not supported: " << config_id);
+    }
+
+    // Return the new domain parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainListMgrParser::commit() {
+    // Add the new domain to the domain storage.
+    mgr_->setDomains(local_domains_);
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 959 - 0
src/bin/d2/d2_config.h

@@ -0,0 +1,959 @@
+// Copyright (C) 2013-2014 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 D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <cc/data.h>
+#include <d2/d2_asio.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, and its secret key component.
+///
+/// @todo  NOTE that TSIG configuration parsing is functional, the use of
+/// TSIG Keys during the actual DNS update transactions is not.  This will be
+/// implemented in a future release.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain.  This value should
+/// map to one of the TSIG Keys in the key list.  Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The configuration specification for use with BIND10 is detailed in the file
+/// dhcp-ddns.spec.
+///
+/// The parsing class hierarchy reflects this same scheme.  Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainLiatParser creates and invokes DdnsDomainListParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+///  "interface" : "eth1" ,
+///  "ip_address" : "192.168.1.33" ,
+///  "port" : 88 ,
+///  "tsig_keys":
+//// [
+///    {
+///     "name": "d2_key.tmark.org" ,
+///     "algorithm": "md5" ,
+///     "secret": "0123456989"
+///    }
+///  ],
+///  "forward_ddns" :
+///  {
+///    "ddns_domains":
+///    [
+///      {
+///        "name": "tmark.org." ,
+///        "key_name": "d2_key.tmark.org" ,
+///        "dns_servers" :
+///        [
+///          { "hostname": "fserver.tmark.org" },
+///          { "hostname": "f2server.tmark.org" }
+///        ]
+///      },
+///      {
+///        "name": "pub.tmark.org." ,
+///        "key_name": "d2_key.tmark.org" ,
+///        "dns_servers" :
+///        [
+///          { "hostname": "f3server.tmark.org" }
+///        ]
+///      }
+///    ]
+///  },
+///  "reverse_ddns" :
+///  {
+///    "ddns_domains":
+///    [
+///      {
+///        "name": " 0.168.192.in.addr.arpa." ,
+///        "key_name": "d2_key.tmark.org" ,
+///        "dns_servers" :
+///        [
+///          { "ip_address": "127.0.0.101" , "port": 100 }
+///        ]
+///      }
+///    ]
+///  }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+    D2CfgError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a TSIG Key.
+///
+/// Currently, this is simple storage class containing the basic attributes of
+/// a TSIG Key.  It is intended primarily as a reference for working with
+/// actual keys and may eventually be replaced by isc::dns::TSIGKey.  TSIG Key
+/// functionality at this stage is strictly limited to configuration parsing.
+/// @todo full functionality for using TSIG during DNS updates will be added
+/// in a future release.
+class TSIGKeyInfo {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param name the unique label used to identify this key
+    /// @param algorithm the name of the encryption alogirthm this key uses.
+    /// (@todo This will be a fixed list of choices)
+    ///
+    /// @param secret the secret component of this key
+    TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+                const std::string& secret);
+
+    /// @brief Destructor
+    virtual ~TSIGKeyInfo();
+
+    /// @brief Getter which returns the key's name.
+    ///
+    /// @return returns the name as as std::string.
+    const std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Getter which returns the key's algorithm.
+    ///
+    /// @return returns the algorithm as as std::string.
+    const std::string getAlgorithm() const {
+        return (algorithm_);
+    }
+
+    /// @brief Getter which returns the key's secret.
+    ///
+    /// @return returns the secret as as std::string.
+    const std::string getSecret() const {
+        return (secret_);
+    }
+
+private:
+    /// @brief The name of the key.
+    ///
+    /// This value is the unique identifeir thay domains use to
+    /// to specify which TSIG key they need.
+    std::string name_;
+
+    /// @brief The algorithm that should be used for this key.
+    std::string algorithm_;
+
+    /// @brief The secret value component of this key.
+    std::string secret_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo {
+public:
+
+    /// @brief defines DNS standard port value
+    static const uint32_t STANDARD_DNS_PORT = 53;
+
+    /// @brief defines an "empty" string version of an ip address.
+    static const char* EMPTY_IP_STR;
+
+
+    /// @brief Constructor
+    ///
+    /// @param hostname is the resolvable name of the server. If not blank,
+    /// then the server address should be resolved at runtime.
+    /// @param ip_address is the static IP address of the server. If hostname
+    /// is blank, then this address should be used to connect to the server.
+    /// @param port is the port number on which the server listens.
+    /// primarily meant for testing purposes.  Normally, DNS traffic is on
+    /// is port 53. (NOTE the constructing code is responsible for setting
+    /// the default.)
+    /// @param enabled is a flag that indicates whether this server is
+    /// enabled for use. It defaults to true.
+    DnsServerInfo(const std::string& hostname,
+                  isc::asiolink::IOAddress ip_address,
+                  uint32_t port = STANDARD_DNS_PORT,
+                  bool enabled=true);
+
+    /// @brief Destructor
+    virtual ~DnsServerInfo();
+
+    /// @brief Getter which returns the server's hostname.
+    ///
+    /// @return returns the hostname as as std::string.
+    const std::string getHostname() const {
+        return (hostname_);
+    }
+
+    /// @brief Getter which returns the server's port number.
+    ///
+    /// @return returns the port number as a unsigned integer.
+    uint32_t getPort() const {
+        return (port_);
+    }
+
+    /// @brief Getter which returns the server's ip_address.
+    ///
+    /// @return returns the address as an IOAddress reference.
+    const isc::asiolink::IOAddress& getIpAddress() const {
+        return (ip_address_);
+    }
+
+    /// @brief Convenience method which returns whether or not the
+    /// server is enabled.
+    ///
+    /// @return returns true if the server is enabled, false otherwise.
+    bool isEnabled() const {
+        return (enabled_);
+    }
+
+    /// @brief Sets the server's enabled flag to true.
+    void enable() {
+        enabled_ = true;
+    }
+
+    /// @brief Sets the server's enabled flag to false.
+    void disable() {
+        enabled_ = false;
+    }
+
+    /// @brief Returns a text representation for the server.
+    std::string toText() const;
+
+
+private:
+    /// @brief The resolvable name of the server. If not blank, then the
+    /// server's IP address should be dynamically resolved at runtime.
+    std::string hostname_;
+
+    /// @brief The static IP address of the server. When hostname is blank,
+    /// then this address should be used to connect to the server.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief The port number on which the server listens for DNS traffic.
+    uint32_t port_;
+
+    /// @param enabled is a flag that indicates whether this server is
+    /// enabled for use. It defaults to true.
+    bool enabled_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const DnsServerInfo& server);
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it.  It's primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just an std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
+class DdnsDomain {
+public:
+    /// @brief Constructor
+    ///
+    /// @param name is the domain name of the domain.
+    /// @param key_name is the TSIG key name for use with this domain.
+    /// @param servers is the list of server(s) supporting this domain.
+    DdnsDomain(const std::string& name, const std::string& key_name,
+               DnsServerInfoStoragePtr servers);
+
+    /// @brief Destructor
+    virtual ~DdnsDomain();
+
+    /// @brief Getter which returns the domain's name.
+    ///
+    /// @return returns the name in an std::string.
+    const std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Getter which returns the domain's TSIG key name.
+    ///
+    /// @return returns the key name in an std::string.
+    const std::string getKeyName() const {
+        return (key_name_);
+    }
+
+    /// @brief Getter which returns the domain's list of servers.
+    ///
+    /// @return returns the pointer to the server storage.
+    const DnsServerInfoStoragePtr& getServers() {
+        return (servers_);
+    }
+
+private:
+    /// @brief The domain name of the domain.
+    std::string name_;
+
+    /// @brief The name of the TSIG key for use with this domain.
+    std::string key_name_;
+
+    /// @brief The list of server(s) supporting this domain.
+    DnsServerInfoStoragePtr servers_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services.  These services are used to match a FQDN to a domain.  Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified.  The wild card domain is
+/// specified as a domain whose name is "*".  The wild card domain will match
+/// any entry and is provided for flexibility in FQDNs  If for instance, all
+/// forward requests are handled by the same servers, the configuration could
+/// specify the wild card domain as the only forward domain. All forward DNS
+/// updates would be sent to that one list of servers, regardless of the FQDN.
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr {
+public:
+    /// @brief defines the domain name for denoting the wildcard domain.
+    static const char* wildcard_domain_name_;
+
+    /// @brief Constructor
+    ///
+    /// @param name is an arbitrary label assigned to this manager.
+    DdnsDomainListMgr(const std::string& name);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainListMgr ();
+
+    /// @brief Matches a given name to a domain based on a longest match
+    /// scheme.
+    ///
+    /// Given a FQDN, search the list of domains, successively removing a
+    /// sub-domain from the FQDN until a match is found.  If no match is found
+    /// and the wild card domain is present in the list, then return it as the
+    /// match.  If the wild card domain is the only domain in the list, then
+    /// it will be returned immediately for any FQDN.
+    ///
+    /// @param fqdn is the name for which to look.
+    /// @param domain receives the matching domain. If no match is found its
+    /// contents will be unchanged.
+    ///
+    /// @return returns true if a match is found, false otherwise.
+    /// @todo This is a very basic match method, which expects valid FQDNs
+    /// both as input and for the DdnsDomain::getName().  Currently both are
+    /// simple strings and there is no normalization (i.e. added trailing dots
+    /// if missing).
+    virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+    /// @brief Fetches the manager's name.
+    ///
+    /// @return returns a std::string containing the name of the manager.
+    const std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Returns the number of domains in the domain list.
+    ///
+    /// @brief returns an unsigned int containing the domain count.
+    uint32_t size() const {
+        return (domains_->size());
+    }
+
+    /// @brief Fetches the wild card domain.
+    ///
+    /// @return returns a pointer reference to the domain.  The pointer will
+    /// empty if the wild card domain is not present.
+    const DdnsDomainPtr& getWildcardDomain() {
+        return (wildcard_domain_);
+    }
+
+    /// @brief Fetches the domain list.
+    ///
+    /// @return returns a pointer reference to the list of domains.
+    const DdnsDomainMapPtr &getDomains() {
+        return (domains_);
+    }
+
+    /// @brief Sets the manger's domain list to the given list of domains.
+    /// This method will scan the inbound list for the wild card domain and
+    /// set the internal wild card domain pointer accordingly.
+    void setDomains(DdnsDomainMapPtr domains);
+
+private:
+    /// @brief An arbitrary label assigned to this manager.
+    std::string name_;
+
+    /// @brief Map of the domains, keyed by name.
+    DdnsDomainMapPtr domains_;
+
+    /// @brief Pointer to the wild card domain.
+    DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map").  It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public DCfgContextBase {
+public:
+
+    /// @brief Constructor
+    DScalarContext() {
+    };
+
+    /// @brief Destructor
+    virtual ~DScalarContext() {
+    }
+
+    /// @brief Creates a clone of a DStubContext.
+    ///
+    /// @return returns a pointer to the new clone.
+    virtual DCfgContextBasePtr clone() {
+        return (DCfgContextBasePtr(new DScalarContext(*this)));
+    }
+
+protected:
+    /// @brief Copy constructor
+    DScalarContext(const DScalarContext& rhs) : DCfgContextBase(rhs) {
+    }
+
+private:
+    /// @brief Private assignment operator, not implemented.
+    DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for  TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig_key" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public  isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition. Since servers are specified in a list this value is likely
+    /// be something akin to "key:0", set during parsing.
+    /// @param keys is a pointer to the storage area to which the parser
+    /// should commit the newly created TSIGKeyInfo instance.
+    TSIGKeyInfoParser(const std::string& entry_name, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~TSIGKeyInfoParser();
+
+    /// @brief Performs the actual parsing of the given  "tsig_key" element.
+    ///
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param key_config is the "tsig_key" configuration to parse
+    virtual void build(isc::data::ConstElementPtr key_config);
+
+    /// @brief Creates a parser for the given "tsig_key" member element id.
+    ///
+    /// The key elements currently supported are(see dhcp-ddns.spec):
+    ///   1. name
+    ///   2. algorithm
+    ///   3. secret
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "tsig_key" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Instantiates a DnsServerInfo from internal data values
+    /// saves it to the storage area pointed to by servers_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    /// Since servers are specified in a list this value is likely be something
+    /// akin to "key:0", set during parsing.  Primarily here for diagnostics.
+    std::string entry_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the newly created TSIGKeyInfo instance. This is given to us as a
+    /// constructor argument by an upper level.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig_key" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The TSIGKeyInfo instances are added
+/// to the given storage upon commit.
+class TSIGKeyInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param list_name is an arbitrary label assigned to this parser instance.
+    /// @param keys is a pointer to the storage area to which the parser
+    /// should commit the newly created TSIGKeyInfo instance.
+    TSIGKeyInfoListParser(const std::string& list_name, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~TSIGKeyInfoListParser();
+
+    /// @brief Performs the parsing of the given list "tsig_key" elements.
+    ///
+    /// It iterates over each key entry in the list:
+    ///   1. Instantiate a TSIGKeyInfoParser for the entry
+    ///   2. Pass the element configuration to the parser's build method
+    ///   3. Add the parser instance to local storage
+    ///
+    /// The net effect is to parse all of the key entries in the list
+    /// prepping them for commit.
+    ///
+    /// @param key_list_config is the list of "tsig_key" elements to parse.
+    virtual void build(isc::data::ConstElementPtr key_list_config);
+
+    /// @brief Iterates over the internal list of TSIGKeyInfoParsers,
+    /// invoking commit on each.  This causes each parser to instantiate a
+    /// TSIGKeyInfo from its internal data values and add that key
+    /// instance to the local key storage area, local_keys_.   If all of the
+    /// key parsers commit cleanly, then update the context key map (keys_)
+    /// with the contents of local_keys_.  This is done to allow for duplicate
+    /// key detection while parsing the keys, but not get stumped by it
+    /// updating the context with a valid list.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string list_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the list of newly created TSIGKeyInfo instances. This is given to us
+    /// as a constructor argument by an upper level.
+    TSIGKeyInfoMapPtr keys_;
+    
+    /// @brief Local storage area to which individual key parsers commit.
+    TSIGKeyInfoMapPtr local_keys_;
+
+    /// @brief Local storage of TSIGKeyInfoParser instances
+    isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for  DnsServerInfo
+///
+/// This class parses the configuration element "dns_server" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public  isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition. Since servers are specified in a list this value is likely
+    /// be something akin to "server:0", set during parsing.
+    /// @param servers is a pointer to the storage area to which the parser
+    /// should commit the newly created DnsServerInfo instance.
+    DnsServerInfoParser(const std::string& entry_name,
+                        DnsServerInfoStoragePtr servers);
+
+    /// @brief Destructor
+    virtual ~DnsServerInfoParser();
+
+    /// @brief Performs the actual parsing of the given  "dns_server" element.
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param server_config is the "dns_server" configuration to parse
+    virtual void build(isc::data::ConstElementPtr server_config);
+
+    /// @brief Creates a parser for the given "dns_server" member element id.
+    ///
+    /// The server elements currently supported are(see dhcp-ddns.spec):
+    ///   1. hostname
+    ///   2. ip_address
+    ///   3. port
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "dns_server" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Instantiates a DnsServerInfo from internal data values
+    /// saves it to the storage area pointed to by servers_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    /// Since servers are specified in a list this value is likely be something
+    /// akin to "server:0", set during parsing.  Primarily here for diagnostics.
+    std::string entry_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the newly created DnsServerInfo instance. This is given to us as a
+    /// constructor argument by an upper level.
+    DnsServerInfoStoragePtr servers_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns_server" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param list_name is an arbitrary label assigned to this parser instance.
+    /// @param servers is a pointer to the storage area to which the parser
+    /// should commit the newly created DnsServerInfo instance.
+    DnsServerInfoListParser(const std::string& list_name,
+                            DnsServerInfoStoragePtr servers);
+
+    /// @brief Destructor
+    virtual ~DnsServerInfoListParser();
+
+    /// @brief Performs the actual parsing of the given list "dns_server"
+    /// elements.
+    /// It iterates over each server entry in the list:
+    ///   1. Instantiate a DnsServerInfoParser for the entry
+    ///   2. Pass the element configuration to the parser's build method
+    ///   3. Add the parser instance to local storage
+    ///
+    /// The net effect is to parse all of the server entries in the list
+    /// prepping them for commit.
+    ///
+    /// @param server_list_config is the list of "dns_server" elements to parse.
+    virtual void build(isc::data::ConstElementPtr server_list_config);
+
+    /// @brief Iterates over the internal list of DnsServerInfoParsers,
+    /// invoking commit on each.  This causes each parser to instantiate a
+    /// DnsServerInfo from its internal data values and add that that server
+    /// instance to the storage area, servers_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string list_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the list of newly created DnsServerInfo instances. This is given to us
+    /// as a constructor argument by an upper level.
+    DnsServerInfoStoragePtr servers_;
+
+    /// @brief Local storage of DnsServerInfoParser instances
+    isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for  DdnsDomain
+///
+/// This class parses the configuration element "ddns_domain" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition. Since domains are specified in a list this value is likely
+    /// be something akin to "forward_ddns:0", set during parsing.
+    /// @param domains is a pointer to the storage area to which the parser
+    /// @param keys is a pointer to a map of the defined TSIG keys.
+    /// should commit the newly created DdnsDomain instance.
+    DdnsDomainParser(const std::string& entry_name, DdnsDomainMapPtr domains,
+                     TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainParser();
+
+    /// @brief Performs the actual parsing of the given  "ddns_domain" element.
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param domain_config is the "ddns_domain" configuration to parse
+    virtual void build(isc::data::ConstElementPtr domain_config);
+
+    /// @brief Creates a parser for the given "ddns_domain" member element id.
+    ///
+    /// The domain elements currently supported are(see dhcp-ddns.spec):
+    ///   1. name
+    ///   2. key_name
+    ///   3. dns_servers
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the "ddns_domain" specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Instantiates a DdnsDomain from internal data values
+    /// saves it to the storage area pointed to by domains_.
+    virtual void commit();
+
+private:
+
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string entry_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the newly created DdnsDomain instance. This is given to us as a
+    /// constructor argument by an upper level.
+    DdnsDomainMapPtr domains_;
+
+    /// @brief Pointer to the map of defined TSIG keys.
+    /// This map is passed into us and contains all of the TSIG keys defined
+    /// for this configuration.  It is used to validate the key name entry of
+    /// DdnsDomains that specify one.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage for DnsServerInfo instances. This is passed into
+    /// DnsServerInfoListParser(s), which in turn passes it into each
+    /// DnsServerInfoParser.  When the DnsServerInfoParsers "commit" they add
+    /// their server instance to this storage.
+    DnsServerInfoStoragePtr local_servers_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns_domain" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DdnsDomain instances are added
+/// to the given storage upon commit.
+class DdnsDomainListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param list_name is an arbitrary label assigned to this parser instance.
+    /// @param domains is a pointer to the storage area to which the parser
+    /// @param keys is a pointer to a map of the defined TSIG keys.
+    /// should commit the newly created DdnsDomain instance.
+    DdnsDomainListParser(const std::string& list_name,
+                         DdnsDomainMapPtr domains, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainListParser();
+
+    /// @brief Performs the actual parsing of the given list "ddns_domain"
+    /// elements.
+    /// It iterates over each server entry in the list:
+    ///   1. Instantiate a DdnsDomainParser for the entry
+    ///   2. Pass the element configuration to the parser's build method
+    ///   3. Add the parser instance to local storage
+    ///
+    /// The net effect is to parse all of the domain entries in the list
+    /// prepping them for commit.
+    ///
+    /// @param domain_list_config is the list of "ddns_domain" elements to
+    /// parse.
+    virtual void build(isc::data::ConstElementPtr domain_list_config);
+
+    /// @brief Iterates over the internal list of DdnsDomainParsers, invoking
+    /// commit on each.  This causes each parser to instantiate a DdnsDomain
+    /// from its internal data values and add that domain instance to the
+    /// storage area, domains_.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string list_name_;
+
+    /// @brief Pointer to the storage area to which the parser should commit
+    /// the list of newly created DdnsDomain instances. This is given to us
+    /// as a constructor argument by an upper level.
+    DdnsDomainMapPtr domains_;
+
+    /// @brief Pointer to the map of defined TSIG keys.
+    /// This map is passed into us and contains all of the TSIG keys defined
+    /// for this configuration.  It is used to validate the key name entry of
+    /// DdnsDomains that specify one.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage of DdnsDomainParser instances
+    isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward_ddns" and
+/// "reverse_ddns" as defined in src/bin/d2/dhcp-ddns.spec.  It populates the
+/// given DdnsDomainListMgr with parsed information upon commit.  Note that
+/// unlike other parsers, this parser does NOT instantiate the final object
+/// during the commit phase, it populates it.  It must pre-exist.
+class DdnsDomainListMgrParser : public isc::dhcp::DhcpConfigParser {
+public:
+    /// @brief Constructor
+    ///
+    /// @param entry_name is an arbitrary label assigned to this configuration
+    /// definition.
+    /// @param mgr is a pointer to the DdnsDomainListMgr to populate.
+    /// @param keys is a pointer to a map of the defined TSIG keys.
+    /// @throw throws D2CfgError if mgr pointer is empty.
+    DdnsDomainListMgrParser(const std::string& entry_name,
+                     DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys);
+
+    /// @brief Destructor
+    virtual ~DdnsDomainListMgrParser();
+
+    /// @brief Performs the actual parsing of the given manager element.
+    /// The results of the parsing are retained internally for use during
+    /// commit.
+    ///
+    /// @param mgr_config is the manager configuration to parse
+    virtual void build(isc::data::ConstElementPtr mgr_config);
+
+    /// @brief Creates a parser for the given manager member element id.
+    ///
+    /// The manager elements currently supported are (see dhcp-ddns.spec):
+    ///     1. ddns_domains
+    ///
+    /// @param config_id is the "item_name" for a specific member element of
+    /// the manager specification.
+    ///
+    /// @return returns a pointer to newly created parser.
+    virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+                                                    config_id);
+
+    /// @brief Populates the DdnsDomainListMgr from internal data values
+    /// set during parsing.
+    virtual void commit();
+
+private:
+    /// @brief Arbitrary label assigned to this parser instance.
+    std::string entry_name_;
+
+    /// @brief Pointer to manager instance to which the parser should commit
+    /// the parsed data. This is given to us as a constructor argument by an
+    /// upper level.
+    DdnsDomainListMgrPtr mgr_;
+
+    /// @brief Pointer to the map of defined TSIG keys.
+    /// This map is passed into us and contains all of the TSIG keys defined
+    /// for this configuration.  It is used to validate the key name entry of
+    /// DdnsDomains that specify one.
+    TSIGKeyInfoMapPtr keys_;
+
+    /// @brief Local storage for DdnsDomain instances. This is passed into a
+    /// DdnsDomainListParser(s), which in turn passes it into each
+    /// DdnsDomainParser.  When the DdnsDomainParsers "commit" they add their
+    /// domain instance to this storage.
+    DdnsDomainMapPtr local_domains_;
+
+    /// @brief Local storage area for scalar parameter values. Use to hold
+    /// data until time to commit.
+    /// @todo Currently, the manager has no scalars but this is likely to
+    /// change as matching capabilities expand.
+    DScalarContext local_scalars_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CONFIG_H

+ 68 - 0
src/bin/d2/d2_controller.cc

@@ -0,0 +1,68 @@
+// Copyright (C) 2013  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 <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/spec_config.h>
+
+#include <stdlib.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the application name, this is passed into base class
+/// and appears in log statements.
+const char* D2Controller::d2_app_name_ = "DHCP-DDNS";
+
+/// @brief Defines the executable name. This is passed into the base class
+/// by convention this should match the BIND10 module name.
+const char* D2Controller::d2_bin_name_ = "b10-dhcp-ddns";
+
+DControllerBasePtr&
+D2Controller::instance() {
+    // If the instance hasn't been created yet, create it.  Note this method
+    // must use the base class singleton instance methods.  The base class
+    // must have access to the singleton in order to use it within BIND10
+    // static function callbacks.
+    if (!getController()) {
+        DControllerBasePtr controller_ptr(new D2Controller());
+        setController(controller_ptr);
+    }
+
+    return (getController());
+}
+
+DProcessBase* D2Controller::createProcess() {
+    // Instantiate and return an instance of the D2 application process. Note
+    // that the process is passed the controller's io_service.
+    return (new D2Process(getAppName().c_str(), getIOService()));
+}
+
+D2Controller::D2Controller()
+    : DControllerBase(d2_app_name_, d2_bin_name_) {
+    // set the BIND10 spec file either from the environment or
+    // use the production value.
+    if (getenv("B10_FROM_BUILD")) {
+        setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
+            "/src/bin/d2/dhcp-ddns.spec");
+    } else {
+        setSpecFileName(D2_SPECFILE_LOCATION);
+    }
+}
+
+D2Controller::~D2Controller() {
+}
+
+}; // end namespace isc::d2
+}; // end namespace isc

+ 72 - 0
src/bin/d2/d2_controller.h

@@ -0,0 +1,72 @@
+// Copyright (C) 2013  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 D2_CONTROLLER_H
+#define D2_CONTROLLER_H
+
+#include <d2/d_controller.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Process Controller for D2 Process
+/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
+/// creates and manages an instance of the DHCP-DDNS application process,
+/// D2Process.
+/// @todo Currently, this class provides only the minimum required specialized
+/// behavior to run the DHCP-DDNS service. It may very well expand as the
+/// service implementation evolves.  Some thought was given to making
+/// DControllerBase a templated class but the labor savings versus the
+/// potential number of virtual methods which may be overridden didn't seem
+/// worth the clutter at this point.
+class D2Controller : public DControllerBase {
+public:
+    /// @brief Static singleton instance method. This method returns the
+    /// base class singleton instance member.  It instantiates the singleton
+    /// and sets the base class instance member upon first invocation.
+    ///
+    /// @return returns the pointer reference to the singleton instance.
+    static DControllerBasePtr& instance();
+
+    /// @brief Destructor.
+    virtual ~D2Controller();
+
+    /// @brief Defines the application name, this is passed into base class
+    /// and appears in log statements.
+    static const char* d2_app_name_;
+
+    /// @brief Defines the executable name. This is passed into the base class
+    /// by convention this should match the BIND10 module name.
+    static const char* d2_bin_name_;
+
+private:
+    /// @brief Creates an instance of the DHCP-DDNS specific application
+    /// process.  This method is invoked during the process initialization
+    /// step of the controller launch.
+    ///
+    /// @return returns a DProcessBase* to the application process created.
+    /// Note the caller is responsible for destructing the process. This
+    /// is handled by the base class, which wraps this pointer with a smart
+    /// pointer.
+    virtual DProcessBase* createProcess();
+
+    /// @brief Constructor is declared private to maintain the integrity of
+    /// the singleton instance.
+    D2Controller();
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

+ 27 - 0
src/bin/d2/d2_log.cc

@@ -0,0 +1,27 @@
+// Copyright (C) 2013  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.
+
+/// Defines the logger used by the top-level component of b10-d2.
+
+#include <d2/d2_log.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the logger used within D2.
+isc::log::Logger dctl_logger("dhcpddns");
+
+} // namespace d2
+} // namespace isc
+

+ 32 - 0
src/bin/d2/d2_log.h

@@ -0,0 +1,32 @@
+// Copyright (C) 2013  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 D2_LOG_H
+#define D2_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <d2/d2_messages.h>
+
+namespace isc {
+namespace d2 {
+
+/// Define the logger for the "d2" logging.
+extern isc::log::Logger dctl_logger;
+
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_LOG_H

+ 449 - 0
src/bin/d2/d2_messages.mes

@@ -0,0 +1,449 @@
+# Copyright (C) 2013-2014  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.
+
+$NAMESPACE isc::d2
+
+% DCTL_CCSESSION_ENDING %1 ending control channel session
+This debug message is issued just before the controller attempts
+to disconnect from its session with the BIND10 control channel.
+
+% DCTL_CCSESSION_STARTING %1 starting control channel session, specfile: %2
+This debug message is issued just before the controller attempts
+to establish a session with the BIND10 control channel.
+
+% DCTL_COMMAND_RECEIVED %1 received command: %2, arguments: %3
+A debug message listing the command (and possible arguments) received
+from the BIND10 control system by the controller.
+
+% DCTL_CONFIG_COMPLETE 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.
+
+% DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
+This critical error message indicates that the initial application
+configuration has failed. The service will start, but will not
+process requests until the configuration has been corrected.
+
+% DCTL_CONFIG_START parsing new configuration: %1
+A debug message indicating that the application process has received an
+updated configuration and has passed it to its configuration manager
+for parsing.
+
+% DCTL_CONFIG_STUB %1 configuration stub handler called
+This debug message is issued when the dummy handler for configuration
+events is called.  This only happens during initial startup.
+
+% DCTL_CONFIG_UPDATE %1 updated configuration received: %2
+A debug message indicating that the controller has received an
+updated configuration from the BIND10 configuration system.
+
+% DCTL_DISCONNECT_FAIL %1 controller failed to end session with BIND10: %2
+This message indicates that while shutting down, the Dhcp-Ddns controller
+encountered an error terminating communication with the BIND10. The service
+will still exit.  While theoretically possible, this situation is rather
+unlikely.
+
+% DCTL_INIT_PROCESS %1 initializing the application
+This debug message is issued just before the controller attempts
+to create and initialize its application instance.
+
+% DCTL_INIT_PROCESS_FAIL %1 application initialization failed: %2
+This error message is issued if the controller could not initialize the
+application and will exit.
+
+% DCTL_NOT_RUNNING %1 application instance is not running
+A warning message is issued when an attempt is made to shut down the
+application when it is not running.
+
+% DCTL_ORDER_ERROR configuration contains more elements than the parsing order
+An error message which indicates that configuration being parsed includes
+element ids not specified the configuration manager's parse order list. This
+is a programmatic error.
+
+% DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
+An error message output during a configuration update.  The program is
+expecting an item but has not found it in the new configuration.  This may
+mean that the BIND 10 configuration database is corrupt.
+
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
+On receipt of message containing details to a change of its configuration,
+the server failed to create a parser to decode the contents of the named
+configuration element, or the creation succeeded but the parsing actions
+and committal of changes failed.  The reason for the failure is given in
+the message.
+
+% DCTL_PROCESS_FAILED %1 application execution failed: %2
+The controller has encountered a fatal error while running the
+application and is terminating. The reason for the failure is
+included in the message.
+
+% DCTL_RUN_PROCESS %1 starting application event loop
+This debug message is issued just before the controller invokes
+the application run method.
+
+% DCTL_SESSION_FAIL %1 controller failed to establish BIND10 session: %1
+The controller has failed to establish communication with the rest of BIND
+10 and will exit.
+
+% DCTL_STANDALONE %1 skipping message queue, running standalone
+This is a debug message indicating that the controller is running in the
+application in standalone mode. This means it will not connected to the BIND10
+message queue. Standalone mode is only useful during program development,
+and should not be used in a production environment.
+
+% DCTL_STARTING %1 controller starting, pid: %2
+This is an informational message issued when controller for the
+service first starts.
+
+% DCTL_STOPPING %1 controller is exiting
+This is an informational message issued when the controller is exiting
+following a shut down (normal or otherwise) of the service.
+
+% DHCP_DDNS_AT_MAX_TRANSACTIONS application has %1 queued requests but has reached maximum number of %2 concurrent transactions
+This is a debug message that indicates that the application has DHCP_DDNS
+requests in the queue but is working as many concurrent requests as allowed.
+
+% DHCP_DDNS_CLEARED_FOR_SHUTDOWN application has met shutdown criteria for shutdown type: %1
+This is an informational message issued when the application has been instructed
+to shutdown and has met the required criteria to exit.
+
+% DHCP_DDNS_COMMAND command directive received, command: %1 - args: %2
+This is a debug message issued when the Dhcp-Ddns application command method
+has been invoked.
+
+% DHCP_DDNS_CONFIGURE configuration update received: %1
+This is a debug message issued when the Dhcp-Ddns application configure method
+has been invoked.
+
+% DHCP_DDNS_FAILED application experienced a fatal error: %1
+This is a debug message issued when the Dhcp-Ddns application encounters an
+unrecoverable error from within the event loop.
+
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an
+error while decoding a response to DNS Update message. Typically, this error
+will be encountered when a response message is malformed.
+
+% DHCP_DDNS_NO_ELIGIBLE_JOBS although there are queued requests, there are pending transactions for each Queue count: %1  Transaction count: %2
+This is a debug message issued when all of the queued requests represent clients
+for which there is a an update already in progress.  This may occur under
+normal operations but should be temporary situation.
+
+% DHCP_DDNS_NO_FWD_MATCH_ERROR the configured list of forward DDNS domains does not contain a match for FQDN %1  The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update a the forward DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration.  Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN).  The DNS Update
+request for the FQDN cannot be processed.
+
+% DHCP_DDNS_NO_REV_MATCH_ERROR the configured list of reverse DDNS domains does not contain a match for FQDN %1  The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update a the reverse DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration.  Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_PROCESS_INIT application init invoked
+This is a debug message issued when the Dhcp-Ddns application enters
+its init method.
+
+% DHCP_DDNS_QUEUE_MGR_QUEUE_FULL application request queue has reached maximum number of entries %1
+This an error message indicating that DHCP-DDNS is receiving DNS update
+requests faster than they can be processed.  This may mean the maximum queue
+needs to be increased, the DHCP-DDNS clients are simply generating too many
+requests too quickly, or perhaps upstream DNS servers are experiencing
+load issues.
+
+% DHCP_DDNS_QUEUE_MGR_RECONFIGURING application is reconfiguring the queue manager
+This is an informational message indicating that DHCP_DDNS is reconfiguring the
+queue manager as part of normal startup or in response to a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RECOVERING application is attempting to recover from a
+queue manager IO error
+This is an informational message indicating that DHCP_DDNS is attempting to
+restart the queue manager after it suffered an IO error while receiving
+requests.
+
+% DHCP_DDNS_QUEUE_MGR_RECV_ERROR application's queue manager was notified of a request receive error by its listener.
+This is an error message indicating that the NameChangeRequest listener used by
+DHCP-DDNS to receive requests encountered a IO error.  There should be
+corresponding log messages from the listener layer with more details. This may
+indicate a network connectivity or system resource issue.
+
+% DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be restarted after stopping due to a full receive queue.  This means that
+the application cannot receive requests. This is most likely due to DHCP_DDNS
+configuration parameters referring to resources such as an IP address or port,
+that is no longer unavailable.  DHCP_DDNS will attempt to restart the queue
+manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RESUMING application is resuming listening for requests now that the request queue size has reached %1 of a maximum %2 allowed
+This is an informational message indicating that DHCP_DDNS, which had stopped
+accepting new requests, has processed enough entries from the receive queue to
+resume accepting requests.
+
+% DHCP_DDNS_QUEUE_MGR_STARTED application's queue manager has begun listening for requests.
+This is a debug message indicating that DHCP_DDNS's Queue Manager has
+successfully started and is now listening for NameChangeRequests.
+
+% DHCP_DDNS_QUEUE_MGR_START_ERROR application could not start the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be started.  This means that the application cannot receive requests. This is
+most likely due to DHCP_DDNS configuration parameters referring to resources
+such as an IP address or port, that are unavailable.  DHCP_DDNS will attempt to
+restart the queue manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPED application's queue manager has stopped listening for requests.
+This is an informational message indicating that DHCP_DDNS's Queue Manager has
+stopped listening for NameChangeRequests.  This may be because of normal event
+such as reconfiguration or as a result of an error.  There should be log
+messages preceding this one to indicate why it has stopped.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPING application is stopping the queue manager for %1
+This is an informational message indicating that DHCP_DDNS is stopping the
+queue manager either to reconfigure it or as part of application shutdown.
+
+% DHCP_DDNS_QUEUE_MGR_STOP_ERROR application encountered an error stopping the queue manager: %1
+This is an error message indicating that DHCP_DDNS encountered an error while
+trying to stop the queue manager.  This error is unlikely to occur or to
+impair the application's ability to function but it should be reported for
+analysis.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR application's queue manager request receive handler experienced an unexpected exception %1:
+This is an error message indicating that an unexpected error occurred within the
+DHCP_DDNS's Queue Manager request receive completion handler. This is most
+likely a programmatic issue that should be reported.  The application may
+recover on its own.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP application's queue manager receive was
+aborted unexpectedly while queue manager state is: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager request
+receive was unexpected interrupted.  Normally, the read is receive is only
+interrupted as a normal part of stopping the queue manager.  This is most
+likely a programmatic issue that should be reported.
+
+% DHCP_DDNS_RUN_ENTER application has entered the event loop
+This is a debug message issued when the Dhcp-Ddns application enters
+its run method.
+
+% DHCP_DDNS_RUN_EXIT application is exiting the event loop
+This is a debug message issued when the Dhcp-Ddns exits the
+in event loop.
+
+% DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1
+This is informational message issued when the application has been instructed
+to shut down by the controller.
+
+% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
+This is error message issued when the application fails to process a
+NameChangeRequest correctly. Some or all of the DNS updates requested as part
+of this update did not succeed. This is a programmatic error and should be
+reported.
+
+% DHCP_DDNS_FORWARD_ADD_REJECTED DNS Server, %1, rejected a DNS update request to add the address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_ADD_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping add for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while adding forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to add a forward address mapping,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while adding a forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was adding a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping replace for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a forward address mapping,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while replacing forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REPLACE_REJECTED DNS Server, %1, rejected a DNS update request to replace the reverse mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REPLACE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping replacement for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while replacing reverse address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a reverse address,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while replacing reverse address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a reverse address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_TRANS_SEND_ERROR application encountered an unexpected error while attempting to send a DNS update: %1
+This is error message issued when the application is able to construct an update
+message but the attempt to send it suffered a unexpected error. This is most
+likely a programmatic error, rather than a communications issue. Some or all
+of the DNS updates requested as part of this request did not succeed.
+
+% DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE DNS udpate message to add a forward DNS entry could not be constructed for this request: %1, reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address addition.  This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.
+This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE DNS update message to replace a forward DNS entry could not be constructed from this request: %1, reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address replacement.  This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE DNS update message to replace a reverse DNS entry could not be constructed from this request: %1, reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR replacement.  This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_ADD_SUCCEEDED DHCP_DDNS successfully added the DNS mapping addition for this request: %1
+This is a debug message issued after DHCP_DDNS has submitted DNS mapping
+additions which were received and accepted by an appropriate DNS server.
+
+% DHCP_DDNS_ADD_FAILED DHCP_DDNS failed attempting to make DNS mapping additions for this request: %1, event: %2
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry additions have failed.  The precise reason for the failure should be
+documented in preceding log entries.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE DNS udpate message to remove a forward DNS Address entry could not be constructed for this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address (A or AAAA) removal.  This
+is due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED DNS Server, %1, rejected a DNS update request to remove the forward address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping address removal for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address remove.  The application will retry
+against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing a forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE DNS udpate message to remove forward DNS RR entries could not be constructed for this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting forward RR (DHCID RR) removal.  This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED DNS Server, %1, rejected a DNS update request to remove forward RR entries for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward RR removal for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward RR remove.  The application will retry
+against the same server.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward RRs for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove forward RRs mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing forward RRs for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing forward RRs.  The request will be aborted. This is
+most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE DNS update message to remove a reverse DNS entry could not be constructed from this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR removal.  This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_REVERSE_REMOVE_REJECTED DNS Server, %1, rejected a DNS update request to remove the reverse mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REMOVE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping remove for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing reverse address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a reverse address,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing reverse address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a reverse address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REMOVE_SUCCEEDED DHCP_DDNS successfully removed the DNS mapping addition for this request: %1
+This is a debug message issued after DHCP_DDNS has submitted DNS mapping
+removals which were received and accepted by an appropriate DNS server.
+
+% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS failed attempting to make DNS mapping removals for this request: %1, event: %2
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry removals have failed.  The precise reason for the failure should be
+documented in preceding log entries.
+
+% DHCP_DDNS_STARTING_TRANSACTION Transaction Key: %1
+
+% DHCP_DDNS_UPDATE_REQUEST_SENT for transaction key: %1 to server: %2
+This is a debug message issued when DHCP_DDNS sends a DNS request to a DNS
+server.
+
+% DHCP_DDNS_UPDATE_RESPONSE_RECEIVED for transaction key: %1  to server: %2 status: %3
+This is a debug message issued when DHCP_DDNS receives sends a DNS update
+response from a DNS server.

+ 394 - 0
src/bin/d2/d2_process.cc

@@ -0,0 +1,394 @@
+// Copyright (C) 2013  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 <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/d2_process.h>
+
+#include <asio.hpp>
+
+namespace isc {
+namespace d2 {
+
+// Setting to 80% for now. This is an arbitrary choice and should probably
+// be configurable.
+const unsigned int D2Process::QUEUE_RESTART_PERCENT =  80;
+
+D2Process::D2Process(const char* name, IOServicePtr io_service)
+    : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())),
+     reconf_queue_flag_(false), shutdown_type_(SD_NORMAL) {
+
+    // Instantiate queue manager.  Note that queue manager does not start
+    // listening at this point.  That can only occur after configuration has
+    // been received.  This means that until we receive the configuration,
+    // D2 will neither receive nor process NameChangeRequests.
+    // Pass in IOService for NCR IO event processing.
+    queue_mgr_.reset(new D2QueueMgr(getIoService()));
+
+    // Instantiate update manager.
+    // Pass in both queue manager and configuration manager.
+    // Pass in IOService for DNS update transaction IO event processing.
+    D2CfgMgrPtr tmp = getD2CfgMgr();
+    update_mgr_.reset(new D2UpdateMgr(queue_mgr_,  tmp,  getIoService()));
+};
+
+void
+D2Process::init() {
+};
+
+void
+D2Process::run() {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DHCP_DDNS_RUN_ENTER);
+    // Loop forever until we are allowed to shutdown.
+    while (!canShutdown()) {
+        try {
+            // Check on the state of the request queue. Take any
+            // actions necessary regarding it.
+            checkQueueStatus();
+
+            // Give update manager a time slice to queue new jobs and
+            // process finished ones.
+            update_mgr_->sweep();
+
+            // Wait on IO event(s)  - block until one or more of the following
+            // has occurred:
+            //   a. NCR message has been received
+            //   b. Transaction IO has completed
+            //   c. Interval timer expired
+            //   d. Something stopped IO service (runIO returns 0)
+            if (runIO() == 0) {
+                // Pretty sure this amounts to an unexpected stop and we
+                // should bail out now.  Normal shutdowns do not utilize
+                // stopping the IOService.
+                isc_throw(DProcessBaseError,
+                          "Primary IO service stopped unexpectedly");
+            }
+        } catch (const std::exception& ex) {
+            LOG_FATAL(dctl_logger, DHCP_DDNS_FAILED).arg(ex.what());
+            isc_throw (DProcessBaseError,
+                       "Process run method failed: " << ex.what());
+        }
+    }
+
+    // @todo - if queue isn't empty, we may need to persist its contents
+    // this might be the place to do it, once there is a persistence mgr.
+    // This may also be better in checkQueueStatus.
+
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DHCP_DDNS_RUN_EXIT);
+
+};
+
+size_t
+D2Process::runIO() {
+    // We want to block until at least one handler is called.  We'll use
+    // asio::io_service directly for two reasons. First off
+    // asiolink::IOService::run_one is a void and asio::io_service::stopped
+    // is not present in older versions of boost.  We need to know if any
+    // handlers ran or if the io_service was stopped.  That latter represents
+    // some form of error and the application cannot proceed with a stopped
+    // service.  Secondly, asiolink::IOService does not provide the poll
+    // method.  This is a handy method which runs all ready handlers without
+    // blocking.
+    IOServicePtr& io = getIoService();
+    asio::io_service& asio_io_service  = io->get_io_service();
+
+    // Poll runs all that are ready. If none are ready it returns immediately
+    // with a count of zero.
+    size_t cnt = asio_io_service.poll();
+    if (!cnt) {
+        // Poll ran no handlers either none are ready or the service has been
+        // stopped.  Either way, call run_one to wait for a IO event. If the
+        // service is stopped it will return immediately with a cnt of zero.
+        cnt = asio_io_service.run_one();
+    }
+
+    return (cnt);
+}
+
+bool
+D2Process::canShutdown() const {
+    bool all_clear = false;
+
+    // If we have been told to shutdown, find out if we are ready to do so.
+    if (shouldShutdown()) {
+        switch (shutdown_type_) {
+        case SD_NORMAL:
+            // For a normal shutdown we need to stop the queue manager but
+            // wait until we have finished all the transactions in progress.
+            all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
+                          (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
+                         && (update_mgr_->getTransactionCount() == 0));
+            break;
+
+        case SD_DRAIN_FIRST:
+            // For a drain first shutdown we need to stop the queue manager but
+            // process all of the requests in the receive queue first.
+            all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
+                          (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
+                          && (queue_mgr_->getQueueSize() == 0)
+                          && (update_mgr_->getTransactionCount() == 0));
+            break;
+
+        case SD_NOW:
+            // Get out right now, no niceties.
+            all_clear = true;
+            break;
+
+        default:
+            // shutdown_type_ is an enum and should only be one of the above.
+            // if its getting through to this, something is whacked.
+            break;
+        }
+
+        if (all_clear) {
+            LOG_INFO(dctl_logger,DHCP_DDNS_CLEARED_FOR_SHUTDOWN)
+                     .arg(getShutdownTypeStr(shutdown_type_));
+        }
+    }
+
+    return (all_clear);
+}
+
+isc::data::ConstElementPtr
+D2Process::shutdown(isc::data::ConstElementPtr args) {
+    LOG_INFO(dctl_logger, DHCP_DDNS_SHUTDOWN).arg(args ? args->str()
+                                                  : "(no args)");
+
+    // Default shutdown type is normal.
+    std::string type_str(getShutdownTypeStr(SD_NORMAL));
+    shutdown_type_ = SD_NORMAL;
+
+    if (args) {
+        if ((args->getType() == isc::data::Element::map) &&
+            args->contains("type")) {
+            type_str = args->get("type")->stringValue();
+
+            if (type_str == getShutdownTypeStr(SD_NORMAL)) {
+                shutdown_type_ = SD_NORMAL;
+            } else if (type_str == getShutdownTypeStr(SD_DRAIN_FIRST)) {
+                shutdown_type_ = SD_DRAIN_FIRST;
+            } else if (type_str == getShutdownTypeStr(SD_NOW)) {
+                shutdown_type_ = SD_NOW;
+            } else {
+                setShutdownFlag(false);
+                return (isc::config::createAnswer(1, "Invalid Shutdown type: "
+                                                  + type_str));
+            }
+        }
+    }
+
+    // Set the base class's shutdown flag.
+    setShutdownFlag(true);
+    return (isc::config::createAnswer(0, "Shutdown initiated, type is: "
+                                      + type_str));
+}
+
+isc::data::ConstElementPtr
+D2Process::configure(isc::data::ConstElementPtr config_set) {
+    LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
+              DHCP_DDNS_CONFIGURE).arg(config_set->str());
+
+    int rcode = 0;
+    isc::data::ConstElementPtr comment;
+    isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;
+    comment = isc::config::parseAnswer(rcode, answer);
+
+    if (rcode) {
+        // Non-zero means we got an invalid configuration, take no further
+        // action.  In integrated mode, this will send a failed response back 
+        // to BIND10.
+        reconf_queue_flag_ = false;
+        return (answer);
+    }
+
+    // Set the reconf_queue_flag to indicate that we need to reconfigure
+    // the queue manager.  Reconfiguring the queue manager may be asynchronous
+    // and require one or more events to occur, therefore we set a flag
+    // indicating it needs to be done but we cannot do it here.  It must
+    // be done over time, while events are being processed.  Remember that
+    // the method we are in now is invoked as part of the configuration event
+    // callback.  This means you can't wait for events here, you are already
+    // in one.
+    // (@todo NOTE This could be turned into a bitmask of flags if we find other
+    // things that need reconfiguration.  It might also be useful if we
+    // did some analysis to decide what if anything we need to do.)
+    reconf_queue_flag_ = true;
+
+    // If we are here, configuration was valid, at least it parsed correctly
+    // and therefore contained no invalid values.
+    // Return the success answer from above.
+    return (answer);
+}
+
+void
+D2Process::checkQueueStatus() {
+    switch (queue_mgr_->getMgrState()){
+    case D2QueueMgr::RUNNING:
+        if (reconf_queue_flag_ || shouldShutdown()) {
+            // If we need to reconfigure the queue manager or we have been
+            // told to shutdown, then stop listening first.  Stopping entails
+            // canceling active listening which may generate an IO event, so
+            // instigate the stop and get out.
+            try {
+                LOG_INFO(dctl_logger, DHCP_DDNS_QUEUE_MGR_STOPPING)
+                         .arg(reconf_queue_flag_ ? "reconfiguration"
+                                                   : "shutdown");
+                queue_mgr_->stopListening();
+            } catch (const isc::Exception& ex) {
+                // It is very unlikey that we would experience an error
+                // here, but theoretically possible. 
+                LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_STOP_ERROR)
+                          .arg(ex.what());
+            }
+        }
+        break;
+
+    case D2QueueMgr::STOPPED_QUEUE_FULL: {
+            // Resume receiving once the queue has decreased by twenty
+            // percent.  This is an arbitrary choice. @todo this value should
+            // probably be configurable.
+            size_t threshold = (((queue_mgr_->getMaxQueueSize()
+                                * QUEUE_RESTART_PERCENT)) / 100);
+            if (queue_mgr_->getQueueSize() <= threshold) {
+                LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RESUMING)
+                          .arg(threshold).arg(queue_mgr_->getMaxQueueSize());
+                try {
+                    queue_mgr_->startListening();
+                } catch (const isc::Exception& ex) {
+                    LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_RESUME_ERROR)
+                              .arg(ex.what());
+                }
+            }
+
+        break;
+        }
+
+    case D2QueueMgr::STOPPED_RECV_ERROR:
+        // If the receive error is not due to some fallout from shutting
+        // down then we will attempt to recover by reconfiguring the listener.
+        // This will close and destruct the current listener and make a new
+        // one with new resources.
+        // @todo This may need a safety valve such as retry count or a timer
+        // to keep from endlessly retrying over and over, with little time
+        // in between.
+        if (!shouldShutdown()) {
+            LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RECOVERING);
+            reconfigureQueueMgr();
+        }
+        break;
+
+    case D2QueueMgr::STOPPING:
+        // We are waiting for IO to cancel, so this is a NOP.
+        // @todo Possible timer for self-defense?  We could conceivably
+        // get into a condition where we never get the event, which would
+        // leave us stuck in stopping.  This is hugely unlikely but possible?
+        break;
+
+    default:
+        // If the reconfigure flag is set, then we are in a state now where
+        // we can do the reconfigure. In other words, we aren't RUNNING or
+        // STOPPING.
+        if (reconf_queue_flag_) {
+            LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RECONFIGURING);
+            reconfigureQueueMgr();
+        }
+        break;
+    }
+}
+
+void
+D2Process::reconfigureQueueMgr() {
+    // Set reconfigure flag to false.  We are only here because we have
+    // a valid configuration to work with so if we fail below, it will be
+    // an operational issue, such as a busy IP address. That will leave
+    // queue manager in INITTED state, which is fine.
+    // What we dont' want is to continually attempt to reconfigure so set
+    // the flag false now.
+    // @todo This method assumes only 1 type of listener.  This will change
+    // to support at least a TCP version, possibly some form of RDBMS listener
+    // as well.
+    reconf_queue_flag_ = false;
+    try {
+        // Wipe out the current listener.
+        queue_mgr_->removeListener();
+
+        // Get the configuration parameters that affect Queue Manager.
+        // @todo Need to add parameters for listener TYPE, FORMAT, address reuse
+        std::string ip_address;
+        uint32_t port;
+        getCfgMgr()->getContext()->getParam("ip_address", ip_address);
+        getCfgMgr()->getContext()->getParam("port", port);
+        isc::asiolink::IOAddress addr(ip_address);
+
+        // Instantiate the listener.
+        queue_mgr_->initUDPListener(addr, port, dhcp_ddns::FMT_JSON, true);
+
+        // Now start it. This assumes that starting is a synchronous,
+        // blocking call that executes quickly.  @todo Should that change then
+        // we will have to expand the state model to accommodate this.
+        queue_mgr_->startListening();
+    } catch (const isc::Exception& ex) {
+        // Queue manager failed to initialize and therefore not listening. 
+        // This is most likely due to an unavailable IP address or port, 
+        // which is a configuration issue.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_START_ERROR).arg(ex.what());
+    }
+}
+
+isc::data::ConstElementPtr
+D2Process::command(const std::string& command, 
+                   isc::data::ConstElementPtr args) {
+    // @todo This is the initial implementation.  If and when D2 is extended
+    // to support its own commands, this implementation must change. Otherwise
+    // it should reject all commands as it does now.
+    LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC, DHCP_DDNS_COMMAND)
+        .arg(command).arg(args ? args->str() : "(no args)");
+
+    return (isc::config::createAnswer(COMMAND_INVALID, "Unrecognized command: "
+                                      + command));
+}
+
+D2Process::~D2Process() {
+};
+
+D2CfgMgrPtr
+D2Process::getD2CfgMgr() {
+    // The base class gives a base class pointer to our configuration manager.
+    // Since we are D2, and we need D2 specific extensions, we need a pointer
+    // to D2CfgMgr for some things.
+    return (boost::dynamic_pointer_cast<D2CfgMgr>(getCfgMgr()));
+}
+
+const char* D2Process::getShutdownTypeStr(const ShutdownType& type) {
+    const char* str = "invalid";
+    switch (type) {
+    case SD_NORMAL:
+        str = "normal";
+        break;
+    case SD_DRAIN_FIRST:
+        str = "drain_first";
+        break;
+    case SD_NOW:
+        str = "now";
+        break;
+    default:
+        break;
+    }
+
+    return (str);
+}
+
+}; // namespace isc::d2
+}; // namespace isc

+ 333 - 0
src/bin/d2/d2_process.h

@@ -0,0 +1,333 @@
+// Copyright (C) 2013  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 D2_PROCESS_H
+#define D2_PROCESS_H
+
+#include <d2/d_process.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2/d2_update_mgr.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief DHCP-DDNS Application Process
+///
+/// D2Process provides the top level application logic for DHCP-driven DDNS
+/// update processing.  It provides the asynchronous event processing required
+/// to receive DNS mapping change requests and carry them out.
+/// It implements the DProcessBase interface, which structures it such that it
+/// is a managed "application", controlled by a management layer.
+class D2Process : public DProcessBase {
+public:
+
+    /// @brief Defines the shutdown types supported by D2Process
+    ///
+    /// * SD_NORMAL - Stops the queue manager and finishes all current
+    /// transactions before exiting. This is the default.
+    ///
+    /// * SD_DRAIN_FIRST - Stops the queue manager but continues processing
+    /// requests from the queue until it is empty.
+    ///
+    /// * SD_NOW - Exits immediately.
+    enum ShutdownType {
+      SD_NORMAL,
+      SD_DRAIN_FIRST,
+      SD_NOW
+    };
+
+    /// @brief Defines the point at which to resume receiving requests.
+    /// If the receive queue has become full, D2Process will "pause" the
+    /// reception of requests by putting the queue manager in the stopped
+    /// state.  Once the number of entries has decreased to this percentage
+    /// of  the maximum allowed, D2Process will "resume" receiving requests
+    /// by restarting the queue manager.
+    static const unsigned int QUEUE_RESTART_PERCENT;
+
+    /// @brief Constructor
+    ///
+    /// Construction creates the configuration manager, the queue
+    /// manager, and the update manager.
+    ///
+    /// @param name name is a text label for the process. Generally used
+    /// in log statements, but otherwise arbitrary.
+    /// @param io_service is the io_service used by the caller for
+    /// asynchronous event handling.
+    ///
+    /// @throw DProcessBaseError is io_service is NULL.
+    D2Process(const char* name, IOServicePtr io_service);
+
+    /// @brief Called after instantiation to perform initialization unique to
+    /// D2.
+    ///
+    /// This is invoked by the controller after command line arguments but
+    /// PRIOR to configuration reception.  The base class provides this method
+    /// as a place to perform any derivation-specific initialization steps
+    /// that are inapppropriate for the constructor but necessary prior to
+    /// launch.  So far, no such steps have been identified for D2, so its
+    /// implementantion is empty but required.
+    ///
+    /// @throw DProcessBaseError if the initialization fails.
+    virtual void init();
+
+    /// @brief Implements the process's event loop.
+    ///
+    /// Once entered, the main control thread remains inside this method
+    /// until shutdown.  The event loop logic is as follows:
+    /// @code
+    ///    while should not down {
+    ///       process queue manager state change
+    ///       process completed jobs
+    ///       dequeue new jobs
+    ///       wait for IO event(s)
+    ///
+    ///       ON an exception, exit with fatal error
+    ///    }
+    /// @endcode
+    ///
+    /// To summarize, each pass through the event loop first checks the state
+    /// of the received queue and takes any steps required to ensure it is
+    /// operating in the manner necessary.  Next the update manager is given
+    /// a chance to clean up any completed transactions and start new
+    /// transactions by dequeuing jobs from the request queue.  Lastly, it
+    /// allows IOService to process until one or more event handlers are
+    /// called.  Note that this last step will block until at least one
+    /// ready handler is invoked.  In other words, if no IO events have occurred
+    /// since it was last called, the event loop will block at this step until
+    /// an IO event occurs.  At that time we return to the top of the loop.
+    ///
+    /// @throw DProcessBaseError if an error is encountered.  Note that
+    /// exceptions thrown at this point are assumed to be FATAL exceptions.
+    /// This includes exceptions generated but not caught by IO callbacks.
+    /// Services which rely on callbacks are expected to be well behaved and
+    /// any errors they encounter handled internally.
+    virtual void run();
+
+    /// @brief Initiates the D2Process shutdown process.
+    ///
+    /// This is last step in the shutdown event callback chain. It is invoked
+    /// to notify D2Process that it needs to begin its shutdown procedure.
+    /// Note that shutting down may be neither instantaneous nor synchronous,
+    /// This method records the request for and the type of shutdown desired.
+    /// Generally it will require one or more subsequent events to complete,
+    /// dependent on the type of shutdown requested.  The type of shutdown is
+    /// specified as an optional argument of the shutdown command. The types
+    /// of shutdown supported are:
+    ///
+    /// * "normal" - Stops the queue manager and finishes all current
+    /// transactions before exiting. This is the default.
+    ///
+    /// * "drain_first" - Stops the queue manager but continues processing
+    /// requests from the queue until it is empty.
+    ///
+    /// * "now" - Exits immediately.
+    ///
+    /// @param args Specifies the shutdown "type" as "normal", "drain_first",
+    /// or "now"
+    ///
+    /// @return an Element that contains the results of argument processing,
+    /// consisting of an integer status value (0 means successful,
+    /// non-zero means failure), and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr
+        shutdown(isc::data::ConstElementPtr args);
+
+    /// @brief Processes the given configuration.
+    ///
+    /// This method may be called multiple times during the process lifetime.
+    /// Certainly once during process startup, and possibly later if the user
+    /// alters configuration. This method must not throw, it should catch any
+    /// processing errors and return a success or failure answer as described
+    /// below.
+    ///
+    /// This method passes the newly received configuration to the configuration
+    /// manager instance for parsing.  The configuration manager parses the
+    /// configuration and updates the necessary values within the context,
+    /// assuming it parses correctly.  If that's the case this method sets the
+    /// flag to reconfigure the queue manager and returns a successful response
+    /// as described below.
+    ///
+    /// If the new configuration fails to parse, then the current configuration
+    /// is retained and a failure response is returned as described below.
+    ///
+    /// @param config_set a new configuration (JSON) for the process
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+                                                 config_set);
+
+    /// @brief Processes the given command.
+    ///
+    /// This method is called to execute any custom commands supported by the
+    /// process. This method must not throw, it should catch any processing
+    /// errors and return a success or failure answer as described below.
+    ///
+    /// @param command is a string label representing the command to execute.
+    /// @param args is a set of arguments (if any) required for the given
+    /// command. It can be a NULL pointer if no arguments exist for a command.
+    /// @return an Element that contains the results of command composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr command(const std::string& command,
+                                               isc::data::ConstElementPtr args);
+    /// @brief Destructor
+    virtual ~D2Process();
+
+protected:
+    /// @brief Monitors current queue manager state, takes action accordingly
+    ///
+    /// This method ensures that the queue manager transitions to the state
+    /// most appropriate to the operational state of the D2Process and any
+    /// events that may have occurred since it was last called.  It is called
+    /// once for each iteration of the event loop.  It is essentially a
+    /// switch statement based on the D2QueueMgr's current state.  The logic
+    /// is as follows:
+    ///
+    /// If the state is D2QueueMgr::RUNNING, and the queue manager needs to be
+    /// reconfigured or we have been told to shutdown, then instruct the queue
+    /// manager to stop listening. Exit the method.
+    ///
+    /// If the state is D2QueueMgr::STOPPED_QUEUE_FULL, then check if the
+    /// number of entries in the queue has fallen below the "resume threshold".
+    /// If it has, then instruct the queue manager to start listening. Exit
+    /// the method.
+    ///
+    /// If the state is D2QueueMgr::STOPPED_RECV_ERROR, then attempt to recover
+    /// by calling reconfigureQueueMgr(). Exit the method.
+    ///
+    /// If the state is D2QueueMgr::STOPPING, simply exit the method. This is
+    /// a NOP condition as we are waiting for the IO cancel event
+    ///
+    /// For any other state, (NOT_INITTED,INITTED,STOPPED), if the reconfigure
+    /// queue flag is set, call reconfigureQueueMgr(). Exit the method.
+    ///
+    /// This method is exception safe.
+    virtual void checkQueueStatus();
+
+    /// @brief Initializes then starts the queue manager.
+    ///
+    /// This method is initializes the queue manager with the current
+    /// configuration parameters and instructs it to start listening.
+    /// Note the existing listener instance (if it exists) is destroyed,
+    /// and that a new listener is created during initialization.
+    ///
+    /// This method is exception safe.
+    virtual void reconfigureQueueMgr();
+
+    /// @brief Allows IO processing to run until at least callback is invoked.
+    ///
+    /// This method is called from within the D2Process main event loop and is
+    /// the point at which the D2Process blocks, waiting for IO events to
+    /// cause IO event callbacks to be invoked.
+    ///
+    /// If callbacks are ready to be executed upon entry, the method will
+    /// return as soon as these callbacks have completed.  If no callbacks
+    /// are ready, then it will wait (indefinitely) until at least one callback
+    /// is executed.
+    ///
+    /// @note: Should become desirable to periodically force an
+    /// event, an interval timer could be used to do so.
+    ///
+    /// @return The number of callback handlers executed, or 0 if the IO
+    /// service has been stopped.
+    ///
+    /// @throw This method does not throw directly, but the execution of
+    /// callbacks invoked in response to IO events might.  If so, these
+    /// will propagate upward out of this method.
+    virtual size_t runIO();
+
+    /// @brief Indicates whether or not the process can perform a shutdown.
+    ///
+    /// Determines if the process has been instructed to shutdown and if
+    /// the criteria for performing the type of shutdown requested has been
+    /// met.
+    ///
+    /// @return Returns true if the criteria has been met, false otherwise.
+    virtual bool canShutdown() const;
+
+    /// @brief Sets queue reconfigure indicator to the given value.
+    ///
+    /// @param value is the new value to assign to the indicator
+    ///
+    /// @note this method is really only intended for testing purposes.
+    void setReconfQueueFlag(const bool value) {
+        reconf_queue_flag_ = value;
+    }
+
+    /// @brief Sets the shutdown type to the given value.
+    ///
+    /// @param value is the new value to assign to shutdown type.
+    ///
+    /// @note this method is really only intended for testing purposes.
+    void setShutdownType(const ShutdownType& value) {
+        shutdown_type_ = value;
+    }
+
+public:
+    /// @brief Returns a pointer to the configuration manager.
+    /// Note, this method cannot return a reference as it uses dynamic
+    /// pointer casting of the base class configuration manager.
+    D2CfgMgrPtr getD2CfgMgr();
+
+    /// @brief Returns a reference to the queue manager.
+    const D2QueueMgrPtr& getD2QueueMgr() const {
+        return (queue_mgr_);
+    }
+
+    /// @brief Returns a reference to the update manager.
+    const D2UpdateMgrPtr& getD2UpdateMgr() const {
+        return (update_mgr_);
+    }
+
+    /// @brief Returns true if the queue manager should be reconfigured.
+    bool getReconfQueueFlag() const {
+        return (reconf_queue_flag_);
+    }
+
+    /// @brief Returns the type of shutdown requested.
+    ///
+    /// Note, this value is meaningless unless shouldShutdown() returns true.
+    ShutdownType getShutdownType() const {
+        return (shutdown_type_);
+    }
+
+    /// @brief Returns a text label for the given shutdown type.
+    ///
+    /// @param type the numerical shutdown type for which the label is desired.
+    ///
+    /// @return A text label corresponding the value or "invalid" if the
+    /// value is not a valid value.
+    static const char* getShutdownTypeStr(const ShutdownType& type);
+
+private:
+    /// @brief Pointer to our queue manager instance.
+    D2QueueMgrPtr queue_mgr_;
+
+    /// @brief Pointer to our update manager instance.
+    D2UpdateMgrPtr update_mgr_;
+
+    /// @brief Indicates if the queue manager should be reconfigured.
+    bool reconf_queue_flag_;
+
+    /// @brief Indicates the type of shutdown requested.
+    ShutdownType shutdown_type_;
+};
+
+/// @brief Defines a shared pointer to D2Process.
+typedef boost::shared_ptr<D2Process> D2ProcessPtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

+ 263 - 0
src/bin/d2/d2_queue_mgr.cc

@@ -0,0 +1,263 @@
+// Copyright (C) 2013 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 <d2/d2_log.h>
+#include <d2/d2_queue_mgr.h>
+#include <dhcp_ddns/ncr_udp.h>
+
+namespace isc {
+namespace d2 {
+
+// Makes constant visible to Google test macros.
+const size_t D2QueueMgr::MAX_QUEUE_DEFAULT;
+
+D2QueueMgr::D2QueueMgr(IOServicePtr& io_service, const size_t max_queue_size)
+    : io_service_(io_service), max_queue_size_(max_queue_size),
+      mgr_state_(NOT_INITTED), target_stop_state_(NOT_INITTED) {
+    if (!io_service_) {
+        isc_throw(D2QueueMgrError, "IOServicePtr cannot be null");
+    }
+
+    // Use setter to do validation.
+    setMaxQueueSize(max_queue_size);
+}
+
+D2QueueMgr::~D2QueueMgr() {
+}
+
+void
+D2QueueMgr::operator()(const dhcp_ddns::NameChangeListener::Result result,
+                       dhcp_ddns::NameChangeRequestPtr& ncr) {
+    try {
+        // Note that error conditions must be handled here without throwing
+        // exceptions. Remember this is the application level "link" in the
+        // callback chain.  Throwing an exception here will "break" the
+        // io_service "run" we are operating under.  With that in mind,
+        // if we hit a problem, we will stop the listener transition to
+        // the appropriate stopped state.  Upper layer(s) must monitor our
+        // state as well as our queue size.
+        switch (result) {
+        case dhcp_ddns::NameChangeListener::SUCCESS:
+            // Receive was successful, attempt to queue the request.
+            if (getQueueSize() < getMaxQueueSize()) {
+                // There's room on the queue, add to the end
+                enqueue(ncr);
+                return;
+            }
+
+            // Queue is full, stop the listener.
+            // Note that we can move straight to a STOPPED state as there
+            // is no receive in progress.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_QUEUE_FULL)
+                      .arg(max_queue_size_);
+            stopListening(STOPPED_QUEUE_FULL);
+            break;
+
+        case dhcp_ddns::NameChangeListener::STOPPED:
+            if (mgr_state_ == STOPPING) {
+                // This is confirmation that the listener has stopped and its
+                // callback will not be called again, unless its restarted.
+                updateStopState();
+            } else {
+                // We should not get an receive complete status of stopped
+                // unless we canceled the read as part of stopping. Therefore
+                // this is unexpected so we will treat it as a receive error.
+                // This is most likely an unforeseen programmatic issue.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP)
+                          .arg(mgr_state_);
+                stopListening(STOPPED_RECV_ERROR);
+            }
+
+            break;
+
+        default:
+            // Receive failed, stop the listener.
+            // Note that we can move straight to a STOPPED state as there
+            // is no receive in progress.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_RECV_ERROR);
+            stopListening(STOPPED_RECV_ERROR);
+            break;
+        }
+    } catch (const std::exception& ex) {
+        // On the outside chance a throw occurs, let's log it and swallow it.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR)
+                  .arg(ex.what());
+    }
+}
+
+void
+D2QueueMgr::initUDPListener(const isc::asiolink::IOAddress& ip_address,
+                            const uint32_t port,
+                            const dhcp_ddns::NameChangeFormat format,
+                            const bool reuse_address) {
+
+    if (listener_) {
+        isc_throw(D2QueueMgrError,
+                  "D2QueueMgr listener is already initialized");
+    }
+
+    // Instantiate a UDP listener and set state to INITTED.
+    // Note UDP listener constructor does not throw.
+    listener_.reset(new dhcp_ddns::
+                    NameChangeUDPListener(ip_address, port, format, *this,
+                                          reuse_address));
+    mgr_state_ = INITTED;
+}
+
+void
+D2QueueMgr::startListening() {
+    // We can't listen if we haven't initialized the listener yet.
+    if (!listener_) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr "
+                  "listener is not initialized, cannot start listening");
+    }
+
+    // If we are already listening, we do not want to "reopen" the listener
+    // and really we shouldn't be trying.
+    if (mgr_state_ == RUNNING) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr "
+                  "cannot call startListening from the RUNNING state");
+    }
+
+    // Instruct the listener to start listening and set state accordingly.
+    try {
+        listener_->startListening(*io_service_);
+        mgr_state_ = RUNNING;
+    } catch (const isc::Exception& ex) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "
+                  << ex.what());
+    }
+
+    LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_STARTED);
+}
+
+void
+D2QueueMgr::stopListening(const State target_stop_state) {
+    if (listener_) {
+        // Enforce only valid "stop" states.
+        // This is purely a programmatic error and should never happen.
+        if (target_stop_state != STOPPED &&
+            target_stop_state != STOPPED_QUEUE_FULL &&
+            target_stop_state != STOPPED_RECV_ERROR) {
+            isc_throw(D2QueueMgrError,
+                      "D2QueueMgr invalid value for stop state: "
+                      << target_stop_state);
+        }
+
+        // Remember the state we want to acheive.
+        target_stop_state_ = target_stop_state;
+
+        // Instruct the listener to stop.  If the listener reports that  it
+        // has IO pending, then we transition to STOPPING to wait for the
+        // cancellation event.  Otherwise, we can move directly to the targeted
+        // state.
+        listener_->stopListening();
+        if (listener_->isIoPending()) {
+            mgr_state_ = STOPPING;
+        } else {
+            updateStopState();
+        }
+    }
+}
+
+void
+D2QueueMgr::updateStopState() {
+    mgr_state_ = target_stop_state_;
+    LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_STOPPED);
+}
+
+
+void
+D2QueueMgr::removeListener() {
+    // Force our managing layer(s) to stop us properly first.
+    if (mgr_state_ == RUNNING) {
+        isc_throw(D2QueueMgrError,
+                  "D2QueueMgr cannot delete listener while state is RUNNING");
+    }
+
+    listener_.reset();
+    mgr_state_ = NOT_INITTED;
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peek() const {
+    if (getQueueSize() ==  0) {
+        isc_throw(D2QueueMgrQueueEmpty,
+                  "D2QueueMgr peek attempted on an empty queue");
+    }
+
+    return (ncr_queue_.front());
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peekAt(const size_t index) const {
+    if (index >= getQueueSize()) {
+        isc_throw(D2QueueMgrInvalidIndex,
+                  "D2QueueMgr peek beyond end of queue attempted"
+                  << " index: " << index << " queue size: " << getQueueSize());
+    }
+
+    return (ncr_queue_.at(index));
+}
+
+void
+D2QueueMgr::dequeueAt(const size_t index) {
+    if (index >= getQueueSize()) {
+        isc_throw(D2QueueMgrInvalidIndex,
+                  "D2QueueMgr dequeue beyond end of queue attempted"
+                  << " index: " << index << " queue size: " << getQueueSize());
+    }
+
+    RequestQueue::iterator pos = ncr_queue_.begin() + index;
+    ncr_queue_.erase(pos);
+}
+
+
+void
+D2QueueMgr::dequeue() {
+    if (getQueueSize() ==  0) {
+        isc_throw(D2QueueMgrQueueEmpty,
+                  "D2QueueMgr dequeue attempted on an empty queue");
+    }
+
+    ncr_queue_.pop_front();
+}
+
+void
+D2QueueMgr::enqueue(dhcp_ddns::NameChangeRequestPtr& ncr) {
+    ncr_queue_.push_back(ncr);
+}
+
+void
+D2QueueMgr::clearQueue() {
+    ncr_queue_.clear();
+}
+
+void
+D2QueueMgr::setMaxQueueSize(const size_t new_queue_max) {
+    if (new_queue_max < 1) {
+        isc_throw(D2QueueMgrError,
+                  "D2QueueMgr maximum queue size must be greater than zero");
+    }
+
+    if (new_queue_max < getQueueSize()) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr maximum queue size value cannot"
+                  " be less than the current queue size :" << getQueueSize());
+    }
+
+    max_queue_size_ = new_queue_max;
+}
+
+} // namespace isc::d2
+} // namespace isc

+ 354 - 0
src/bin/d2/d2_queue_mgr.h

@@ -0,0 +1,354 @@
+// Copyright (C) 2013 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 D2_QUEUE_MGR_H
+#define D2_QUEUE_MGR_H
+
+/// @file d2_queue_mgr.h This file defines the class D2QueueMgr.
+
+#include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp_ddns/ncr_io.h>
+
+#include <boost/noncopyable.hpp>
+#include <deque>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines a queue of requests.
+/// @todo This may be replaced with an actual class in the future.
+typedef std::deque<dhcp_ddns::NameChangeRequestPtr> RequestQueue;
+
+/// @brief Thrown if the queue manager encounters a general error.
+class D2QueueMgrError : public isc::Exception {
+public:
+    D2QueueMgrError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the queue manager's receive handler is passed
+/// a failure result.
+class D2QueueMgrReceiveError : public isc::Exception {
+public:
+    D2QueueMgrReceiveError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Thrown if the request queue is full when an enqueue is attempted.
+class D2QueueMgrQueueFull : public isc::Exception {
+public:
+    D2QueueMgrQueueFull(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the request queue empty and a read is attempted.
+class D2QueueMgrQueueEmpty : public isc::Exception {
+public:
+    D2QueueMgrQueueEmpty(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if a queue index is beyond the end of the queue
+class D2QueueMgrInvalidIndex : public isc::Exception {
+public:
+    D2QueueMgrInvalidIndex(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief D2QueueMgr creates and manages a queue of DNS update requests.
+///
+/// D2QueueMgr is class specifically designed as an integral part of DHCP-DDNS.
+/// Its primary responsibility is to listen for NameChangeRequests from
+/// DHCP-DDNS clients (e.g. DHCP servers) and queue them for processing. In
+/// addition it may provide a number services to locate entries in the queue
+/// such as by FQDN or DHCID.  These services may eventually be used
+/// for processing optimization.  The initial implementation will support
+/// simple FIFO access.
+///
+/// D2QueueMgr uses a NameChangeListener to asynchronously receive requests.
+/// It derives from NameChangeListener::RequestReceiveHandler and supplies an
+/// implementation of the operator()(Result, NameChangeRequestPtr).  It is
+/// through this operator() that D2QueueMgr is passed inbound NCRs. D2QueueMgr
+/// will add each newly received request onto the back of the request queue
+///
+/// D2QueueMgr defines a simple state model constructed around the status of
+/// its NameChangeListener, consisting of the following states:
+///
+///     * NOT_INITTED - D2QueueMgr has been constructed, but its listener has
+///     not been initialized.
+///
+///     * INITTED - The listener has been initialized, but it is not open for
+///     listening.   To move from NOT_INITTED to INITTED, one of the D2QueueMgr
+///     listener initialization methods must be invoked.  Currently there is
+///     only one type of listener, NameChangeUDPListener, hence there is only
+///     one listener initialization method, initUDPListener.  As more listener
+///     types are created, listener initialization methods will need to be
+///     added.
+///
+///     * RUNNING - The listener is open and listening for requests.
+///     Once initialized, in order to begin listening for requests, the
+///     startListener() method must be invoked.  Upon successful completion of
+///     of this call, D2QueueMgr will begin receiving requests as they arrive
+///     without any further steps.   This method may be called from the INITTED
+///     or one of the STOPPED states.
+///
+///     * STOPPING - The listener is in the process of stopping active
+///     listening. This is transitory state between RUNNING and STOPPED, which
+///     is completed by IO cancellation event.
+///
+///     * STOPPED - The listener has been listening but has been stopped
+///     without error. To return to listening, startListener() must be invoked.
+///
+///     * STOPPED_QUEUE_FULL - Request queue is full, the listener has been
+///     stopped.  D2QueueMgr will enter this state when the request queue
+///     reaches the maximum queue size.  Once this limit is reached, the
+///     listener will be closed and no further requests will be received.
+///     To return to listening, startListener() must be invoked.  Note that so
+///     long as the queue is full, any attempt to queue a request will fail.
+///
+///     * STOPPED_RECV_ERROR - The listener has experienced a receive error
+///     and has been stopped.  D2QueueMgr will enter this state when it is
+///     passed a failed status into the request completion handler.  To return
+///     to listening, startListener() must be invoked.
+///
+/// D2QueueMgr does not attempt to recover from stopped conditions, this is left
+/// to upper layers.
+///
+/// It is important to note that the queue contents are preserved between
+/// state transitions.  In other words entries in the queue remain there
+/// until they are removed explicitly via the deque() or implicitly by
+/// via the clearQueue() method.
+///
+class D2QueueMgr : public dhcp_ddns::NameChangeListener::RequestReceiveHandler,
+                   boost::noncopyable {
+public:
+    /// @brief Maximum number of entries allowed in the request queue.
+    /// NOTE that 1024 is an arbitrary choice picked for the initial
+    /// implementation.
+    static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+    /// @brief Defines the list of possible states for D2QueueMgr.
+    enum State {
+      NOT_INITTED,
+      INITTED,
+      RUNNING,
+      STOPPING,
+      STOPPED_QUEUE_FULL,
+      STOPPED_RECV_ERROR,
+      STOPPED,
+    };
+
+    /// @brief Constructor
+    ///
+    /// Creates a D2QueueMgr instance.  Note that the listener is not created
+    /// in the constructor. The initial state will be NOT_INITTED.
+    ///
+    /// @param io_service IOService instance to be passed into the listener for
+    /// IO management.
+    /// @param max_queue_size the maximum number of entries allowed in the
+    /// queue.
+    /// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT.
+    ///
+    /// @throw D2QueueMgrError if max_queue_size is zero.
+    D2QueueMgr(IOServicePtr& io_service,
+               const size_t max_queue_size = MAX_QUEUE_DEFAULT);
+
+    /// @brief Destructor
+    virtual ~D2QueueMgr();
+
+    /// @brief Initializes the listener as a UDP listener.
+    ///
+    /// Instantiates the listener_ member as NameChangeUDPListener passing
+    /// the given parameters.  Upon successful completion, the D2QueueMgr state
+    /// will be INITTED.
+    ///
+    /// @param ip_address is the network address on which to listen
+    /// @param port is the IP port on which to listen
+    /// @param format is the wire format of the inbound requests.
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    void initUDPListener(const isc::asiolink::IOAddress& ip_address,
+                         const uint32_t port,
+                         const dhcp_ddns::NameChangeFormat format,
+                         const bool reuse_address = false);
+
+    /// @brief Starts actively listening for requests.
+    ///
+    /// Invokes the listener's startListening method passing in our
+    /// IOService instance.
+    ///
+    /// @throw D2QueueMgrError if the listener has not been initialized,
+    /// state is already RUNNING, or the listener fails to actually start.
+    void startListening();
+
+    /// @brief Function operator implementing the NCR receive callback.
+    ///
+    /// This method is invoked by the listener as part of its receive
+    /// completion callback and is how the inbound NameChangeRequests are
+    /// passed up to the D2QueueMgr for queueing.
+    /// If the given result indicates a successful receive completion and
+    /// there is room left in the queue, the given request is queued.
+    ///
+    /// If the queue is at maximum capacity, stopListening() is invoked and
+    /// the state is set to STOPPED_QUEUE_FULL.
+    ///
+    /// If the result indicates IO stopped, then the state is set to STOPPED.
+    /// Note this is not an error, it results from a deliberate cancellation
+    /// of listener IO as part of a normal stopListener call.
+    ///
+    /// If the result indicates a failed receive, stopListening() is invoked
+    /// and the state is set to STOPPED_RECV_ERROR.
+    ///
+    /// This method specifically avoids throwing on an error as any such throw
+    /// would surface at the io_service::run (or run variant) method invocation
+    /// site. The upper layers are expected to monitor D2QueueMgr's state and
+    /// act accordingly.
+    ///
+    /// @param result contains that receive outcome status.
+    /// @param ncr is a pointer to the newly received NameChangeRequest if
+    /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+    /// wise.
+    virtual void operator ()(const dhcp_ddns::NameChangeListener::Result result,
+                             dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Stops listening for requests.
+    ///
+    /// Invokes the listener's stopListening method which will cause it to
+    /// cancel any pending IO and close its IO source.  It the sets target
+    /// stop state to the given value.
+    ///
+    /// If there is no IO pending, the manager state is immediately set to the
+    /// target stop state, otherwise the manager state is set to STOPPING.
+    ///
+    /// @param target_stop_state is one of the three stopped state values.
+    ///
+    /// @throw D2QueueMgrError if stop_state is a valid stop state.
+    void stopListening(const State target_stop_state = STOPPED);
+
+
+    /// @brief Deletes the current listener
+    ///
+    /// This method will delete the current listener and returns the manager
+    /// to the NOT_INITTED state.  This is provided to support reconfiguring
+    /// a new listener without losing queued requests.
+    ///
+    /// @throw D2QueMgrError if called when the manager state is RUNNING.
+    void removeListener();
+
+    /// @brief Returns the number of entries in the queue.
+    size_t getQueueSize() const {
+        return (ncr_queue_.size());
+    };
+
+    /// @brief Returns the maximum number of entries allowed in the queue.
+    size_t getMaxQueueSize() const {
+        return (max_queue_size_);
+    }
+
+    /// @brief Sets the maximum number of entries allowed in the queue.
+    ///
+    /// @param max_queue_size is the new maximum size of the queue.
+    ///
+    /// @throw D2QueueMgrError if the new value is less than one or if
+    /// the new value is less than the number of entries currently in the
+    /// queue.
+    void setMaxQueueSize(const size_t max_queue_size);
+
+    /// @brief Returns the current state.
+    State getMgrState() const {
+        return (mgr_state_);
+    }
+
+    /// @brief Returns the entry at the front of the queue.
+    ///
+    /// The entry returned is next in line to be processed, assuming a FIFO
+    /// approach to task selection.  Note, the entry is not removed from the
+    /// queue.
+    ///
+    /// @return Pointer reference to the queue entry.
+    ///
+    /// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
+    const dhcp_ddns::NameChangeRequestPtr& peek() const;
+
+    /// @brief Returns the entry at a given position in the queue.
+    ///
+    /// Note that the entry is not removed from the queue.
+    /// @param index the index of the entry in the queue to fetch.
+    /// Valid values are 0 (front of the queue) to (queue size - 1).
+    ///
+    /// @return Pointer reference to the queue entry.
+    ///
+    /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+    /// end of the queue.
+    const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
+
+    /// @brief Removes the entry at a given position in the queue.
+    ///
+    /// @param index the index of the entry in the queue to remove.
+    /// Valid values are 0 (front of the queue) to (queue size - 1).
+    ///
+    /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+    /// end of the queue.
+    void dequeueAt(const size_t index);
+
+    /// @brief Removes the entry at the front of the queue.
+    ///
+    /// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
+    void dequeue();
+
+    /// @brief Adds a request to the end of the queue.
+    ///
+    /// @param ncr pointer to the NameChangeRequest to add to the queue.
+    void enqueue(dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Removes all entries from the queue.
+    void clearQueue();
+
+  private:
+    /// @brief Sets the manager state to the target stop state.
+    ///
+    /// Convenience method which sets the manager state to the target stop
+    /// state and logs that the manager is stopped.
+    void updateStopState();
+
+    /// @brief IOService that our listener should use for IO management.
+    IOServicePtr io_service_;
+
+    /// @brief Dictates the maximum number of entries allowed in the queue.
+    size_t max_queue_size_;
+
+    /// @brief Queue of received NameChangeRequests.
+    RequestQueue ncr_queue_;
+
+    /// @brief Listener instance from which requests are received.
+    boost::shared_ptr<dhcp_ddns::NameChangeListener> listener_;
+
+    /// @brief Current state of the manager.
+    State mgr_state_;
+
+    /// @brief Tracks the state the manager should be in once stopped.
+    State target_stop_state_;
+};
+
+/// @brief Defines a pointer for manager instances.
+typedef boost::shared_ptr<D2QueueMgr> D2QueueMgrPtr;
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif

+ 221 - 0
src/bin/d2/d2_update_message.cc

@@ -0,0 +1,221 @@
+// Copyright (C) 2013  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 <d2/d2_update_message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+
+namespace isc {
+namespace d2 {
+
+using namespace isc::dns;
+
+D2UpdateMessage::D2UpdateMessage(const Direction direction)
+    : message_(direction == INBOUND ?
+               dns::Message::PARSE : dns::Message::RENDER) {
+    // If this object is to create an outgoing message, we have to
+    // set the proper Opcode field and QR flag here.
+    if (direction == OUTBOUND) {
+        message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
+        message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
+        message_.setRcode(Rcode::NOERROR());
+    }
+}
+
+D2UpdateMessage::QRFlag
+D2UpdateMessage::getQRFlag() const {
+    return (message_.getHeaderFlag(dns::Message::HEADERFLAG_QR) ?
+            RESPONSE : REQUEST);
+}
+
+uint16_t
+D2UpdateMessage::getId() const {
+    return (message_.getQid());
+}
+
+void
+D2UpdateMessage::setId(const uint16_t id) {
+    message_.setQid(id);
+}
+
+
+const dns::Rcode&
+D2UpdateMessage::getRcode() const {
+    return (message_.getRcode());
+}
+
+void
+D2UpdateMessage::setRcode(const dns::Rcode& rcode) {
+    message_.setRcode(rcode);
+}
+
+unsigned int
+D2UpdateMessage::getRRCount(const UpdateMsgSection section) const {
+    return (message_.getRRCount(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::beginSection(const UpdateMsgSection section) const {
+    return (message_.beginSection(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::endSection(const UpdateMsgSection section) const {
+    return (message_.endSection(ddnsToDnsSection(section)));
+}
+
+void
+D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
+    // The Zone data is kept in the underlying Question class. If there
+    // is a record stored there already, we need to remove it, because
+    // we may have at most one Zone record in the DNS Update message.
+    if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
+        message_.clearSection(dns::Message::SECTION_QUESTION);
+    }
+    // Add the new record...
+    Question question(zone, rrclass, RRType::SOA());
+    message_.addQuestion(question);
+    // ... and update the local class member holding the D2Zone object.
+    zone_.reset(new D2Zone(question.getName(), question.getClass()));
+}
+
+D2ZonePtr
+D2UpdateMessage::getZone() const {
+    return (zone_);
+}
+
+void
+D2UpdateMessage::addRRset(const UpdateMsgSection section,
+                          const dns::RRsetPtr& rrset) {
+    if (section == SECTION_ZONE) {
+        isc_throw(isc::BadValue, "unable to add RRset to the Zone section"
+                  " of the DNS Update message, use setZone instead");
+    }
+    message_.addRRset(ddnsToDnsSection(section), rrset);
+}
+
+void
+D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
+    // We are preparing the wire format of the message, meaning
+    // that this message will be sent as a request to the DNS.
+    // Therefore, we expect that this message is a REQUEST.
+    if (getQRFlag() != REQUEST) {
+        isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
+                  " DNS Update message");
+    }
+    // According to RFC2136, the ZONE section may contain exactly one
+    // record.
+    if (getRRCount(SECTION_ZONE) != 1) {
+        isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
+                  " must comprise exactly one record (RFC2136, section 2.3)");
+    }
+    message_.toWire(renderer);
+}
+
+void
+D2UpdateMessage::fromWire(isc::util::InputBuffer& buffer) {
+    // First, use the underlying dns::Message implementation to get the
+    // contents of the DNS response message. Note that it may or may
+    // not be the message that we are interested in, but needs to be
+    // parsed so as we can check its ID, Opcode etc.
+    message_.fromWire(buffer);
+    // This class exposes the getZone() function. This function will return
+    // pointer to the D2Zone object if non-empty Zone section exists in the
+    // received message. It will return NULL pointer if it doesn't exist.
+    // The pointer is held in the D2UpdateMessage class member. We need to
+    // update this pointer every time we parse the message.
+    if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
+        // There is a Zone section in the received message. Replace
+        // Zone pointer with the new value.
+        QuestionPtr question = *message_.beginQuestion();
+        // If the Zone counter is greater than 0 (which we have checked)
+        // there must be a valid Question pointer stored in the message_
+        // object. If there isn't, it is a programming error.
+        assert(question);
+        zone_.reset(new D2Zone(question->getName(), question->getClass()));
+
+    } else {
+        // Zone section doesn't hold any pointers, so set the pointer to NULL.
+        zone_.reset();
+
+    }
+    // Check that the content of the received message is sane.
+    // One of the basic checks to do is to verify that we have
+    // received the DNS update message. If not, it can be dropped
+    // or an error message can be printed. Other than that, we
+    // will check that there is at most one Zone record and QR flag
+    // is set.
+    validateResponse();
+}
+
+dns::Message::Section
+D2UpdateMessage::ddnsToDnsSection(const UpdateMsgSection section) {
+    /// The following switch maps the enumerator values from the
+    /// DNS Update message to the corresponding enumerator values
+    /// representing fields of the DNS message.
+    switch(section) {
+    case SECTION_ZONE :
+        return (dns::Message::SECTION_QUESTION);
+
+    case SECTION_PREREQUISITE:
+        return (dns::Message::SECTION_ANSWER);
+
+    case SECTION_UPDATE:
+        return (dns::Message::SECTION_AUTHORITY);
+
+    case SECTION_ADDITIONAL:
+        return (dns::Message::SECTION_ADDITIONAL);
+
+    default:
+        ;
+    }
+    isc_throw(dns::InvalidMessageSection,
+              "unknown message section " << section);
+}
+
+void
+D2UpdateMessage::validateResponse() const {
+    // Verify that we are dealing with the DNS Update message. According to
+    // RFC 2136, section 3.8 server will copy the Opcode from the query.
+    // If we are dealing with a different type of message, we may simply
+    // stop further processing, because it is likely that the message was
+    // directed to someone else.
+    if (message_.getOpcode() != Opcode::UPDATE()) {
+        isc_throw(NotUpdateMessage, "received message is not a DDNS update,"
+                  << " received message code is "
+                  << message_.getOpcode().getCode());
+    }
+    // Received message should have QR flag set, which indicates that it is
+    // a RESPONSE.
+    if (getQRFlag() == REQUEST) {
+        isc_throw(InvalidQRFlag, "received message should have QR flag set,"
+                  " to indicate that it is a RESPONSE message; the QR"
+                  << " flag in received message is unset");
+    }
+    // DNS server may copy a Zone record from the query message. Since query
+    // must comprise exactly one Zone record (RFC 2136, section 2.3), the
+    // response message may contain 1 record at most. It may also contain no
+    // records if a server chooses not to copy Zone section.
+    if (getRRCount(SECTION_ZONE) > 1) {
+        isc_throw(InvalidZoneSection, "received message contains "
+                  << getRRCount(SECTION_ZONE) << " Zone records,"
+                  << " it should contain at most 1 record");
+    }
+}
+
+} // namespace d2
+} // namespace isc
+

+ 341 - 0
src/bin/d2/d2_update_message.h

@@ -0,0 +1,341 @@
+// Copyright (C) 2013 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 D2_UPDATE_MESSAGE_H
+#define D2_UPDATE_MESSAGE_H
+
+#include <d2/d2_zone.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception indicating that Zone section contains invalid content.
+///
+/// This exception is thrown when ZONE section of the DNS Update message
+/// is invalid. According to RFC2136, section 2.3, the zone section is
+/// allowed to contain exactly one record. When Request message contains
+/// more records or is empty, this exception is thrown.
+class InvalidZoneSection : public Exception {
+public:
+    InvalidZoneSection(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that QR flag has invalid value.
+///
+/// This exception is thrown when QR flag has invalid value for
+/// the operation performed on the particular message. For instance,
+/// the QR flag must be set to indicate that the given message is
+/// a RESPONSE when @c D2UpdateMessage::fromWire is performed.
+/// The QR flag must be cleared when @c D2UpdateMessage::toWire
+/// is executed.
+class InvalidQRFlag : public Exception {
+public:
+    InvalidQRFlag(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that the parsed message is not DNS Update.
+///
+/// This exception is thrown when decoding the DNS message which is not
+/// a DNS Update.
+class NotUpdateMessage : public Exception {
+public:
+    NotUpdateMessage(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+class D2UpdateMessage;
+
+/// @brief Pointer to the DNS Update Message.
+typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr;
+
+/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
+///
+/// This class represents the DNS Update message. Functions exposed by this
+/// class allow to specify the data sections carried by the message and create
+/// an on-wire format of this message. This class is also used to decode
+/// messages received from the DNS server in the on-wire format.
+///
+/// <b>Design choice:</b> A dedicated class has been created to encapsulate
+/// DNS Update message because existing @c isc::dns::Message is designed to
+/// support regular DNS messages (described in RFC 1035) only. Although DNS
+/// Update has the same format, particular sections serve different purposes.
+/// In order to avoid rewrite of significant portions of @c isc::dns::Message
+/// class, this class is implemented in-terms-of @c isc::dns::Message class
+/// to reuse its functionality where possible.
+class D2UpdateMessage {
+public:
+
+    /// @brief Indicates if the @c D2UpdateMessage object encapsulates Inbound
+    /// or Outbound message.
+    enum Direction {
+        INBOUND,
+        OUTBOUND
+    };
+
+    /// @brief Indicates whether DNS Update message is a REQUEST or RESPONSE.
+    enum QRFlag {
+        REQUEST,
+        RESPONSE
+    };
+
+    /// @brief Identifies sections in the DNS Update Message.
+    ///
+    /// Each message comprises message Header and may contain the following
+    /// sections:
+    /// - ZONE
+    /// - PREREQUISITE
+    /// - UPDATE
+    /// - ADDITIONAL
+    ///
+    /// The enum elements are used by functions such as @c getRRCount (to get
+    /// the number of records in a corresponding section) and @c beginSection
+    /// and @c endSection (to access data in the corresponding section).
+    enum UpdateMsgSection {
+        SECTION_ZONE,
+        SECTION_PREREQUISITE,
+        SECTION_UPDATE,
+        SECTION_ADDITIONAL
+    };
+
+public:
+    /// @brief Constructor used to create an instance of the DNS Update Message
+    /// (either outgoing or incoming).
+    ///
+    /// This constructor is used to create an instance of either incoming or
+    /// outgoing DNS Update message. The boolean argument indicates wheteher it
+    /// is incoming (true) or outgoing (false) message. For incoming messages
+    /// the @c D2UpdateMessage::fromWire function is used to parse on-wire data.
+    /// For outgoing messages, modifier functions should be used to set the
+    /// message contents and @c D2UpdateMessage::toWire function to create
+    /// on-wire data.
+    ///
+    /// @param direction indicates if this is an inbound or outbound message.
+    D2UpdateMessage(const Direction direction = OUTBOUND);
+
+    ///
+    /// @name Copy constructor and assignment operator
+    ///
+    /// Copy constructor and assignment operator are private because we assume
+    /// there will be no need to copy messages on the client side.
+    //@{
+private:
+    D2UpdateMessage(const D2UpdateMessage& source);
+    D2UpdateMessage& operator=(const D2UpdateMessage& source);
+    //@}
+
+public:
+
+    /// @brief Returns enum value indicating if the message is a
+    /// REQUEST or RESPONSE
+    ///
+    /// The returned value is REQUEST if the message is created as an outgoing
+    /// message. In such case the QR flag bit in the message header is cleared.
+    /// The returned value is RESPONSE if the message is created as an incoming
+    /// message and the QR flag bit was set in the received message header.
+    ///
+    /// @return An enum value indicating whether the message is a
+    /// REQUEST or RESPONSE.
+    QRFlag getQRFlag() const;
+
+    /// @brief Returns message ID.
+    ///
+    /// @return message ID.
+    uint16_t getId() const;
+
+    /// @brief Sets message ID.
+    ///
+    /// @param id 16-bit value of the message id.
+    void setId(const uint16_t id);
+
+    /// @brief Returns an object representing message RCode.
+    ///
+    /// @return An object representing message RCode.
+    const dns::Rcode& getRcode() const;
+
+    /// @brief Sets message RCode.
+    ///
+    /// @param rcode An object representing message RCode.
+    void setRcode(const dns::Rcode& rcode);
+
+    /// @brief Returns number of RRsets in the specified message section.
+    ///
+    /// @param section An @c UpdateMsgSection enum specifying a message section
+    /// for which the number of RRsets is to be returned.
+    ///
+    /// @return A number of RRsets in the specified message section.
+    unsigned int getRRCount(const UpdateMsgSection section) const;
+
+    /// @name Functions returning iterators to RRsets in message sections.
+    ///
+    //@{
+    /// @brief Return iterators pointing to the beginning of the list of RRsets,
+    /// which belong to the specified section.
+    ///
+    /// @param section An @c UpdateMsgSection enum specifying a message section
+    /// for which the iterator should be returned.
+    ///
+    /// @return An iterator pointing to the beginning of the list of the
+    /// RRsets, which belong to the specified section.
+    const dns::RRsetIterator beginSection(const UpdateMsgSection section) const;
+
+    /// @brief Return iterators pointing to the end of the list of RRsets,
+    /// which belong to the specified section.
+    ///
+    /// @param section An @c UpdateMsgSection enum specifying a message section
+    /// for which the iterator should be returned.
+    ///
+    /// @return An iterator pointing to the end of the list of the
+    /// RRsets, which belong to the specified section.
+    const dns::RRsetIterator endSection(const UpdateMsgSection section) const;
+    //@}
+
+    /// @brief Sets the Zone record.
+    ///
+    /// This function creates the @c D2Zone object, representing a Zone record
+    /// for the outgoing message. If the Zone record is already set, it is
+    /// replaced by the new record being set by this function. The RRType for
+    /// the record is always SOA.
+    ///
+    /// @param zone A name of the zone being updated.
+    /// @param rrclass A class of the zone record.
+    void setZone(const dns::Name& zone, const dns::RRClass& rrclass);
+
+    /// @brief Returns a pointer to the object representing Zone record.
+    ///
+    /// @return A pointer to the object representing Zone record.
+    D2ZonePtr getZone() const;
+
+    /// @brief Adds an RRset to the specified section.
+    ///
+    /// This function may throw exception if the specified section is
+    /// out of bounds or Zone section update is attempted. For Zone
+    /// section @c D2UpdateMessage::setZone function should be used instead.
+    /// Also, this function expects that @c rrset argument is non-NULL.
+    ///
+    /// @param section A message section where the RRset should be added.
+    /// @param rrset A reference to a RRset which should be added.
+    void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset);
+
+    /// @name Functions to handle message encoding and decoding.
+    ///
+    //@{
+    /// @brief Encode outgoing message into wire format.
+    ///
+    /// This function encodes the DNS Update into the wire format. The format of
+    /// such a message is described in the RFC2136, section 2. Some of the
+    /// sections which belong to encoded message may be empty. If a particular
+    /// message section is empty (does not comprise any RRs), the corresponding
+    /// counter in the message header is set to 0. These counters are: PRCOUNT,
+    /// UPCOUNT, ADCOUNT for the Prerequisites, Update RRs and Additional Data
+    /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
+    /// requires that the message comprises exactly one Zone record.
+    ///
+    /// This function does not guarantee exception safety. However, exceptions
+    /// should be rare because @c D2UpdateMessage class API prevents invalid
+    /// use of the class. The typical case, when this function may throw an
+    /// exception is when this it is called on the object representing
+    /// incoming (instead of outgoing) message. In such case, the QR field
+    /// will be set to RESPONSE, which is invalid setting when calling this
+    /// function.
+    ///
+    /// @param renderer A renderer object used to generate the message wire
+    /// format.
+    void toWire(dns::AbstractMessageRenderer& renderer);
+
+    /// @brief Decode incoming message from the wire format.
+    ///
+    /// This function decodes the DNS Update message stored in the buffer
+    /// specified by the function argument. In the first turn, this function
+    /// parses message header and extracts the section counters: ZOCOUNT,
+    /// PRCOUNT, UPCOUNT and ADCOUNT. Using these counters, function identifies
+    /// message sections, which follow message header. These sections can be
+    /// later accessed using: @c D2UpdateMessage::getZone,
+    /// @c D2UpdateMessage::beginSection and @c D2UpdateMessage::endSection
+    /// functions.
+    ///
+    /// This function is NOT exception safe. It signals message decoding errors
+    /// through exceptions. Message decoding error may occur if the received
+    /// message does not conform to the general DNS Message format, specified in
+    /// RFC 1035. Errors which are specific to DNS Update messages include:
+    /// - Invalid Opcode - not an UPDATE.
+    /// - Invalid QR flag - the QR bit should be set to indicate that the
+    /// message is the server response.
+    /// - The number of records in the Zone section is greater than 1.
+    ///
+    /// @param buffer input buffer, holding DNS Update message to be parsed.
+    void fromWire(isc::util::InputBuffer& buffer);
+    //@}
+
+private:
+    /// Maps the values of the @c UpdateMessageSection field to the
+    /// corresponding values in the @c isc::dns::Message class. This
+    /// mapping is required here because this class uses @c isc::dns::Message
+    /// class to do the actual processing of the DNS Update message.
+    ///
+    /// @param section An enum indicating the section for which the
+    /// corresponding  enum value from @c isc::dns::Message will be returned.
+    ///
+    /// @return The enum value indicating the section in the DNS message
+    /// represented by the @c isc::dns::Message class.
+    static
+    dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section);
+
+    /// @brief Checks received response message for correctness.
+    ///
+    /// This function verifies that the received response from a server is
+    /// correct. Currently this function checks the following:
+    /// - Opcode is 'DNS Update',
+    /// - QR flag is RESPONSE (flag bit is set),
+    /// - Zone section comprises at most one record.
+    ///
+    /// The function will throw exception if any of the conditions above are
+    /// not met.
+    ///
+    /// @throw isc::d2::NotUpdateMessage if invalid Opcode.
+    /// @throw isc::d2::InvalidQRFlag if QR flag is not set to RESPONSE
+    /// @throw isc::d2::InvalidZone section, if Zone section comprises more
+    /// than one record.
+    void validateResponse() const;
+
+    /// @brief An object representing DNS Message which is used by the
+    /// implementation of @c D2UpdateMessage to perform low level.
+    ///
+    /// Declaration of this object pollutes the header with the details
+    /// of @c D2UpdateMessage implementation. It might be cleaner to use
+    /// Pimpl idiom to hide this object in an D2UpdateMessageImpl. However,
+    /// it would bring additional complications to the implementation
+    /// while the benefit would low - this header is not a part of any
+    /// common library. Therefore, if implementation is changed, modification of
+    /// private members of this class in the header has low impact.
+    dns::Message message_;
+
+    /// @brief Holds a pointer to the object, representing Zone in the DNS
+    /// Update.
+    D2ZonePtr zone_;
+
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_UPDATE_MESSAGE_H

+ 249 - 0
src/bin/d2/d2_update_mgr.cc

@@ -0,0 +1,249 @@
+// Copyright (C) 2013 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 <d2/d2_update_mgr.h>
+#include <d2/nc_add.h>
+#include <d2/nc_remove.h>
+
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+namespace isc {
+namespace d2 {
+
+const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
+
+D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+                         IOServicePtr& io_service,
+                         const size_t max_transactions)
+    :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
+    if (!queue_mgr_) {
+        isc_throw(D2UpdateMgrError, "D2UpdateMgr queue manager cannot be null");
+    }
+
+    if (!cfg_mgr_) {
+        isc_throw(D2UpdateMgrError,
+                  "D2UpdateMgr configuration manager cannot be null");
+    }
+
+    if (!io_service_) {
+        isc_throw(D2UpdateMgrError, "IOServicePtr cannot be null");
+    }
+
+    // Use setter to do validation.
+    setMaxTransactions(max_transactions);
+}
+
+D2UpdateMgr::~D2UpdateMgr() {
+    transaction_list_.clear();
+}
+
+void D2UpdateMgr::sweep() {
+    // cleanup finished transactions;
+    checkFinishedTransactions();
+
+    // if the queue isn't empty, find the next suitable job and
+    // start a transaction for it.
+    // @todo - Do we want to queue max transactions? The logic here will only
+    // start one new transaction per invocation.  On the other hand a busy
+    // system will generate many IO events and this method will be called
+    // frequently.  It will likely achieve max transactions quickly on its own.
+    if (getQueueCount() > 0)  {
+        if (getTransactionCount() >= max_transactions_) {
+            LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA,
+                      DHCP_DDNS_AT_MAX_TRANSACTIONS).arg(getQueueCount())
+                      .arg(getMaxTransactions());
+
+            return;
+        }
+
+        // We are not at maximum transactions, so pick and start the next job.
+        pickNextJob();
+    }
+}
+
+void
+D2UpdateMgr::checkFinishedTransactions() {
+    // Cycle through transaction list and do whatever needs to be done
+    // for finished transactions.
+    // At the moment all we do is remove them from the list. This is likely
+    // to expand as DHCP_DDNS matures.
+    // NOTE: One must use postfix increments of the iterator on the calls
+    // to erase.  This replaces the old iterator which becomes invalid by the
+    // erase with a the next valid iterator.  Prefix incrementing will not
+    // work.
+    TransactionList::iterator it = transaction_list_.begin();
+    while (it != transaction_list_.end()) {
+        NameChangeTransactionPtr trans = (*it).second;
+        if (trans->isModelDone()) {
+            // @todo  Addtional actions based on NCR status could be
+            // performed here.
+            transaction_list_.erase(it++);
+        } else {
+            ++it;
+        }
+    }
+}
+
+void D2UpdateMgr::pickNextJob() {
+    // Start at the front of the queue, looking for the first entry for
+    // which no transaction is in progress.  If we find an eligible entry
+    // remove it from the queue and  make a transaction for it.
+    // Requests and transactions are associated by DHCID.  If a request has
+    // the same DHCID as a transaction, they are presumed to be for the same
+    // "end user".
+    size_t queue_count = getQueueCount();
+    for (size_t index = 0; index < queue_count; ++index) {
+        dhcp_ddns::NameChangeRequestPtr found_ncr = queue_mgr_->peekAt(index);
+        if (!hasTransaction(found_ncr->getDhcid())) {
+            queue_mgr_->dequeueAt(index);
+            makeTransaction(found_ncr);
+            return;
+        }
+    }
+
+    // There were no eligible jobs. All of the current DHCIDs already have
+    // transactions pending.
+    LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA, DHCP_DDNS_NO_ELIGIBLE_JOBS)
+              .arg(getQueueCount()).arg(getTransactionCount());
+}
+
+void
+D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) {
+    // First lets ensure there is not a transaction in progress for this
+    // DHCID. (pickNextJob should ensure this, as it is the only real caller
+    // but for safety's sake we'll check).
+    const TransactionKey& key = next_ncr->getDhcid();
+    if (findTransaction(key) != transactionListEnd()) {
+        // This is programmatic error.  Caller(s) should be checking this.
+        isc_throw(D2UpdateMgrError, "Transaction already in progress for: "
+            << key.toStr());
+    }
+
+    // If forward change is enabled, match to forward servers.
+    DdnsDomainPtr forward_domain;
+    if (next_ncr->isForwardChange()) {
+        bool matched = cfg_mgr_->matchForward(next_ncr->getFqdn(),
+                                             forward_domain);
+        // Could not find a match for forward DNS server. Log it and get out.
+        // This has the net affect of dropping the request on the floor.
+        if (!matched) {
+            LOG_ERROR(dctl_logger, DHCP_DDNS_NO_FWD_MATCH_ERROR)
+                      .arg(next_ncr->getFqdn());
+            return;
+        }
+    }
+
+    // If reverse change is enabled, match to reverse servers.
+    DdnsDomainPtr reverse_domain;
+    if (next_ncr->isReverseChange()) {
+        bool matched = cfg_mgr_->matchReverse(next_ncr->getIpAddress(),
+                                              reverse_domain);
+        // Could not find a match for reverse DNS server. Log it and get out.
+        // This has the net affect of dropping the request on the floor.
+        if (!matched) {
+            LOG_ERROR(dctl_logger, DHCP_DDNS_NO_REV_MATCH_ERROR)
+                      .arg(next_ncr->getIpAddress());
+            return;
+        }
+    }
+
+    // We matched to the required servers, so construct the transaction.
+    // @todo If multi-threading is implemented, one would pass in an
+    // empty IOServicePtr, rather than our instance value.  This would cause
+    // the transaction to instantiate its own, separate IOService to handle
+    // the transaction's IO.
+    NameChangeTransactionPtr trans;
+    if (next_ncr->getChangeType() == dhcp_ddns::CHG_ADD) {
+        trans.reset(new NameAddTransaction(io_service_, next_ncr,
+                                           forward_domain, reverse_domain));
+    } else {
+        trans.reset(new NameRemoveTransaction(io_service_, next_ncr,
+                                              forward_domain, reverse_domain));
+    }
+
+    // Add the new transaction to the list.
+    transaction_list_[key] = trans;
+
+    // Start it.
+    trans->startTransaction();
+}
+
+TransactionList::iterator
+D2UpdateMgr::findTransaction(const TransactionKey& key) {
+    return (transaction_list_.find(key));
+}
+
+bool
+D2UpdateMgr::hasTransaction(const TransactionKey& key) {
+   return (findTransaction(key) != transactionListEnd());
+}
+
+void
+D2UpdateMgr::removeTransaction(const TransactionKey& key) {
+    TransactionList::iterator pos = findTransaction(key);
+    if (pos != transactionListEnd()) {
+        transaction_list_.erase(pos);
+    }
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListBegin() {
+    return (transaction_list_.begin());
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListEnd() {
+    return (transaction_list_.end());
+}
+
+void
+D2UpdateMgr::clearTransactionList() {
+    // @todo for now this just wipes them out. We might need something
+    // more elegant, that allows a cancel first.
+    transaction_list_.clear();
+}
+
+void
+D2UpdateMgr::setMaxTransactions(const size_t new_trans_max) {
+    // Obviously we need at room for at least one transaction.
+    if (new_trans_max < 1) {
+        isc_throw(D2UpdateMgrError, "D2UpdateMgr"
+                  " maximum transactions limit must be greater than zero");
+    }
+
+    // Do not allow the list maximum to be set to less then current list size.
+    if (new_trans_max < getTransactionCount()) {
+        isc_throw(D2UpdateMgrError, "D2UpdateMgr maximum transaction limit "
+                  "cannot be less than the current transaction count :"
+                  << getTransactionCount());
+    }
+
+    max_transactions_ = new_trans_max;
+}
+
+size_t
+D2UpdateMgr::getQueueCount() const {
+    return (queue_mgr_->getQueueSize());
+}
+
+size_t
+D2UpdateMgr::getTransactionCount() const {
+    return (transaction_list_.size());
+}
+
+
+} // namespace isc::d2
+} // namespace isc

+ 256 - 0
src/bin/d2/d2_update_mgr.h

@@ -0,0 +1,256 @@
+// Copyright (C) 2013 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 D2_UPDATE_MGR_H
+#define D2_UPDATE_MGR_H
+
+/// @file d2_update_mgr.h This file defines the class D2UpdateMgr.
+
+#include <exceptions/exceptions.h>
+#include <d2/d2_asio.h>
+#include <d2/d2_log.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/nc_trans.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the update manager encounters a general error.
+class D2UpdateMgrError : public isc::Exception {
+public:
+    D2UpdateMgrError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines a list of transactions.
+typedef std::map<TransactionKey, NameChangeTransactionPtr> TransactionList;
+
+/// @brief D2UpdateMgr creates and manages update transactions.
+///
+/// D2UpdateMgr is the DHCP_DDNS task master, instantiating and then supervising
+/// transactions that execute the DNS updates needed to fulfill the requests
+/// (NameChangeRequests) received from DHCP_DDNS clients (e.g. DHCP servers).
+///
+/// D2UpdateMgr uses the services of D2QueueMgr to monitor the queue of
+/// NameChangeRequests and select and dequeue requests for processing.
+/// When request is dequeued for processing it is removed from the queue and
+/// wrapped in NameChangeTransaction and added to the D2UpdateMgr's list of
+/// transactions.
+///
+/// As part of the process of forming transactions, D2UpdateMgr matches each
+/// request with the appropriate list of DNS servers.  This matching is  based
+/// upon request attributes, primarily the FQDN and update direction (forward
+/// or reverse).  D2UpdateMgr uses the services of D2CfgMgr to match requests
+/// to DNS server lists.
+///
+/// Once created, each transaction is responsible for carrying out the steps
+/// required to fulfill its specific request.  These steps typically consist of
+/// one or more DNS packet exchanges with the appropriate DNS server.  As
+/// transactions complete,  D2UpdateMgr removes them from the transaction list,
+/// replacing them with new transactions.
+///
+/// D2UpdateMgr carries out each of the above steps, from with a method called
+/// sweep().  This method is intended to be called as IO events complete.
+/// The upper layer(s) are responsible for calling sweep in a timely and cyclic
+/// manner.
+///
+class D2UpdateMgr : public boost::noncopyable {
+public:
+    /// @brief Maximum number of concurrent transactions
+    /// NOTE that 32 is an arbitrary choice picked for the initial
+    /// implementation.
+    static const size_t MAX_TRANSACTIONS_DEFAULT = 32;
+
+    // @todo This structure is not yet used. It is here in anticipation of
+    // enabled statistics capture.
+    struct Stats {
+        uint64_t start_time_;
+        uint64_t stop_time_;
+        uint64_t update_count_;
+        uint64_t min_update_time_;
+        uint64_t max_update_time_;
+        uint64_t server_rejects_;
+        uint64_t server_timeouts_;
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param queue_mgr reference to the queue manager receiving requests
+    /// @param cfg_mgr reference to the configuration manager
+    /// @param io_service IO service used by the upper layer(s) to manage
+    /// IO events
+    /// @param max_transactions the maximum number of concurrent transactions
+    ///
+    /// @throw D2UpdateMgrError if either the queue manager or configuration
+    /// managers are NULL, or max transactions is less than one.
+    D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+                IOServicePtr& io_service,
+                const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT);
+
+    /// @brief Destructor
+    virtual ~D2UpdateMgr();
+
+    /// @brief Check current transactions; start transactions for new requests.
+    ///
+    /// This method is the primary public interface used by the upper layer. It
+    /// should be called as IO events complete.  During each invocation it does
+    /// the following:
+    ///
+    /// - Removes all completed transactions from the transaction list.
+    ///
+    /// - If the request queue is not empty and the number of transactions
+    /// in the transaction list has not reached maximum allowed, then select
+    /// a request from the queue.
+    ///
+    /// - If a request was selected, start a new transaction for it and
+    /// add the transaction to the list of transactions.
+    void sweep();
+
+protected:
+    /// @brief Performs post-completion cleanup on completed transactions.
+    ///
+    /// Iterates through the list of transactions and removes any that have
+    /// reached completion.  This method may expand in complexity or even
+    /// disappear altogether as the implementation matures.
+    void checkFinishedTransactions();
+
+    /// @brief Starts a transaction for the next eligible request in the queue.
+    ///
+    /// This method will scan the request queue for the next request to
+    /// dequeue.  The current implementation starts at the front of the queue
+    /// and looks for the first request for whose DHCID there is no current
+    /// transaction in progress.
+    ///
+    /// If a request is selected, it is removed from the queue and transaction
+    /// is constructed for it.
+    ///
+    /// It is possible that no such request exists, though this is likely to be
+    /// rather rare unless a system is frequently seeing requests for the same
+    /// clients in quick succession.
+    void pickNextJob();
+
+    /// @brief Create a new transaction for the given request.
+    ///
+    /// This method will attempt to match the request to a list of configured
+    /// DNS servers.  If a list of servers is found, it will instantiate a
+    /// transaction for it and add the transaction to the transaction list.
+    ///
+    /// If no servers are found that match the request, this constitutes a
+    /// configuration error.  The error will be logged and the request will
+    /// be discarded.
+    ///
+    /// @param ncr the NameChangeRequest for which to create a transaction.
+    ///
+    /// @throw D2UpdateMgrError if a transaction for this DHCID already
+    /// exists. Note this would be programmatic error.
+    void makeTransaction(isc::dhcp_ddns::NameChangeRequestPtr& ncr);
+
+public:
+    /// @brief Gets the UpdateMgr's IOService.
+    ///
+    /// @return returns a reference to the IOService
+    const IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Returns the maximum number of concurrent transactions.
+    size_t getMaxTransactions() const {
+        return (max_transactions_);
+    }
+
+    /// @brief Sets the maximum number of entries allowed in the queue.
+    ///
+    /// @param max_transactions is the new maximum number of transactions
+    ///
+    /// @throw Throws D2QueueMgrError if the new value is less than one or if
+    /// the new value is less than the number of entries currently in the
+    /// queue.
+    void setMaxTransactions(const size_t max_transactions);
+
+    /// @brief Search the transaction list for the given key.
+    ///
+    /// @param key the transaction key value for which to search.
+    ///
+    /// @return Iterator pointing to the entry found.  If no entry is
+    /// it will point to the list end position.
+    TransactionList::iterator findTransaction(const TransactionKey& key);
+
+    /// @brief Returns the transaction list end position.
+    TransactionList::iterator transactionListEnd();
+
+    /// @brief Returns the transaction list beg position.
+    TransactionList::iterator transactionListBegin();
+
+    /// @brief Convenience method that checks transaction list for the given key
+    ///
+    /// @param key the transaction key value for which to search.
+    ///
+    /// @return Returns true if the key is found within the list, false
+    /// otherwise.
+    bool hasTransaction(const TransactionKey& key);
+
+    /// @brief Removes the entry pointed to by key from the transaction list.
+    ///
+    /// Removes the entry referred to by key if it exists.  It has no effect
+    /// if the entry is not found.
+    ///
+    /// @param key of the transaction to remove
+    void removeTransaction(const TransactionKey& key);
+
+    /// @brief Immediately discards all entries in the transaction list.
+    ///
+    /// @todo For now this just wipes them out. We might need something
+    /// more elegant, that allows a cancel first.
+    void clearTransactionList();
+
+    /// @brief Convenience method that returns the number of requests queued.
+    size_t getQueueCount() const;
+
+    /// @brief Returns the current number of transactions.
+    size_t getTransactionCount() const;
+
+private:
+    /// @brief Pointer to the queue manager.
+    D2QueueMgrPtr queue_mgr_;
+
+    /// @brief Pointer to the configuration manager.
+    D2CfgMgrPtr cfg_mgr_;
+
+    /// @brief Primary IOService instance.
+    /// This is the IOService that the upper layer(s) use for IO events, such
+    /// as shutdown and configuration commands.  It is the IOService that is
+    /// passed into transactions to manager their IO events.
+    /// (For future reference, multi-threaded transactions would each use their
+    /// own IOService instance.)
+    IOServicePtr io_service_;
+
+    /// @brief Maximum number of concurrent transactions.
+    size_t max_transactions_;
+
+    /// @brief List of transactions.
+    TransactionList transaction_list_;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgr> D2UpdateMgrPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif

+ 36 - 0
src/bin/d2/d2_zone.cc

@@ -0,0 +1,36 @@
+// Copyright (C) 2013  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 <d2/d2_zone.h>
+
+namespace isc {
+namespace d2 {
+
+D2Zone::D2Zone(const dns::Name& name, const dns::RRClass& rrclass)
+    : name_(name), rrclass_(rrclass) {
+}
+
+std::string D2Zone::toText() const {
+    return (name_.toText() + " " + rrclass_.toText() + " SOA\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Zone& zone) {
+    os << zone.toText();
+    return (os);
+}
+
+} // namespace d2
+} // namespace isc
+

+ 117 - 0
src/bin/d2/d2_zone.h

@@ -0,0 +1,117 @@
+// Copyright (C) 2013 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 D2_ZONE_H
+#define D2_ZONE_H
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief The @c D2Zone encapsulates the Zone section in DNS Update message.
+///
+/// This class is used by the @c D2UpdateMessage to encapsulate the Zone section
+/// of the DNS Update message. Class members hold corresponding values of
+/// section's fields: NAME, CLASS. This class does not hold the RTYPE field
+/// value because RTYPE is always equal to SOA for DNS Update message (see
+/// RFC 2136, section 2.3).
+///
+/// Note, that this @c D2Zone class neither exposes functions to decode messages
+/// from wire format nor to encode to wire format. This is not needed, because
+/// @c isc::d2::D2UpdateMessage class uses @c D2Zone only to return the parsed
+/// Zone information to the caller. Internally, D2UpdateMessage parses and
+/// stores Zone section using @c isc::dns::Question class, and the @c toWire
+/// and @c fromWire functions of the @c isc::dns::Question class are used.
+class D2Zone {
+public:
+    /// @brief Constructor from Name and RRClass.
+    ///
+    /// @param name The name of the Zone.
+    /// @param rrclass The RR class of the Zone.
+    D2Zone(const dns::Name& name, const dns::RRClass& rrclass);
+
+    ///
+    /// @name Getters
+    ///
+    //@{
+    /// @brief Returns the Zone name.
+    ///
+    /// @return A reference to the Zone name.
+    const dns::Name& getName() const { return (name_); }
+
+    /// @brief Returns the Zone class.
+    ///
+    /// @return A reference to the Zone class.
+    const dns::RRClass& getClass() const { return (rrclass_); }
+    //@}
+
+    /// @brief Returns text representation of the Zone.
+    ///
+    /// This function concatenates the name of the Zone, Class and Type.
+    /// The type is always SOA.
+    ///
+    /// @return A text representation of the Zone.
+    std::string toText() const;
+
+    ///
+    /// @name Comparison Operators
+    ///
+    //@{
+    /// @brief Equality operator to compare @c D2Zone objects in query and
+    /// response messages.
+    ///
+    /// @param rhs Zone to compare against.
+    ///
+    /// @return true if name and class are equal, false otherwise.
+    bool operator==(const D2Zone& rhs) const {
+        return ((rrclass_ == rhs.rrclass_) && (name_ == rhs.name_));
+    }
+
+    /// @brief Inequality operator to compare @c D2Zone objects in query and
+    /// response messages.
+    ///
+    /// @param rhs Zone to compare against.
+    ///
+    /// @return true if any of name or class are unequal, false otherwise.
+    bool operator!=(const D2Zone& rhs) const {
+        return (!operator==(rhs));
+    }
+    //@}
+
+private:
+    dns::Name name_;       ///< Holds the Zone name.
+    dns::RRClass rrclass_; ///< Holds the Zone class.
+};
+
+typedef boost::shared_ptr<D2Zone> D2ZonePtr;
+
+/// @brief Insert the @c D2Zone as a string into stream.
+///
+/// @param os A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param zone A reference to the @c D2Zone object output by the
+/// operation.
+///
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const D2Zone& zone);
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_ZONE_H

+ 240 - 0
src/bin/d2/d_cfg_mgr.cc

@@ -0,0 +1,240 @@
+// Copyright (C) 2013 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 <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <dhcp/libdhcp++.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** DCfgContextBase  *************************
+
+DCfgContextBase::DCfgContextBase():
+        boolean_values_(new BooleanStorage()),
+        uint32_values_(new Uint32Storage()),
+        string_values_(new StringStorage()) {
+    }
+
+DCfgContextBase::DCfgContextBase(const DCfgContextBase& rhs):
+        boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+        uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+        string_values_(new StringStorage(*(rhs.string_values_))) {
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, bool& value, bool optional) {
+    try {
+        value = boolean_values_->getParam(name);
+    } catch (DhcpConfigError& ex) {
+        // If the parameter is not optional, re-throw the exception.
+        if (!optional) {
+            throw;
+        }
+    }
+}
+
+
+void
+DCfgContextBase::getParam(const std::string& name, uint32_t& value,
+                          bool optional) {
+    try {
+        value = uint32_values_->getParam(name);
+    } catch (DhcpConfigError& ex) {
+        // If the parameter is not optional, re-throw the exception.
+        if (!optional) {
+            throw;
+        }
+    }
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, std::string& value,
+                          bool optional) {
+    try {
+        value = string_values_->getParam(name);
+    } catch (DhcpConfigError& ex) {
+        // If the parameter is not optional, re-throw the exception.
+        if (!optional) {
+            throw;
+        }
+    }
+}
+
+DCfgContextBase::~DCfgContextBase() {
+}
+
+// *********************** DCfgMgrBase  *************************
+
+DCfgMgrBase::DCfgMgrBase(DCfgContextBasePtr context)
+    : parse_order_(), context_(context) {
+    if (!context_) {
+        isc_throw(DCfgMgrBaseError, "DCfgMgrBase ctor: context cannot be NULL");
+    }
+}
+
+DCfgMgrBase::~DCfgMgrBase() {
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
+    LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+                DCTL_CONFIG_START).arg(config_set->str());
+
+    if (!config_set) {
+        return (isc::config::createAnswer(1,
+                                    std::string("Can't parse NULL config")));
+    }
+
+    // The parsers implement data inheritance by directly accessing
+    // configuration context. For this reason the data parsers must store
+    // the parsed data into context immediately. This may cause data
+    // inconsistency if the parsing operation fails after the context has been
+    // modified. We need to preserve the original context here
+    // so as we can rollback changes when an error occurs.
+    DCfgContextBasePtr original_context = context_->clone();
+
+    // Answer will hold the result returned to the caller.
+    ConstElementPtr answer;
+
+    // Holds the name of the element being parsed.
+    std::string element_id;
+
+    try {
+        // Grab a map of element_ids and their data values from the new
+        // configuration set.
+        const std::map<std::string, ConstElementPtr>& values_map =
+                                                        config_set->mapValue();
+
+        // Use a pre-ordered list of element ids to parse the elements in a
+        // specific order if the list (parser_order_) is not empty; otherwise
+        // elements are parsed in the order the value_map presents them.
+
+        if (!parse_order_.empty()) {
+            // For each element_id in the parse order list, look for it in the
+            // value map.  If the element exists in the map, pass it and it's
+            // associated data in for parsing.
+            // If there is no matching entry in the value map an error is
+            // thrown.  Note, that elements tagged as "optional" from the user
+            // perspective must still have default or empty entries in the
+            // configuration set to be parsed.
+            int parsed_count = 0;
+            std::map<std::string, ConstElementPtr>::const_iterator it;
+            BOOST_FOREACH(element_id, parse_order_) {
+                it = values_map.find(element_id);
+                if (it != values_map.end()) {
+                    ++parsed_count;
+                    buildAndCommit(element_id, it->second);
+                }
+                else {
+                    LOG_ERROR(dctl_logger, DCTL_ORDER_NO_ELEMENT)
+                              .arg(element_id);
+                    isc_throw(DCfgMgrBaseError, "Element:" << element_id <<
+                              " is listed in the parse order but is not "
+                              " present in the configuration");
+                }
+            }
+
+            // NOTE: When using ordered parsing, the parse order list MUST
+            // include every possible element id that the value_map may contain.
+            // Entries in the map that are not in the parse order, would not be
+            // parsed. For now we will flag this as a programmatic error.  One
+            // could attempt to adjust for this, by identifying such entries
+            // and parsing them either first or last but which would be correct?
+            // Better to hold the engineer accountable.  So, if we parsed none
+            // or we parsed fewer than are in the map; then either the parse i
+            // order is incomplete OR the map has unsupported values.
+            if (!parsed_count ||
+                (parsed_count && ((parsed_count + 1) < values_map.size()))) {
+                LOG_ERROR(dctl_logger, DCTL_ORDER_ERROR);
+                isc_throw(DCfgMgrBaseError,
+                        "Configuration contains elements not in parse order");
+            }
+        } else {
+            // Order doesn't matter so iterate over the value map directly.
+            // Pass each element and it's associated data in to be parsed.
+            ConfigPair config_pair;
+            BOOST_FOREACH(config_pair, values_map) {
+                element_id = config_pair.first;
+                buildAndCommit(element_id, config_pair.second);
+            }
+        }
+
+        // Everything was fine. Configuration set processed successfully.
+        LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
+        answer = isc::config::createAnswer(0, "Configuration committed.");
+
+    } catch (const isc::Exception& ex) {
+        LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(element_id).arg(ex.what());
+        answer = isc::config::createAnswer(1,
+                     string("Configuration parsing failed: ") + ex.what());
+
+        // An error occurred, so make sure that we restore original context.
+        context_ = original_context;
+        return (answer);
+    }
+
+    return (answer);
+}
+
+void DCfgMgrBase::buildAndCommit(std::string& element_id,
+                                 isc::data::ConstElementPtr value) {
+    // Call derivation's implementation to create the appropriate parser
+    // based on the element id.
+    ParserPtr parser = createConfigParser(element_id);
+    if (!parser) {
+        isc_throw(DCfgMgrBaseError, "Could not create parser");
+    }
+
+    try {
+        // Invoke the parser's build method passing in the value. This will
+        // "convert" the Element form of value into the actual data item(s)
+        // and store them in parser's local storage.
+        parser->build(value);
+
+        // Invoke the parser's commit method. This "writes" the the data
+        // item(s) stored locally by the parser into the context.  (Note that
+        // parsers are free to do more than update the context, but that is an
+        // nothing something we are concerned with here.)
+        parser->commit();
+    } catch (const isc::Exception& ex) {
+        isc_throw(DCfgMgrBaseError,
+                  "Could not build and commit: " << ex.what());
+    } catch (...) {
+        isc_throw(DCfgMgrBaseError, "Non-ISC exception occurred");
+    }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+

+ 331 - 0
src/bin/d2/d_cfg_mgr.h

@@ -0,0 +1,331 @@
+// Copyright (C) 2013 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 D_CFG_MGR_H
+#define D_CFG_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the configuration manager encounters an error.
+class DCfgMgrBaseError : public isc::Exception {
+public:
+    DCfgMgrBaseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+class DCfgContextBase;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
+
+/// @brief Abstract class that implements a container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other context specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// The base class supports storage for a small set of simple data types.
+/// Derivations simply add additional storage as needed.  Note that this class
+/// declares the pure virtual clone() method, its copy constructor is protected,
+/// and its copy operator is inaccessible.  Derivations must supply an
+/// implementation of clone that calls the base class copy constructor.
+/// This allows the management class to perform context backup and restoration
+/// without derivation specific knowledge using logic like
+/// the following:
+///
+///    // Make a backup copy
+///    DCfgContextBasePtr backup_copy(context_->clone());
+///    :
+///    // Restore from backup
+///    context_ = backup_copy;
+///
+class DCfgContextBase {
+public:
+    /// @brief Indicator that a configuration parameter is optional.
+    static const bool OPTIONAL = true;
+    static const bool REQUIRED = false;
+
+    /// @brief Constructor
+    DCfgContextBase();
+
+    /// @brief Destructor
+    virtual ~DCfgContextBase();
+
+    /// @brief Fetches the value for a given boolean configuration parameter
+    /// from the context.
+    ///
+    /// @param name is the name of the parameter to retrieve.
+    /// @param value is an output parameter in which to return the retrieved
+    /// value.
+    /// @param optional if true, the parameter is optional and the method
+    /// will not throw if the parameter is not found in the context. The
+    /// contents of the output parameter, value, will not be altered.
+    /// It defaults to false if not specified.
+    /// @throw throws DhcpConfigError if the context does not contain the
+    /// parameter and optional is false.
+    void getParam(const std::string& name, bool& value, bool optional=false);
+
+    /// @brief Fetches the value for a given uint32_t configuration parameter
+    /// from the context.
+    ///
+    /// @param name is the name of the parameter to retrieve.
+    /// @param value is an output parameter in which to return the retrieved
+    /// value.
+    /// @param optional if true, the parameter is optional and the method
+    /// will not throw if the parameter is not found in the context. The
+    /// contents of the output parameter, value, will not be altered.
+    /// @throw throws DhcpConfigError if the context does not contain the
+    /// parameter and optional is false.
+    void getParam(const std::string& name, uint32_t& value,
+                 bool optional=false);
+
+    /// @brief Fetches the value for a given string configuration parameter
+    /// from the context.
+    ///
+    /// @param name is the name of the parameter to retrieve.
+    /// @param value is an output parameter in which to return the retrieved
+    /// value.
+    /// @param optional if true, the parameter is optional and the method
+    /// will not throw if the parameter is not found in the context. The
+    /// contents of the output parameter, value, will not be altered.
+    /// @throw throws DhcpConfigError if the context does not contain the
+    /// parameter and optional is false.
+    void getParam(const std::string& name, std::string& value,
+                  bool optional=false);
+
+    /// @brief Fetches the Boolean Storage. Typically used for passing
+    /// into parsers.
+    ///
+    /// @return returns a pointer to the Boolean Storage.
+    isc::dhcp::BooleanStoragePtr getBooleanStorage() {
+        return (boolean_values_);
+    }
+
+    /// @brief Fetches the uint32 Storage. Typically used for passing
+    /// into parsers.
+    ///
+    /// @return returns a pointer to the uint32 Storage.
+    isc::dhcp::Uint32StoragePtr getUint32Storage() {
+        return (uint32_values_);
+    }
+
+    /// @brief Fetches the string Storage. Typically used for passing
+    /// into parsers.
+    ///
+    /// @return returns a pointer to the string Storage.
+    isc::dhcp::StringStoragePtr getStringStorage() {
+        return (string_values_);
+    }
+
+    /// @brief Creates a clone of this context object.
+    ///
+    /// As mentioned in the the class brief, derivation must supply an
+    /// implementation that initializes the base class storage as well as its
+    /// own.  Typically the derivation's clone method would return the result
+    /// of passing  "*this" into its own copy constructor:
+    ///
+    /// @code
+    /// class DStubContext : public DCfgContextBase {
+    /// public:
+    ///  :
+    ///     // Clone calls its own copy constructor
+    ///     virtual DCfgContextBasePtr clone() {
+    ///         return (DCfgContextBasePtr(new DStubContext(*this)));
+    ///     }
+    ///
+    ///     // Note that the copy constructor calls the base class copy ctor
+    ///     // then initializes its additional storage.
+    ///     DStubContext(const DStubContext& rhs) : DCfgContextBase(rhs),
+    ///         extra_values_(new Uint32Storage(*(rhs.extra_values_))) {
+    ///     }
+    ///  :
+    ///    // Here's the derivation's additional storage.
+    ///    isc::dhcp::Uint32StoragePtr extra_values_;
+    ///  :
+    /// @endcode
+    ///
+    /// @return returns a pointer to the new clone.
+    virtual DCfgContextBasePtr clone() = 0;
+
+protected:
+    /// @brief Copy constructor for use by derivations in clone().
+    DCfgContextBase(const DCfgContextBase& rhs);
+
+private:
+    /// @brief Private assignment operator to avoid potential for slicing.
+    DCfgContextBase& operator=(const DCfgContextBase& rhs);
+
+    /// @brief Storage for boolean parameters.
+    isc::dhcp::BooleanStoragePtr boolean_values_;
+
+    /// @brief Storage for uint32 parameters.
+    isc::dhcp::Uint32StoragePtr uint32_values_;
+
+    /// @brief Storage for string parameters.
+    isc::dhcp::StringStoragePtr string_values_;
+};
+
+/// @brief Defines an unsorted, list of string Element IDs.
+typedef std::vector<std::string> ElementIdList;
+
+/// @brief Configuration Manager
+///
+/// DCfgMgrBase is an abstract class that provides the mechanisms for managing
+/// an application's configuration.  This includes services for parsing sets of
+/// configuration values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.  It is intended to be the worker
+/// class which is handed a set of configuration values to process by upper
+/// application management layers.
+///
+/// The class presents a public method for receiving new configurations,
+/// parseConfig.  This method coordinates the parsing effort as follows:
+///
+/// @code
+///    make backup copy of configuration context
+///    for each top level element in new configuration
+///        get derivation-specific parser for element
+///        run parser
+///        update context with parsed results
+///        break on error
+///
+///    if an error occurred
+///        restore configuration context from backup
+/// @endcode
+///
+/// After making a backup of the current context, it iterates over the top-level
+/// elements in the new configuration.  The order in which the elements are
+/// processed is either:
+///
+///    1. Natural order presented by the configuration set
+///    2. Specific order determined by a list of element ids
+///
+/// This allows a derivation to specify the order in which its elements are
+/// parsed if there are dependencies between elements.
+///
+/// To parse a given element, its id is passed into createConfigParser,
+/// which returns an instance of the appropriate parser.  This method is
+/// abstract so the derivation's implementation determines the type of parser
+/// created. This isolates the knowledge of specific element ids and which
+/// application specific parsers to derivation.
+///
+/// Once the parser has been created, it is used to parse the data value
+/// associated with the element id and update the context with the parsed
+/// results.
+///
+/// In the event that an error occurs, parsing is halted and the
+/// configuration context is restored from backup.
+class DCfgMgrBase {
+public:
+    /// @brief Constructor
+    ///
+    /// @param context is a pointer to the configuration context the manager
+    /// will use for storing parsed results.
+    ///
+    /// @throw throws DCfgMgrBaseError if context is null
+    DCfgMgrBase(DCfgContextBasePtr context);
+
+    /// @brief Destructor
+    virtual ~DCfgMgrBase();
+
+    /// @brief Acts as the receiver of new configurations and coordinates
+    /// the parsing as described in the class brief.
+    ///
+    /// @param config_set is a set of configuration elements to parsed.
+    ///
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
+                                           config_set);
+
+    /// @brief Adds a given element id to the end of the parse order list.
+    ///
+    /// The order in which elements are retrieved from this is the order in
+    /// which they are added to the list. Derivations should use this method
+    /// to populate the parse order as part of their constructor.
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    void addToParseOrder(const std::string& element_id){
+        parse_order_.push_back(element_id);
+    }
+
+    /// @brief Fetches the parse order list.
+    ///
+    /// @return returns a const reference to the list.
+    const ElementIdList& getParseOrder() const {
+        return (parse_order_);
+    }
+
+    /// @brief Fetches the configuration context.
+    ///
+    /// @return returns a pointer reference to the configuration context.
+    DCfgContextBasePtr& getContext() {
+        return (context_);
+    }
+
+protected:
+    /// @brief  Create a parser instance based on an element id.
+    ///
+    /// Given an element_id returns an instance of the appropriate parser.
+    /// This method is abstract, isolating any direct knowledge of element_ids
+    /// and parsers to within the application-specific derivation.
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    ///
+    /// @return returns a ParserPtr to the parser instance.
+    /// @throw throws DCfgMgrBaseError if an error occurs.
+    virtual isc::dhcp::ParserPtr
+    createConfigParser(const std::string& element_id) = 0;
+
+private:
+
+    /// @brief Parse a configuration element.
+    ///
+    /// Given an element_id and data value, instantiate the appropriate
+    /// parser,  parse the data value, and commit the results.
+    ///
+    /// @param element_id is the string name of the element as it will appear
+    /// in the configuration set.
+    /// @param value is the data value to be parsed and associated with
+    /// element_id.
+    ///
+    /// @throw throws DCfgMgrBaseError if an error occurs.
+    void buildAndCommit(std::string& element_id,
+                        isc::data::ConstElementPtr value);
+
+    /// @brief A list of element ids which specifies the element parsing order.
+    ///
+    /// If the list is empty, the natural order in the configuration set
+    /// it used.
+    ElementIdList parse_order_;
+
+    /// @brief Pointer to the configuration context instance.
+    DCfgContextBasePtr context_;
+};
+
+/// @brief Defines a shared pointer to DCfgMgrBase.
+typedef boost::shared_ptr<DCfgMgrBase> DCfgMgrBasePtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D_CFG_MGR_H

+ 423 - 0
src/bin/d2/d_controller.cc

@@ -0,0 +1,423 @@
+// Copyright (C) 2013  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 <d2/d2_log.h>
+#include <d2/d_controller.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <sstream>
+
+namespace isc {
+namespace d2 {
+
+DControllerBasePtr DControllerBase::controller_;
+
+// Note that the constructor instantiates the controller's primary IOService.
+DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
+    : app_name_(app_name), bin_name_(bin_name), stand_alone_(false),
+      verbose_(false), spec_file_name_(""),
+      io_service_(new isc::asiolink::IOService()){
+}
+
+
+void
+DControllerBase::setController(const DControllerBasePtr& controller) {
+    if (controller_) {
+        // This shouldn't happen, but let's make sure it can't be done.
+        // It represents a programmatic error.
+        isc_throw (DControllerBaseError,
+                "Multiple controller instances attempted.");
+    }
+
+    controller_ = controller;
+}
+
+void
+DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
+    // Step 1 is to parse the command line arguments.
+    try {
+        parseArgs(argc, argv);
+    } catch (const InvalidUsage& ex) {
+        usage(ex.what());
+        throw; // rethrow it
+    }
+
+    // Do not initialize logger here if we are running unit tests. It would
+    // replace an instance of unit test specific logger.
+    if (!test_mode) {
+        // Now that we know what the mode flags are, we can init logging.
+        // If standalone is enabled, do not buffer initial log messages
+        isc::log::initLogger(bin_name_,
+                             ((verbose_ && stand_alone_)
+                              ? isc::log::DEBUG : isc::log::INFO),
+                             isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
+    }
+
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
+              .arg(app_name_).arg(getpid());
+    try {
+        // Step 2 is to create and initialize the application process object.
+        initProcess();
+    } catch (const std::exception& ex) {
+        LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
+                  .arg(app_name_).arg(ex.what());
+        isc_throw (ProcessInitError,
+                   "Application Process initialization failed: " << ex.what());
+    }
+
+    // Next we connect if we are running integrated.
+    if (stand_alone_) {
+        LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE)
+                  .arg(app_name_);
+    } else {
+        try {
+            establishSession();
+        } catch (const std::exception& ex) {
+            LOG_FATAL(dctl_logger, DCTL_SESSION_FAIL).arg(ex.what());
+            isc_throw (SessionStartError,
+                       "Session start up failed: " << ex.what());
+        }
+    }
+
+    // Everything is clear for launch, so start the application's
+    // event loop.
+    try {
+        runProcess();
+    } catch (const std::exception& ex) {
+        LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
+                  .arg(app_name_).arg(ex.what());
+        isc_throw (ProcessRunError,
+                   "Application process event loop failed: " << ex.what());
+    }
+
+    // If running integrated, disconnect.
+    if (!stand_alone_) {
+        try {
+            disconnectSession();
+        } catch (const std::exception& ex) {
+            LOG_ERROR(dctl_logger, DCTL_DISCONNECT_FAIL)
+                      .arg(app_name_).arg(ex.what());
+            isc_throw (SessionEndError, "Session end failed: " << ex.what());
+        }
+    }
+
+    // All done, so bail out.
+    LOG_INFO(dctl_logger, DCTL_STOPPING).arg(app_name_);
+}
+
+
+void
+DControllerBase::parseArgs(int argc, char* argv[])
+{
+    // Iterate over the given command line options. If its a stock option
+    // ("s" or "v") handle it here.  If its a valid custom option, then
+    // invoke customOption.
+    int ch;
+    opterr = 0;
+    optind = 1;
+    std::string opts(":vs" + getCustomOpts());
+    while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
+        switch (ch) {
+        case 'v':
+            // Enables verbose logging.
+            verbose_ = true;
+            break;
+
+        case 's':
+            // Enables stand alone or "BINDLESS" operation.
+            stand_alone_ = true;
+            break;
+
+        case '?': {
+            // We hit an invalid option.
+            isc_throw(InvalidUsage, "unsupported option: ["
+                      << static_cast<char>(optopt) << "] "
+                      << (!optarg ? "" : optarg));
+
+            break;
+            }
+
+        default:
+            // We hit a valid custom option
+            if (!customOption(ch, optarg)) {
+                // This would be a programmatic error.
+                isc_throw(InvalidUsage, " Option listed but implemented?: ["
+                          << static_cast<char>(ch) << "] "
+                          << (!optarg ? "" : optarg));
+            }
+            break;
+        }
+    }
+
+    // There was too much information on the command line.
+    if (argc > optind) {
+        isc_throw(InvalidUsage, "extraneous command line information");
+    }
+}
+
+bool
+DControllerBase::customOption(int /* option */, char* /*optarg*/)
+{
+    // Default implementation returns false.
+    return (false);
+}
+
+void
+DControllerBase::initProcess() {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
+
+    // Invoke virtual method to instantiate the application process.
+    try {
+        process_.reset(createProcess());
+    } catch (const std::exception& ex) {
+        isc_throw(DControllerBaseError, std::string("createProcess failed: ")
+                  + ex.what());
+    }
+
+    // This is pretty unlikely, but will test for it just to be safe..
+    if (!process_) {
+        isc_throw(DControllerBaseError, "createProcess returned NULL");
+    }
+
+    // Invoke application's init method (Note this call should throw
+    // DProcessBaseError if it fails).
+    process_->init();
+}
+
+void
+DControllerBase::establishSession() {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_STARTING)
+              .arg(app_name_).arg(spec_file_name_);
+
+    // Create the BIND10 command control session with the our IOService.
+    cc_session_ = SessionPtr(new isc::cc::Session(
+                             io_service_->get_io_service()));
+
+    // Create the BIND10 config session with the stub configuration handler.
+    // This handler is internally invoked by the constructor and on success
+    // the constructor updates the current session with the configuration that
+    // had been committed in the previous session. If we do not install
+    // the dummy handler, the previous configuration would be lost.
+    config_session_ = ModuleCCSessionPtr(new isc::config::ModuleCCSession(
+                                         spec_file_name_, *cc_session_,
+                                         dummyConfigHandler, commandHandler,
+                                         false));
+    // Enable configuration even processing.
+    config_session_->start();
+
+    // We initially create ModuleCCSession() with a dummy configHandler, as
+    // the session module is too eager to send partial configuration.
+    // Replace the dummy config handler with the real handler.
+    config_session_->setConfigHandler(configHandler);
+
+    // Call the real configHandler with the full configuration retrieved
+    // from the config session.
+    isc::data::ConstElementPtr answer = configHandler(
+                                            config_session_->getFullConfig());
+
+    // Parse the answer returned from the configHandler.  Log the error but
+    // keep running. This provides an opportunity for the user to correct
+    // the configuration dynamically.
+    int ret = 0;
+    isc::data::ConstElementPtr comment = isc::config::parseAnswer(ret, answer);
+    if (ret) {
+        LOG_ERROR(dctl_logger, DCTL_CONFIG_LOAD_FAIL)
+                  .arg(app_name_).arg(comment->str());
+    }
+
+    // Lastly, call onConnect. This allows deriving class to execute custom
+    // logic predicated by session connect.
+    onSessionConnect();
+}
+
+void
+DControllerBase::runProcess() {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
+    if (!process_) {
+        // This should not be possible.
+        isc_throw(DControllerBaseError, "Process not initialized");
+    }
+
+    // Invoke the application process's run method. This may throw
+    // DProcessBaseError
+    process_->run();
+}
+
+void DControllerBase::disconnectSession() {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_ENDING)
+              .arg(app_name_);
+
+    // Call virtual onDisconnect. Allows deriving class to execute custom
+    // logic prior to session loss.
+    onSessionDisconnect();
+
+    // Destroy the BIND10 config session.
+    if (config_session_) {
+        config_session_.reset();
+    }
+
+    // Destroy the BIND10 command and control session.
+    if (cc_session_) {
+        cc_session_->disconnect();
+        cc_session_.reset();
+    }
+}
+
+isc::data::ConstElementPtr
+DControllerBase::dummyConfigHandler(isc::data::ConstElementPtr) {
+    LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CONFIG_STUB)
+             .arg(controller_->getAppName());
+    return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::configHandler(isc::data::ConstElementPtr new_config) {
+
+    LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_CONFIG_UPDATE)
+              .arg(controller_->getAppName()).arg(new_config->str());
+
+    // Invoke the instance method on the controller singleton.
+    return (controller_->updateConfig(new_config));
+}
+
+// Static callback which invokes non-static handler on singleton
+isc::data::ConstElementPtr
+DControllerBase::commandHandler(const std::string& command,
+                                isc::data::ConstElementPtr args) {
+
+    LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_COMMAND_RECEIVED)
+        .arg(controller_->getAppName()).arg(command)
+        .arg(args ? args->str() : "(no args)");
+
+    // Invoke the instance method on the controller singleton.
+    return (controller_->executeCommand(command, args));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
+    isc::data::ConstElementPtr full_config;
+    if (stand_alone_) {
+        // @todo Until there is a configuration manager to provide retrieval
+        // we'll just assume the incoming config is the full configuration set.
+        // It may also make more sense to isolate the controller from the
+        // configuration manager entirely. We could do something like
+        // process_->getFullConfig() here for stand-alone mode?
+        full_config = new_config;
+    } else {
+        if (!config_session_) {
+            // That should never happen as we install config_handler
+            // after we instantiate the server.
+            isc::data::ConstElementPtr answer =
+                    isc::config::createAnswer(1, "Configuration rejected,"
+                                              " Session has not started.");
+            return (answer);
+        }
+
+        // Let's get the existing configuration.
+        full_config = config_session_->getFullConfig();
+    }
+
+    // The configuration passed to this handler function is partial.
+    // In other words, it just includes the values being modified.
+    // In the same time, there may be dependencies between various
+    // configuration parsers. For example: the option value can
+    // be set if the definition of this option is set. If someone removes
+    // an existing option definition then the partial configuration that
+    // removes that definition is triggered while a relevant option value
+    // may remain configured. This eventually results in the
+    // configuration being in the inconsistent state.
+    // In order to work around this problem we need to merge the new
+    // configuration with the existing (full) configuration.
+
+    // Let's create a new object that will hold the merged configuration.
+    boost::shared_ptr<isc::data::MapElement>
+                            merged_config(new isc::data::MapElement());
+
+    // Merge an existing and new configuration.
+    merged_config->setValue(full_config->mapValue());
+    isc::data::merge(merged_config, new_config);
+
+    // Send the merged configuration to the application.
+    return (process_->configure(merged_config));
+}
+
+
+isc::data::ConstElementPtr
+DControllerBase::executeCommand(const std::string& command,
+                            isc::data::ConstElementPtr args) {
+    // Shutdown is universal.  If its not that, then try it as
+    // an custom command supported by the derivation.  If that
+    // doesn't pan out either, than send to it the application
+    // as it may be supported there.
+    isc::data::ConstElementPtr answer;
+    if (command.compare(SHUT_DOWN_COMMAND) == 0) {
+        answer = shutdown(args);
+    } else {
+        // It wasn't shutdown, so may be a custom controller command.
+        int rcode = 0;
+        answer = customControllerCommand(command, args);
+        isc::config::parseAnswer(rcode, answer);
+        if (rcode == COMMAND_INVALID)
+        {
+            // It wasn't controller command, so may be an application command.
+            answer = process_->command(command, args);
+        }
+    }
+
+    return (answer);
+}
+
+isc::data::ConstElementPtr
+DControllerBase::customControllerCommand(const std::string& command,
+                                     isc::data::ConstElementPtr /* args */) {
+
+    // Default implementation always returns invalid command.
+    return (isc::config::createAnswer(COMMAND_INVALID,
+                                      "Unrecognized command: " + command));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::shutdown(isc::data::ConstElementPtr args) {
+    if (process_) {
+        return (process_->shutdown(args));
+    } 
+
+    // Not really a failure, but this condition is worth noting. In reality
+    // it should be pretty hard to cause this.
+    LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
+    return (isc::config::createAnswer(0, "Process has not been initialzed."));
+}
+
+void
+DControllerBase::usage(const std::string & text)
+{
+    if (text != "") {
+        std::cerr << "Usage error: " << text << std::endl;
+    }
+
+    std::cerr << "Usage: " << bin_name_ <<  std::endl;
+    std::cerr << "  -v: verbose output" << std::endl;
+    std::cerr << "  -s: stand-alone mode (don't connect to BIND10)"
+              << std::endl;
+
+    std::cerr << getUsageText() << std::endl;
+}
+
+DControllerBase::~DControllerBase() {
+}
+
+}; // namespace isc::d2
+}; // namespace isc

+ 557 - 0
src/bin/d2/d_controller.h

@@ -0,0 +1,557 @@
+// Copyright (C) 2013  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 D_CONTROLLER_H
+#define D_CONTROLLER_H
+
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <d2/d2_asio.h>
+#include <d2/d2_log.h>
+#include <d2/d_process.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+
+namespace isc {
+namespace d2 {
+
+/// @brief DControllerBase launch exit status values.  Upon service shutdown
+/// normal or otherwise, the Controller's launch method will return one of
+/// these values.
+
+/// @brief Exception thrown when the command line is invalid.
+class InvalidUsage : public isc::Exception {
+public:
+    InvalidUsage(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the application process fails.
+class ProcessInitError: public isc::Exception {
+public:
+    ProcessInitError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the session start up fails.
+class SessionStartError: public isc::Exception {
+public:
+    SessionStartError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the application process encounters an
+/// operation in its event loop (i.e. run method).
+class ProcessRunError: public isc::Exception {
+public:
+    ProcessRunError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the session end fails.
+class SessionEndError: public isc::Exception {
+public:
+    SessionEndError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Exception thrown when the controller encounters an operational error.
+class DControllerBaseError : public isc::Exception {
+public:
+    DControllerBaseError (const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Defines a shared pointer to DControllerBase.
+class DControllerBase;
+typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
+
+/// @brief Defines a shared pointer to a Session.
+typedef boost::shared_ptr<isc::cc::Session> SessionPtr;
+
+/// @brief Defines a shared pointer to a ModuleCCSession.
+typedef boost::shared_ptr<isc::config::ModuleCCSession> ModuleCCSessionPtr;
+
+
+/// @brief Application Controller
+///
+/// DControllerBase is an abstract singleton which provides the framework and
+/// services for managing an application process that implements the
+/// DProcessBase interface.  It allows the process to run either in
+/// integrated mode as a BIND10 module or stand-alone. It coordinates command
+/// line argument parsing, process instantiation and initialization, and runtime
+/// control through external command and configuration event handling.
+/// It creates the IOService instance which is used for runtime control
+/// events and passes the IOService into the application process at process
+/// creation.  In integrated mode it is responsible for establishing BIND10
+/// session(s) and passes this IOService into the session creation method(s).
+/// It also provides the callback handlers for command and configuration events
+/// received from the external framework (aka BIND10).  For example, when
+/// running in integrated mode and a user alters the configuration with the
+/// bindctl tool, BIND10 will emit a configuration message which is sensed by
+/// the controller's IOService. The IOService in turn invokes the configuration
+/// callback, DControllerBase::configHandler().  If the user issues a command
+/// such as shutdown via bindctl,  BIND10 will emit a command message, which is
+/// sensed by controller's IOService which invokes the command callback,
+/// DControllerBase::commandHandler().
+///
+/// NOTE: Derivations must supply their own static singleton instance method(s)
+/// for creating and fetching the instance. The base class declares the instance
+/// member in order for it to be available for BIND10 callback functions. This
+/// would not be required if BIND10 supported instance method callbacks.
+class DControllerBase : public boost::noncopyable {
+public:
+    /// @brief Constructor
+    ///
+    /// @param app_name is display name of the application under control. This
+    /// name appears in log statements.
+    /// @param bin_name is the name of the application executable. Typically
+    /// this matches the BIND10 module name.
+    DControllerBase(const char* app_name, const char* bin_name);
+
+    /// @brief Destructor
+    virtual ~DControllerBase();
+
+    /// @brief Acts as the primary entry point into the controller execution
+    /// and provides the outermost application control logic:
+    ///
+    /// 1. parse command line arguments
+    /// 2. instantiate and initialize the application process
+    /// 3. establish BIND10 session(s) if in integrated mode
+    /// 4. start and wait on the application process event loop
+    /// 5. upon event loop completion, disconnect from BIND10 (if needed)
+    /// 6. exit to the caller
+    ///
+    /// It is intended to be called from main() and be given the command line
+    /// arguments. Note this method is deliberately not virtual to ensure the
+    /// proper sequence of events occur.
+    ///
+    /// This function can be run in the test mode. It prevents initialization
+    /// of D2 module logger. This is used in unit tests which initialize logger
+    /// in their main function. Such logger uses environmental variables to
+    /// control severity, verbosity etc. Reinitialization of logger by this
+    /// function would replace unit tests specific logger configuration with
+    /// this suitable for D2 running as a bind10 module.
+    ///
+    /// @param argc  is the number of command line arguments supplied
+    /// @param argv  is the array of string (char *) command line arguments
+    /// @param test_mode is a bool value which indicates if
+    /// @c DControllerBase::launch should be run in the test mode (if true).
+    /// This parameter doesn't have default value to force test implementers to
+    /// enable test mode explicitly.
+    ///
+    /// @throw throws one of the following exceptions:
+    /// InvalidUsage - Indicates invalid command line.
+    /// ProcessInitError  - Failed to create and initialize application
+    /// process object.
+    /// SessionStartError  - Could not connect to BIND10 (integrated mode only).
+    /// ProcessRunError - A fatal error occurred while in the application
+    /// process event loop.
+    /// SessionEndError - Could not disconnect from BIND10 (integrated mode
+    /// only).
+    void launch(int argc, char* argv[], const bool test_mode);
+
+    /// @brief A dummy configuration handler that always returns success.
+    ///
+    /// This configuration handler does not perform configuration
+    /// parsing and always returns success. A dummy handler should
+    /// be installed using \ref isc::config::ModuleCCSession ctor
+    /// to get the initial configuration. This initial configuration
+    /// comprises values for only those elements that were modified
+    /// the previous session. The D2 configuration parsing can't be
+    /// used to parse the initial configuration because it may need the
+    /// full configuration to satisfy dependencies between the
+    /// various configuration values. Installing the dummy handler
+    /// that guarantees to return success causes initial configuration
+    /// to be stored for the session being created and that it can
+    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
+    ///
+    /// @param new_config new configuration.
+    ///
+    /// @return success configuration status.
+    static isc::data::ConstElementPtr
+    dummyConfigHandler(isc::data::ConstElementPtr new_config);
+
+    /// @brief A callback for handling all incoming configuration updates.
+    ///
+    /// As a pointer to this method is used as a callback in ASIO for
+    /// ModuleCCSession, it has to be static.  It acts as a wrapper around
+    /// the virtual instance method, updateConfig.
+    ///
+    /// @param new_config textual representation of the new configuration
+    ///
+    /// @return status of the config update
+    static isc::data::ConstElementPtr
+    configHandler(isc::data::ConstElementPtr new_config);
+
+    /// @brief A callback for handling all incoming commands.
+    ///
+    /// As a pointer to this method is used as a callback in ASIO for
+    /// ModuleCCSession, it has to be static.  It acts as a wrapper around
+    /// the virtual instance method, executeCommand.
+    ///
+    /// @param command textual representation of the command
+    /// @param args parameters of the command. It can be NULL pointer if no
+    /// arguments exist for a particular command.
+    ///
+    /// @return status of the processed command
+    static isc::data::ConstElementPtr
+    commandHandler(const std::string& command, isc::data::ConstElementPtr args);
+
+    /// @brief Instance method invoked by the configuration event handler and
+    /// which processes the actual configuration update.  Provides behavioral
+    /// path for both integrated and stand-alone modes. The current
+    /// implementation will merge the configuration update into the existing
+    /// configuration and then invoke the application process' configure method.
+    ///
+    /// @todo This implementation is will evolve as the D2 configuration
+    /// management task is implemented (trac #2957).
+    ///
+    /// @param  new_config is the new configuration
+    ///
+    /// @return returns an Element that contains the results of configuration
+    /// update composed of an integer status value (0 means successful,
+    /// non-zero means failure), and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr
+    updateConfig(isc::data::ConstElementPtr new_config);
+
+
+    /// @brief Instance method invoked by the command event handler and  which
+    /// processes the actual command directive.
+    ///
+    /// It supports the execution of:
+    ///
+    ///   1. Stock controller commands - commands common to all DControllerBase
+    /// derivations.  Currently there is only one, the shutdown command.
+    ///
+    ///   2. Custom controller commands - commands that the deriving controller
+    /// class implements.  These commands are executed by the deriving
+    /// controller.
+    ///
+    ///   3. Custom application commands - commands supported by the application
+    /// process implementation.  These commands are executed by the application
+    /// process.
+    ///
+    /// @param command is a string label representing the command to execute.
+    /// @param args is a set of arguments (if any) required for the given
+    /// command.
+    ///
+    /// @return an Element that contains the results of command composed
+    /// of an integer status value and a string explanation of the outcome.
+    /// The status value is one of the following:
+    ///   D2::COMMAND_SUCCESS - Command executed successfully
+    ///   D2::COMMAND_ERROR - Command is valid but suffered an operational
+    ///   failure.
+    ///   D2::COMMAND_INVALID - Command is not recognized as valid be either
+    ///   the controller or the application process.
+    virtual isc::data::ConstElementPtr
+    executeCommand(const std::string& command, isc::data::ConstElementPtr args);
+
+protected:
+    /// @brief Virtual method that provides derivations the opportunity to
+    /// support additional command line options.  It is invoked during command
+    /// line argument parsing (see parseArgs method) if the option is not
+    /// recognized as a stock DControllerBase option.
+    ///
+    /// @param option is the option "character" from the command line, without
+    /// any prefixing hyphen(s)
+    /// @param optarg is the argument value (if one) associated with the option
+    ///
+    /// @return must return true if the option was valid, false is it is
+    /// invalid. (Note the default implementation always returns false.)
+    virtual bool customOption(int option, char *optarg);
+
+    /// @brief Abstract method that is responsible for instantiating the
+    /// application process object. It is invoked by the controller after
+    /// command line argument parsing as part of the process initialization
+    /// (see initProcess method).
+    ///
+    /// @return returns a pointer to the new process object (DProcessBase*)
+    /// or NULL if the create fails.
+    /// Note this value is subsequently wrapped in a smart pointer.
+    virtual DProcessBase* createProcess() = 0;
+
+    /// @brief Virtual method that provides derivations the opportunity to
+    /// support custom external commands executed by the controller.  This
+    /// method is invoked by the processCommand if the received command is
+    /// not a stock controller command.
+    ///
+    /// @param command is a string label representing the command to execute.
+    /// @param args is a set of arguments (if any) required for the given
+    /// command.
+    ///
+    /// @return an Element that contains the results of command composed
+    /// of an integer status value and a string explanation of the outcome.
+    /// The status value is one of the following:
+    ///   D2::COMMAND_SUCCESS - Command executed successfully
+    ///   D2::COMMAND_ERROR - Command is valid but suffered an operational
+    ///   failure.
+    ///   D2::COMMAND_INVALID - Command is not recognized as a valid custom
+    ///   controller command.
+    virtual isc::data::ConstElementPtr customControllerCommand(
+            const std::string& command, isc::data::ConstElementPtr args);
+
+    /// @brief Virtual method which is invoked after the controller successfully
+    /// establishes BIND10 connectivity.  It provides an opportunity for the
+    /// derivation to execute any custom behavior associated with session
+    /// establishment.
+    ///
+    /// Note, it is not called  when running stand-alone.
+    ///
+    /// @throw should throw a DControllerBaseError if it fails.
+    virtual void onSessionConnect(){};
+
+    /// @brief Virtual method which is invoked as the first action taken when
+    /// the controller is terminating the session(s) with BIND10.  It provides
+    /// an opportunity for the derivation to execute any custom behavior
+    /// associated with session termination.
+    ///
+    /// Note, it is not called  when running stand-alone.
+    ///
+    /// @throw should throw a DControllerBaseError if it fails.
+    virtual void onSessionDisconnect(){};
+
+    /// @brief Virtual method which can be used to contribute derivation
+    /// specific usage text.  It is invoked by the usage() method under
+    /// invalid usage conditions.
+    ///
+    /// @return returns the desired text.
+    virtual const std::string getUsageText() const {
+        return ("");
+    }
+
+    /// @brief Virtual method which returns a string containing the option
+    /// letters for any custom command line options supported by the derivation.
+    /// These are added to the stock options of "s" and "v" during command
+    /// line interpretation.
+    ///
+    /// @return returns a string containing the custom option letters.
+    virtual const std::string getCustomOpts() const {
+        return ("");
+    }
+
+    /// @brief Fetches the name of the application under control.
+    ///
+    /// @return returns the controller service name string
+    const std::string getAppName() const {
+        return (app_name_);
+    }
+
+    /// @brief Fetches the name of the application executable.
+    ///
+    /// @return returns the controller logger name string
+    const std::string getBinName() const {
+        return (bin_name_);
+    }
+
+    /// @brief Supplies whether or not the controller is in stand alone mode.
+    ///
+    /// @return returns true if in stand alone mode, false otherwise
+    bool isStandAlone() const {
+        return (stand_alone_);
+    }
+
+    /// @brief Method for enabling or disabling stand alone mode.
+    ///
+    /// @param value is the new value to assign the flag.
+    void setStandAlone(bool value) {
+        stand_alone_ = value;
+    }
+
+    /// @brief Supplies whether or not verbose logging is enabled.
+    ///
+    /// @return returns true if verbose logging is enabled.
+    bool isVerbose() const {
+        return (verbose_);
+    }
+
+    /// @brief Method for enabling or disabling verbose logging.
+    ///
+    /// @param value is the new value to assign the flag.
+    void setVerbose(bool value) {
+        verbose_ = value;
+    }
+
+    /// @brief Getter for fetching the controller's IOService
+    ///
+    /// @return returns a pointer reference to the IOService.
+    IOServicePtr& getIOService() {
+        return (io_service_);
+    }
+
+    /// @brief Getter for fetching the name of the controller's BIND10 spec
+    /// file.
+    ///
+    /// @return returns the file name string.
+    const std::string getSpecFileName() const {
+        return (spec_file_name_);
+    }
+
+    /// @brief Setter for setting the name of the controller's BIND10 spec file.
+    ///
+    /// @param spec_file_name the file name string.
+    void setSpecFileName(const std::string& spec_file_name) {
+        spec_file_name_ = spec_file_name;
+    }
+
+    /// @brief Static getter which returns the singleton instance.
+    ///
+    /// @return returns a pointer reference to the private singleton instance
+    /// member.
+    static DControllerBasePtr& getController() {
+        return (controller_);
+    }
+
+    /// @brief Static setter which sets the singleton instance.
+    ///
+    /// @param controller is a pointer to the singleton instance.
+    ///
+    /// @throw throws DControllerBase error if an attempt is made to set the
+    /// instance a second time.
+    static void setController(const DControllerBasePtr& controller);
+
+private:
+    /// @brief Processes the command line arguments. It is the first step
+    /// taken after the controller has been launched.  It combines the stock
+    /// list of options with those returned by getCustomOpts(), and uses
+    /// cstdlib's getopt to loop through the command line.  The stock options
+    /// It handles stock options directly, and passes any custom options into
+    /// the customOption method.  Currently there are only two stock options
+    /// -s for stand alone mode, and -v for verbose logging.
+    ///
+    /// @param argc  is the number of command line arguments supplied
+    /// @param argv  is the array of string (char *) command line arguments
+    ///
+    /// @throw throws InvalidUsage when there are usage errors.
+    void parseArgs(int argc, char* argv[]);
+
+    /// @brief Instantiates the application process and then initializes it.
+    /// This is the second step taken during launch, following successful
+    /// command line parsing. It is used to invoke the derivation-specific
+    /// implementation of createProcess, following by an invoking of the
+    /// newly instantiated process's init method.
+    ///
+    /// @throw throws DControllerBaseError or indirectly DProcessBaseError
+    /// if there is a failure creating or initializing the application process.
+    void initProcess();
+
+    /// @brief Establishes connectivity with BIND10.  This method is used
+    /// invoked during launch, if running in integrated mode, following
+    /// successful process initialization.  It is responsible for establishing
+    /// the BIND10 control and config sessions. During the session creation,
+    /// it passes in the controller's IOService and the callbacks for command
+    /// directives and config events.  Lastly, it will invoke the onConnect
+    /// method providing the derivation an opportunity to execute any custom
+    /// logic associated with session establishment.
+    ///
+    /// @throw the BIND10 framework may throw std::exceptions.
+    void establishSession();
+
+    /// @brief Invokes the application process's event loop,(DBaseProcess::run).
+    /// It is called during launch only after successfully completing the
+    /// requested setup: command line parsing, application initialization,
+    /// and session establishment (if not stand-alone).
+    /// The process event loop is expected to only return upon application
+    /// shutdown either in response to the shutdown command or due to an
+    /// unrecoverable error.
+    ///
+    // @throw throws DControllerBaseError or indirectly DProcessBaseError
+    void runProcess();
+
+    /// @brief Terminates connectivity with BIND10. This method is invoked
+    /// in integrated mode after the application event loop has exited. It
+    /// first calls the onDisconnect method providing the derivation an
+    /// opportunity to execute custom logic if needed, and then terminates the
+    /// BIND10 config and control sessions.
+    ///
+    /// @throw the BIND10 framework may throw std:exceptions.
+    void disconnectSession();
+
+    /// @brief Initiates shutdown procedure.  This method is invoked
+    /// by executeCommand in response to the shutdown command. It will invoke
+    /// the application process's shutdown method which causes the process to
+    /// to begin its shutdown process.
+    ///
+    /// Note, it is assumed that the process of shutting down is neither
+    /// instanteneous nor synchronous.  This method does not "block" waiting
+    /// until the process has halted.  Rather it is used to convey the
+    /// need to shutdown.  A successful return indicates that the shutdown
+    /// has successfully commenced, but does not indicate that the process
+    /// has actually exited. 
+    ///
+    /// @return returns an Element that contains the results of shutdown
+    /// command composed of an integer status value (0 means successful,
+    /// non-zero means failure), and a string explanation of the outcome.
+    isc::data::ConstElementPtr shutdown(isc::data::ConstElementPtr args);
+
+    /// @brief Prints the program usage text to std error.
+    ///
+    /// @param text is a string message which will preceded the usage text.
+    /// This is intended to be used for specific usage violation messages.
+    void usage(const std::string& text);
+
+private:
+    /// @brief Display name of the service under control. This name
+    /// appears in log statements.
+    std::string app_name_;
+
+    /// @brief Name of the service executable. By convention this matches
+    /// the BIND10 module name. It is also used to establish the logger
+    /// name.
+    std::string bin_name_;
+
+    /// @brief Indicates if the controller stand alone mode is enabled. When
+    /// enabled, the controller will not establish connectivity with BIND10.
+    bool stand_alone_;
+
+    /// @brief Indicates if the verbose logging mode is enabled.
+    bool verbose_;
+
+    /// @brief The absolute file name of the BIND10 spec file.
+    std::string spec_file_name_;
+
+    /// @brief Pointer to the instance of the process.
+    ///
+    /// This is required for config and command handlers to gain access to
+    /// the process
+    DProcessBasePtr process_;
+
+    /// @brief Shared pointer to an IOService object, used for ASIO operations.
+    IOServicePtr io_service_;
+
+    /// @brief Helper session object that represents raw connection to msgq.
+    SessionPtr cc_session_;
+
+    /// @brief Session that receives configuration and commands.
+    ModuleCCSessionPtr config_session_;
+
+    /// @brief Singleton instance value.
+    static DControllerBasePtr controller_;
+
+// DControllerTest is named a friend class to facilitate unit testing while
+// leaving the intended member scopes intact.
+friend class DControllerTest;
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

+ 217 - 0
src/bin/d2/d_process.h

@@ -0,0 +1,217 @@
+// Copyright (C) 2013  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 D_PROCESS_H
+#define D_PROCESS_H
+
+#include <cc/data.h>
+#include <d2/d2_asio.h>
+#include <d2/d_cfg_mgr.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the process encountered an operational error.
+class DProcessBaseError : public isc::Exception {
+public:
+    DProcessBaseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief String value for the shutdown command.
+static const std::string SHUT_DOWN_COMMAND("shutdown");
+
+/// @brief Returned by the process to indicate a command was successful.
+static const int COMMAND_SUCCESS = 0;
+
+/// @brief Returned by the process to indicates a command failed.
+static const int COMMAND_ERROR = 1;
+
+/// @brief Returned by the process to indicates a command is not valid.
+static const int COMMAND_INVALID = 2;
+
+/// @brief Application Process Interface
+///
+/// DProcessBase is an abstract class represents the primary "application"
+/// level object in a "managed" asynchronous application. It provides a uniform
+/// interface such that a managing layer can construct, initialize, and start
+/// the application's event loop.  The event processing is centered around the
+/// use of isc::asiolink::io_service. The io_service is shared between the
+/// managing layer and the DProcessBase.  This allows management layer IO such
+/// as directives to be sensed and handled, as well as processing IO activity
+/// specific to the application.  In terms of management layer IO, there are
+/// methods shutdown, configuration updates, and commands unique to the
+/// application.
+class DProcessBase {
+public:
+    /// @brief Constructor
+    ///
+    /// @param app_name is a text label for the process. Generally used
+    /// in log statements, but otherwise arbitrary.
+    /// @param io_service is the io_service used by the caller for
+    /// asynchronous event handling.
+    /// @param cfg_mgr the configuration manager instance that handles
+    /// configuration parsing.
+    ///
+    /// @throw DProcessBaseError is io_service is NULL.
+    DProcessBase(const char* app_name, IOServicePtr io_service, 
+                 DCfgMgrBasePtr cfg_mgr)
+        : app_name_(app_name), io_service_(io_service), shut_down_flag_(false),
+        cfg_mgr_(cfg_mgr) {
+        if (!io_service_) {
+            isc_throw (DProcessBaseError, "IO Service cannot be null");
+        }
+
+        if (!cfg_mgr_) {
+            isc_throw (DProcessBaseError, "CfgMgr cannot be null");
+        }
+    };
+
+    /// @brief May be used after instantiation to perform initialization unique
+    /// to application. It must be invoked prior to invoking run. This would
+    /// likely include the creation of additional IO sources and their
+    /// integration into the io_service.
+    /// @throw DProcessBaseError if the initialization fails.
+    virtual void init() = 0;
+
+    /// @brief Implements the process's event loop. In its simplest form it
+    /// would an invocation io_service_->run().  This method should not exit
+    /// until the process itself is exiting due to a request to shutdown or
+    /// some anomaly is forcing an exit.
+    /// @throw DProcessBaseError if an operational error is encountered.
+    virtual void run() = 0;
+
+    /// @brief Initiates the process's shutdown process. 
+    /// 
+    /// This is last step in the shutdown event callback chain, that is 
+    /// intended to notify the process it is to begin its shutdown process.
+    ///
+    /// @param args an Element set of shutdown arguments (if any) that are
+    /// supported by the process derivation. 
+    /// 
+    /// @return an Element that contains the results of argument processing,
+    /// consisting of an integer status value (0 means successful, 
+    /// non-zero means failure), and a string explanation of the outcome. 
+    ///  
+    /// @throw DProcessBaseError if an operational error is encountered.
+    virtual isc::data::ConstElementPtr 
+        shutdown(isc::data::ConstElementPtr args) = 0;
+
+    /// @brief Processes the given configuration.
+    ///
+    /// This method may be called multiple times during the process lifetime.
+    /// Certainly once during process startup, and possibly later if the user
+    /// alters configuration. This method must not throw, it should catch any
+    /// processing errors and return a success or failure answer as described
+    /// below.
+    ///
+    /// @param config_set a new configuration (JSON) for the process
+    /// @return an Element that contains the results of configuration composed
+    /// of an integer status value (0 means successful, non-zero means failure),
+    /// and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+                                                 config_set) = 0;
+
+    /// @brief Processes the given command.
+    ///
+    /// This method is called to execute any custom commands supported by the
+    /// process. This method must not throw, it should catch any processing
+    /// errors and return a success or failure answer as described below.
+    ///
+    /// @param command is a string label representing the command to execute.
+    /// @param args is a set of arguments (if any) required for the given
+    /// command.
+    /// @return an Element that contains the results of command composed
+    /// of an integer status value: 
+    ///
+    /// - COMMAND_SUCCESS indicates a command was successful.
+    /// - COMMAND_ERROR indicates a valid command failed execute.
+    /// - COMMAND_INVALID indicates a command is not valid.
+    ///
+    /// and a string explanation of the outcome.
+    virtual isc::data::ConstElementPtr command(
+            const std::string& command, isc::data::ConstElementPtr args) = 0;
+
+    /// @brief Destructor
+    virtual ~DProcessBase(){};
+
+    /// @brief Checks if the process has been instructed to shut down.
+    ///
+    /// @return true if process shutdown flag is true.
+    bool shouldShutdown() const {
+        return (shut_down_flag_);
+    }
+
+    /// @brief Sets the process shut down flag to the given value.
+    ///
+    /// @param value is the new value to assign the flag.
+    void setShutdownFlag(bool value) {
+        shut_down_flag_ = value;
+    }
+
+    /// @brief Fetches the application name.
+    ///
+    /// @return application name string.
+    const std::string getAppName() const {
+        return (app_name_);
+    }
+
+    /// @brief Fetches the controller's IOService.
+    ///
+    /// @return a reference to the controller's IOService.
+    IOServicePtr& getIoService() {
+        return (io_service_);
+    }
+
+    /// @brief Convenience method for stopping IOservice processing.
+    /// Invoking this will cause the process to exit any blocking
+    /// IOService method such as run().  No further IO events will be
+    /// processed.
+    void stopIOService() {
+        io_service_->stop();
+    }
+
+    /// @brief Fetches the process's configuration manager.
+    ///
+    /// @return a reference to the configuration manager.
+    DCfgMgrBasePtr& getCfgMgr() {
+        return (cfg_mgr_);
+    }
+
+private:
+    /// @brief Text label for the process. Generally used in log statements,
+    /// but otherwise can be arbitrary.
+    std::string app_name_;
+
+    /// @brief The IOService to be used for asynchronous event handling.
+    IOServicePtr io_service_;
+
+    /// @brief Boolean flag set when shutdown has been requested.
+    bool shut_down_flag_;
+
+    /// @brief  Pointer to the configuration manager.
+    DCfgMgrBasePtr cfg_mgr_;
+};
+
+/// @brief Defines a shared pointer to DProcessBase.
+typedef boost::shared_ptr<DProcessBase> DProcessBasePtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif

+ 212 - 0
src/bin/d2/dhcp-ddns.spec

@@ -0,0 +1,212 @@
+{ 
+"module_spec": 
+{
+    "module_name": "DhcpDdns",
+    "module_description": "DHPC-DDNS Service",
+    "config_data": [
+    { 
+        "item_name": "interface",
+        "item_type": "string",
+        "item_optional": true,
+        "item_default": "eth0"
+    },
+
+    { 
+        "item_name": "ip_address",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "127.0.0.1" 
+    },
+
+    { 
+        "item_name": "port",
+        "item_type": "integer",
+        "item_optional": true,
+        "item_default": 51771 
+    },
+    {
+        "item_name": "tsig_keys",
+        "item_type": "list",
+        "item_optional": true, 
+        "item_default": [],
+        "list_item_spec":
+        {
+            "item_name": "tsig_key",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {"algorithm" : "hmac_md5"},
+            "map_item_spec": [ 
+            {
+                "item_name": "name",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": ""
+            },
+            {
+                "item_name": "algorithm",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": ""
+            },
+            {
+                "item_name": "secret",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": ""
+            }]
+        }
+    },
+    {
+        "item_name": "forward_ddns",
+        "item_type": "map",
+        "item_optional": true,
+         "item_default": {},
+         "map_item_spec": [ 
+         {
+            "item_name": "ddns_domains",
+            "item_type": "list",
+            "item_optional": false, 
+            "item_default": [],
+            "list_item_spec":
+            {
+                "item_name": "ddns_domain",
+                "item_type": "map",
+                "item_optional": false,
+                "item_default": {},
+                "map_item_spec": [ 
+                { 
+                    "item_name": "name",
+                    "item_type": "string",
+                    "item_optional": false,
+                    "item_default": ""
+                },
+
+                { 
+                    "item_name": "key_name",
+                    "item_type": "string",
+                    "item_optional": true,
+                    "item_default": "" 
+                },
+    
+                {
+                    "item_name": "dns_servers",
+                    "item_type": "list",
+                    "item_optional": false, 
+                    "item_default": [],
+                    "list_item_spec":
+                    {
+                        "item_name": "dns_server",
+                        "item_type": "map",
+                        "item_optional": false, 
+                        "item_default": {},
+                        "map_item_spec": [ 
+                        { 
+                            "item_name": "hostname",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "ip_address",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "port",
+                            "item_type": "integer",
+                            "item_optional": true,
+                            "item_default": 53 
+                        }]
+                    }
+                }]
+            }
+        }]
+    },
+
+    {
+        "item_name": "reverse_ddns",
+        "item_type": "map",
+        "item_optional": true,
+         "item_default": {},
+         "map_item_spec": [ 
+         { 
+            "item_name": "ddns_domains",
+            "item_type": "list",
+            "item_optional": false, 
+            "item_default": [],
+            "list_item_spec":
+            {
+                "item_name": "ddns_domain",
+                "item_type": "map",
+                "item_optional": false,
+                "item_default": {},
+                "map_item_spec": [ 
+                { 
+                    "item_name": "name",
+                    "item_type": "string",
+                    "item_optional": false,
+                    "item_default": ""
+                },
+
+                { 
+                    "item_name": "key_name",
+                    "item_type": "string",
+                    "item_optional": true,
+                    "item_default": "" 
+                },
+    
+                {
+                    "item_name": "dns_servers",
+                    "item_type": "list",
+                    "item_optional": false, 
+                    "item_default": [],
+                    "list_item_spec":
+                    {
+                        "item_name": "dns_server",
+                        "item_type": "map",
+                        "item_optional": false, 
+                        "item_default": {},
+                        "map_item_spec": [ 
+                        { 
+                            "item_name": "hostname",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "ip_address",
+                            "item_type": "string",
+                            "item_optional": true,
+                            "item_default": ""
+                        },
+                        { 
+                            "item_name": "port",
+                            "item_type": "integer",
+                            "item_optional": true,
+                            "item_default": 53 
+                        }]
+                    }
+                }]
+            }
+        }]
+    }],
+
+    "commands": [
+        {
+            "command_name": "shutdown",
+            "command_description": "Shuts down b10-dhcp-ddns module server.",
+            "command_args": [
+            {
+                "item_name": "type",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "normal",
+                "item_description": "values: normal (default), now, or drain_first"
+            }
+            ]
+        }
+    ]
+  }
+}
+

+ 262 - 0
src/bin/d2/dns_client.cc

@@ -0,0 +1,262 @@
+// Copyright (C) 2013 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 <d2/dns_client.h>
+#include <d2/d2_log.h>
+#include <dns/messagerenderer.h>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+// OutputBuffer objects are pre-allocated before data is written to them.
+// This is a default number of bytes for the buffers we create within
+// DNSClient class.
+const size_t DEFAULT_BUFFER_SIZE = 128;
+
+}
+
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::dns;
+
+// This class provides the implementation for the DNSClient. This allows for
+// the separation of the DNSClient interface from the implementation details.
+// Currently, implementation uses IOFetch object to handle asynchronous
+// communication with the DNS. This design may be revisited in the future. If
+// implementation is changed, the DNSClient API will remain unchanged thanks
+// to this separation.
+class DNSClientImpl : public asiodns::IOFetch::Callback {
+public:
+    // A buffer holding response from a DNS.
+    util::OutputBufferPtr in_buf_;
+    // A caller-supplied object which will hold the parsed response from DNS.
+    // The response object is (or descends from) isc::dns::Message and is
+    // populated using Message::fromWire().  This method may only be called
+    // once in the lifetime of a Message instance.  Therefore, response_ is a
+    // pointer reference thus allowing this class to replace the object
+    // pointed to with a new Message instance each time a message is
+    // received. This allows a single DNSClientImpl instance to be used for
+    // multiple, sequential IOFetch calls. (@todo Trac# 3286 has been opened
+    // against dns::Message::fromWire.  Should the behavior of fromWire change
+    // the behavior here with could be rexamined).
+    D2UpdateMessagePtr& response_;
+    // A caller-supplied external callback which is invoked when DNS message
+    // exchange is complete or interrupted.
+    DNSClient::Callback* callback_;
+    // A Transport Layer protocol used to communicate with a DNS.
+    DNSClient::Protocol proto_;
+
+    // Constructor and Destructor
+    DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+                  DNSClient::Callback* callback,
+                  const DNSClient::Protocol proto);
+    virtual ~DNSClientImpl();
+
+    // This internal callback is called when the DNS update message exchange is
+    // complete. It further invokes the external callback provided by a caller.
+    // Before external callback is invoked, an object of the D2UpdateMessage
+    // type, representing a response from the server is set.
+    virtual void operator()(asiodns::IOFetch::Result result);
+
+    // Starts asynchronous DNS Update.
+    void doUpdate(asiolink::IOService& io_service,
+                  const asiolink::IOAddress& ns_addr,
+                  const uint16_t ns_port,
+                  D2UpdateMessage& update,
+                  const unsigned int wait);
+
+    // This function maps the IO error to the DNSClient error.
+    DNSClient::Status getStatus(const asiodns::IOFetch::Result);
+};
+
+DNSClientImpl::DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+                             DNSClient::Callback* callback,
+                             const DNSClient::Protocol proto)
+    : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
+      response_(response_placeholder), callback_(callback), proto_(proto) {
+
+    // Response should be an empty pointer. It gets populated by the
+    // operator() method.
+    if (response_) {
+        isc_throw(isc::BadValue, "Response buffer pointer should be null");
+    }
+
+    // @todo Currently we only support UDP. The support for TCP is planned for
+    // the future release.
+    if (proto_ == DNSClient::TCP) {
+        isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
+                  << " Transport protocol for DNS Updates; please use UDP");
+    }
+
+    // Given that we already eliminated the possibility that TCP is used, it
+    // would be sufficient  to check that (proto != DNSClient::UDP). But, once
+    // support TCP is added the check above will disappear and the extra check
+    // will be needed here anyway.
+    // Note that cascaded check is used here instead of:
+    //   if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
+    // because some versions of GCC compiler complain that check above would
+    // be always 'false' due to limited range of enumeration. In fact, it is
+    // possible to pass out of range integral value through enum and it should
+    // be caught here.
+    if (proto_ != DNSClient::TCP) {
+        if (proto_ != DNSClient::UDP) {
+            isc_throw(isc::NotImplemented, "invalid transport protocol type '"
+                      << proto_ << "' specified for DNS Updates");
+        }
+    }
+}
+
+DNSClientImpl::~DNSClientImpl() {
+}
+
+void
+DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
+    // Get the status from IO. If no success, we just call user's callback
+    // and pass the status code.
+    DNSClient::Status status = getStatus(result);
+    if (status == DNSClient::SUCCESS) {
+        InputBuffer response_buf(in_buf_->getData(), in_buf_->getLength());
+        // Allocate a new response message. (Note that Message::fromWire
+        // may only be run once per message, so we need to start fresh
+        // each time.)
+        response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+
+        // Server's response may be corrupted. In such case, fromWire will
+        // throw an exception. We want to catch this exception to return
+        // appropriate status code to the caller and log this event.
+        try {
+            response_->fromWire(response_buf);
+
+        } catch (const Exception& ex) {
+            status = DNSClient::INVALID_RESPONSE;
+            LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
+                      DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
+
+        }
+    }
+
+    // Once we are done with internal business, let's call a callback supplied
+    // by a caller.
+    if (callback_ != NULL) {
+        (*callback_)(status);
+    }
+}
+
+DNSClient::Status
+DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
+    switch (result) {
+    case IOFetch::SUCCESS:
+        return (DNSClient::SUCCESS);
+
+    case IOFetch::TIME_OUT:
+        return (DNSClient::TIMEOUT);
+
+    case IOFetch::STOPPED:
+        return (DNSClient::IO_STOPPED);
+
+    default:
+        ;
+    }
+    return (DNSClient::OTHER);
+}
+
+void
+DNSClientImpl::doUpdate(asiolink::IOService& io_service,
+                        const IOAddress& ns_addr,
+                        const uint16_t ns_port,
+                        D2UpdateMessage& update,
+                        const unsigned int wait) {
+    // A renderer is used by the toWire function which creates the on-wire data
+    // from the DNS Update message. A renderer has its internal buffer where it
+    // renders data by default. However, this buffer can't be directly accessed.
+    // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
+    // create our own buffer and pass it to the renderer so as the message is
+    // rendered to this buffer. Finally, we pass this buffer to IOFetch.
+    dns::MessageRenderer renderer;
+    OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
+    renderer.setBuffer(msg_buf.get());
+
+    // Render DNS Update message. This may throw a bunch of exceptions if
+    // invalid message object is given.
+    update.toWire(renderer);
+
+    // IOFetch has all the mechanisms that we need to perform asynchronous
+    // communication with the DNS server. The last but one argument points to
+    // this object as a completion callback for the message exchange. As a
+    // result operator()(Status) will be called.
+
+    // Timeout value is explicitly cast to the int type to avoid warnings about
+    // overflows when doing implicit cast. It should have been checked by the
+    // caller that the unsigned timeout value will fit into int.
+    IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
+                     in_buf_, this, static_cast<int>(wait));
+
+    // Post the task to the task queue in the IO service. Caller will actually
+    // run these tasks by executing IOService::run.
+    io_service.post(io_fetch);
+}
+
+
+DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
+                     Callback* callback, const DNSClient::Protocol proto)
+    : impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
+}
+
+DNSClient::~DNSClient() {
+    delete (impl_);
+}
+
+unsigned int
+DNSClient::getMaxTimeout() {
+    static const unsigned int max_timeout = std::numeric_limits<int>::max();
+    return (max_timeout);
+}
+
+void
+DNSClient::doUpdate(asiolink::IOService&,
+                    const IOAddress&,
+                    const uint16_t,
+                    D2UpdateMessage&,
+                    const unsigned int,
+                    const dns::TSIGKey&) {
+    isc_throw(isc::NotImplemented, "TSIG is currently not supported for"
+              "DNS Update message");
+}
+
+void
+DNSClient::doUpdate(asiolink::IOService& io_service,
+                    const IOAddress& ns_addr,
+                    const uint16_t ns_port,
+                    D2UpdateMessage& update,
+                    const unsigned int wait) {
+    // The underlying implementation which we use to send DNS Updates uses
+    // signed integers for timeout. If we want to avoid overflows we need to
+    // respect this limitation here.
+    if (wait > getMaxTimeout()) {
+        isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
+                  " not exceed " << getMaxTimeout()
+                  << ". Provided timeout value is '" << wait << "'");
+    }
+    impl_->doUpdate(io_service, ns_addr, ns_port, update, wait);
+}
+
+
+
+} // namespace d2
+} // namespace isc
+

+ 0 - 0
src/bin/d2/dns_client.h


Some files were not shown because too many files changed in this diff