Browse Source

Merge branch 'trac2238' into trac2269

Tomek Mrugalski 12 years ago
parent
commit
e3cc1803c6
100 changed files with 6223 additions and 766 deletions
  1. 15 5
      ChangeLog
  2. 1 1
      Makefile.am
  3. 1 0
      configure.ac
  4. 2 1
      doc/devel/mainpage.dox
  5. 3 1
      src/bin/dhcp4/.gitignore
  6. 13 4
      src/bin/dhcp4/Makefile.am
  7. 23 20
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  8. 26 0
      src/bin/dhcp4/dhcp4_log.cc
  9. 59 0
      src/bin/dhcp4/dhcp4_log.h
  10. 98 0
      src/bin/dhcp4/dhcp4_messages.mes
  11. 66 27
      src/bin/dhcp4/dhcp4_srv.cc
  12. 21 4
      src/bin/dhcp4/dhcp4_srv.h
  13. 29 31
      src/bin/dhcp4/main.cc
  14. 3 1
      src/bin/dhcp4/tests/Makefile.am
  15. 33 1
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  16. 29 27
      src/bin/dhcp4/tests/dhcp4_test.py
  17. 5 10
      src/bin/dhcp6/.gitignore
  18. 13 4
      src/bin/dhcp6/Makefile.am
  19. 23 20
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  20. 26 0
      src/bin/dhcp6/dhcp6_log.cc
  21. 59 0
      src/bin/dhcp6/dhcp6_log.h
  22. 101 0
      src/bin/dhcp6/dhcp6_messages.mes
  23. 88 27
      src/bin/dhcp6/dhcp6_srv.cc
  24. 18 0
      src/bin/dhcp6/dhcp6_srv.h
  25. 27 31
      src/bin/dhcp6/main.cc
  26. 1 0
      src/bin/dhcp6/tests/.gitignore
  27. 6 3
      src/bin/dhcp6/tests/Makefile.am
  28. 46 1
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  29. 28 25
      src/bin/dhcp6/tests/dhcp6_test.py
  30. 48 0
      src/bin/xfrout/b10-xfrout.xml
  31. 146 1
      src/bin/xfrout/tests/xfrout_test.py.in
  32. 143 9
      src/bin/xfrout/xfrout.py.in
  33. 59 0
      src/bin/xfrout/xfrout.spec.pre.in
  34. 4 0
      src/bin/xfrout/xfrout_messages.mes
  35. 10 13
      src/lib/asiolink/io_address.h
  36. 1 1
      src/lib/asiolink/tests/io_address_unittest.cc
  37. 4 4
      src/lib/config/ccsession.cc
  38. 1 2
      src/lib/datasrc/Makefile.am
  39. 2 0
      src/lib/datasrc/memory/.gitignore
  40. 13 1
      src/lib/datasrc/memory/Makefile.am
  41. 1 0
      src/lib/datasrc/memory/benchmarks/.gitignore
  42. 19 6
      src/lib/datasrc/memory/domaintree.h
  43. 25 0
      src/lib/datasrc/memory/logger.cc
  44. 52 0
      src/lib/datasrc/memory/logger.h
  45. 904 0
      src/lib/datasrc/memory/memory_client.cc
  46. 257 0
      src/lib/datasrc/memory/memory_client.h
  47. 90 0
      src/lib/datasrc/memory/memory_messages.mes
  48. 8 0
      src/lib/datasrc/memory/tests/Makefile.am
  49. 136 0
      src/lib/datasrc/memory/tests/domaintree_unittest.cc
  50. 740 0
      src/lib/datasrc/memory/tests/memory_client_unittest.cc
  51. 3 0
      src/lib/datasrc/memory/tests/run_unittests.cc
  52. 32 0
      src/lib/datasrc/memory/tests/testdata/Makefile.am
  53. 0 0
      src/lib/datasrc/memory/tests/testdata/empty.zone
  54. 1 0
      src/lib/datasrc/memory/tests/testdata/example.org-broken1.zone
  55. 5 0
      src/lib/datasrc/memory/tests/testdata/example.org-broken2.zone
  56. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-cname-and-not-nsec-1.zone
  57. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-cname-and-not-nsec-2.zone
  58. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-apex-1.zone
  59. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-apex-2.zone
  60. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-nonapex-1.zone
  61. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-nonapex-2.zone
  62. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-duplicate-type-bad.zone
  63. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-duplicate-type.zone
  64. 2 0
      src/lib/datasrc/memory/tests/testdata/example.org-empty.zone
  65. 3 0
      src/lib/datasrc/memory/tests/testdata/example.org-multiple-cname.zone
  66. 3 0
      src/lib/datasrc/memory/tests/testdata/example.org-multiple-dname.zone
  67. 3 0
      src/lib/datasrc/memory/tests/testdata/example.org-multiple-nsec3.zone
  68. 3 0
      src/lib/datasrc/memory/tests/testdata/example.org-multiple-nsec3param.zone
  69. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-multiple.zone
  70. 3 0
      src/lib/datasrc/memory/tests/testdata/example.org-nsec3-fewer-labels.zone
  71. 3 0
      src/lib/datasrc/memory/tests/testdata/example.org-nsec3-more-labels.zone
  72. 15 0
      src/lib/datasrc/memory/tests/testdata/example.org-nsec3-signed-no-param.zone
  73. 14 0
      src/lib/datasrc/memory/tests/testdata/example.org-nsec3-signed.zone
  74. 5 0
      src/lib/datasrc/memory/tests/testdata/example.org-out-of-zone.zone
  75. 5 0
      src/lib/datasrc/memory/tests/testdata/example.org-rrsig-follows-nothing.zone
  76. 6 0
      src/lib/datasrc/memory/tests/testdata/example.org-rrsig-name-unmatched.zone
  77. 6 0
      src/lib/datasrc/memory/tests/testdata/example.org-rrsig-type-unmatched.zone
  78. 8 0
      src/lib/datasrc/memory/tests/testdata/example.org-rrsigs.zone
  79. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-wildcard-dname.zone
  80. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-wildcard-ns.zone
  81. 4 0
      src/lib/datasrc/memory/tests/testdata/example.org-wildcard-nsec3.zone
  82. 81 0
      src/lib/datasrc/memory/tests/testdata/example.org.zone
  83. 9 8
      src/lib/datasrc/memory/tests/treenode_rrset_unittest.cc
  84. 1481 0
      src/lib/datasrc/memory/tests/zone_finder_unittest.cc
  85. 7 1
      src/lib/datasrc/memory/treenode_rrset.cc
  86. 7 4
      src/lib/datasrc/memory/treenode_rrset.h
  87. 1 0
      src/lib/datasrc/memory/zone_data.h
  88. 600 0
      src/lib/datasrc/memory/zone_finder.cc
  89. 128 0
      src/lib/datasrc/memory/zone_finder.h
  90. 14 0
      src/lib/datasrc/memory/zone_table.cc
  91. 12 2
      src/lib/datasrc/memory/zone_table.h
  92. 3 0
      src/lib/dhcp/Makefile.am
  93. 42 8
      src/lib/dhcp/addr_utilities.cc
  94. 8 4
      src/lib/dhcp/addr_utilities.h
  95. 7 119
      src/lib/dhcp/cfgmgr.cc
  96. 2 321
      src/lib/dhcp/cfgmgr.h
  97. 29 13
      src/lib/dhcp/iface_mgr.cc
  98. 11 5
      src/lib/dhcp/iface_mgr.h
  99. 87 0
      src/lib/dhcp/pool.cc
  100. 0 0
      src/lib/dhcp/pool.h

+ 15 - 5
ChangeLog

@@ -1,11 +1,21 @@
 4XX.	[func]		tomek
 4XX.	[func]		tomek
-	A new library (libb10-dhcpsrv) has been create. Currently its
-	functionality is limited to a Configuration Manager. CfgMgr
-	currently only supports basic configuration storage for DHCPv6
-	server,	but that capability is expected to be expanded in a near
-	future.
+	A new library (libb10-dhcpsrv) has been created. At present, it
+	only holds the code for the DHCP Configuration Manager. Currently
+	this object only supports basic configuration storage for the DHCPv6
+	server,	but that capability will be expanded.
 	(Trac #2238, git TBD)
 	(Trac #2238, git TBD)
 
 
+475.	[func]		naokikambe
+	Added Xfrout statistics counters: notifyoutv4, notifyoutv6, xfrrej, and
+	xfrreqdone. These are per-zone type counters. The value of these
+	counters can be seen with zone name by invoking "Stats show Xfrout" via
+	bindctl.
+	(Trac #2158, git e68c127fed52e6034ab5309ddd506da03c37a08a)
+
+474.	[func]      stephen
+	DHCP servers now use the BIND 10 logging system for messages.
+	(Trac #1545, git de69a92613b36bd3944cb061e1b7c611c3c85506)
+
 473.	[bug]		jelte
 473.	[bug]		jelte
 	TCP connections now time out in b10-auth if no (or not all) query
 	TCP connections now time out in b10-auth if no (or not all) query
 	data is sent by the client. The timeout value defaults to 5000
 	data is sent by the client. The timeout value defaults to 5000

+ 1 - 1
Makefile.am

@@ -30,7 +30,7 @@ endif
 
 
 check-valgrind-suppress:
 check-valgrind-suppress:
 if HAVE_VALGRIND
 if HAVE_VALGRIND
-	@VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --error-exitcode=1 --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions.revisit --num-callers=48 --leak-check=full --fullpath-after=" \
+	@VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --track-origins=yes --error-exitcode=1 --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions.revisit --num-callers=48 --leak-check=full --fullpath-after=" \
 	make -C $(abs_top_builddir) check
 	make -C $(abs_top_builddir) check
 else
 else
 	@echo "*** Valgrind is required for check-valgrind-suppress ***"; exit 1;
 	@echo "*** Valgrind is required for check-valgrind-suppress ***"; exit 1;

+ 1 - 0
configure.ac

@@ -1188,6 +1188,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/datasrc/Makefile
                  src/lib/datasrc/Makefile
                  src/lib/datasrc/memory/Makefile
                  src/lib/datasrc/memory/Makefile
                  src/lib/datasrc/memory/tests/Makefile
                  src/lib/datasrc/memory/tests/Makefile
+                 src/lib/datasrc/memory/tests/testdata/Makefile
                  src/lib/datasrc/memory/benchmarks/Makefile
                  src/lib/datasrc/memory/benchmarks/Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/datasrc/tests/testdata/Makefile
                  src/lib/datasrc/tests/testdata/Makefile

+ 2 - 1
doc/devel/mainpage.dox

@@ -24,6 +24,7 @@
  * - @subpage libdhcp
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIfaceMgr
  *   - @subpage libdhcpIfaceMgr
+ * - @subpage perfdhcpInternals
  *
  *
  * @section misc Miscellaneous topics
  * @section misc Miscellaneous topics
  * - @subpage LoggingApi
  * - @subpage LoggingApi
@@ -36,4 +37,4 @@
  * @todo: Move this logo to the right (and possibly up). Not sure what
  * @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.
  * is the best way to do it in Doxygen, without using CSS hacks.
  * @image html isc-logo.png
  * @image html isc-logo.png
- */
+ */

+ 3 - 1
src/bin/dhcp4/.gitignore

@@ -1,4 +1,6 @@
 /b10-dhcp4
 /b10-dhcp4
+/b10-dhcp4.8
+/dhcp4_messages.cc
+/dhcp4_messages.h
 /spec_config.h
 /spec_config.h
 /spec_config.h.pre
 /spec_config.h.pre
-/b10-dhcp4.8

+ 13 - 4
src/bin/dhcp4/Makefile.am

@@ -12,7 +12,7 @@ endif
 
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 
-CLEANFILES = spec_config.h
+CLEANFILES  = *.gcno *.gcda spec_config.h dhcp4_messages.h dhcp4_messages.cc
 
 
 man_MANS = b10-dhcp4.8
 man_MANS = b10-dhcp4.8
 DISTCLEANFILES = $(man_MANS)
 DISTCLEANFILES = $(man_MANS)
@@ -35,11 +35,20 @@ endif
 spec_config.h: spec_config.h.pre
 spec_config.h: spec_config.h.pre
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
 
 
-BUILT_SOURCES = spec_config.h
+dhcp4_messages.h dhcp4_messages.cc: dhcp4_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/dhcp4/dhcp4_messages.mes
+
+BUILT_SOURCES = spec_config.h dhcp4_messages.h dhcp4_messages.cc
+
 pkglibexec_PROGRAMS = b10-dhcp4
 pkglibexec_PROGRAMS = b10-dhcp4
 
 
-b10_dhcp4_SOURCES = main.cc dhcp4_srv.cc dhcp4_srv.h
+b10_dhcp4_SOURCES  = main.cc
 b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
 b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
+b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
+b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
+
+nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
+EXTRA_DIST += dhcp4_messages.mes
 
 
 if USE_CLANGPP
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Disable unused parameter warning caused by some of the
@@ -47,7 +56,7 @@ if USE_CLANGPP
 b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
 b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
 endif
 endif
 
 
-b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp4_LDADD  = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la

+ 23 - 20
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -13,28 +13,30 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
+
 #include <cassert>
 #include <cassert>
 #include <iostream>
 #include <iostream>
 
 
-#include <cc/session.h>
+#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/data.h>
-#include <exceptions/exceptions.h>
+#include <cc/session.h>
 #include <cc/session.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
-#include <util/buffer.h>
-#include <dhcp4/spec_config.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/spec_config.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
-#include <asiolink/asiolink.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
 
 
-using namespace std;
-using namespace isc::util;
-using namespace isc::dhcp;
-using namespace isc::util;
-using namespace isc::data;
+using namespace isc::asiolink;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::config;
-using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::log;
+using namespace isc::util;
+using namespace std;
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -43,7 +45,8 @@ ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
 
 
 ConstElementPtr
 ConstElementPtr
 ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
 ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
-    cout << "b10-dhcp4: Received new config:" << new_config->str() << endl;
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
+              .arg(new_config->str());
     ConstElementPtr answer = isc::config::createAnswer(0,
     ConstElementPtr answer = isc::config::createAnswer(0,
                              "Thank you for sending config.");
                              "Thank you for sending config.");
     return (answer);
     return (answer);
@@ -51,13 +54,14 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
 
 
 ConstElementPtr
 ConstElementPtr
 ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
 ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
-    cout << "b10-dhcp4: Received new command: [" << command << "], args="
-         << args->str() << endl;
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
+              .arg(command).arg(args->str());
+
     if (command == "shutdown") {
     if (command == "shutdown") {
         if (ControlledDhcpv4Srv::server_) {
         if (ControlledDhcpv4Srv::server_) {
             ControlledDhcpv4Srv::server_->shutdown();
             ControlledDhcpv4Srv::server_->shutdown();
         } else {
         } else {
-            cout << "Server not initialized yet or already shut down." << endl;
+            LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
             ConstElementPtr answer = isc::config::createAnswer(1,
             ConstElementPtr answer = isc::config::createAnswer(1,
                                      "Shutdown failure.");
                                      "Shutdown failure.");
             return (answer);
             return (answer);
@@ -93,10 +97,9 @@ void ControlledDhcpv4Srv::establishSession() {
 
 
     /// @todo: Check if session is not established already. Throw, if it is.
     /// @todo: Check if session is not established already. Throw, if it is.
 
 
-    cout << "b10-dhcp4: my specfile is " << specfile << endl;
-
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
+              .arg(specfile);
     cc_session_ = new Session(io_service_.get_io_service());
     cc_session_ = new Session(io_service_.get_io_service());
-
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
                                           dhcp4ConfigHandler,
                                           dhcp4ConfigHandler,
                                           dhcp4CommandHandler, false);
                                           dhcp4CommandHandler, false);
@@ -106,8 +109,8 @@ void ControlledDhcpv4Srv::establishSession() {
     /// control with the "select" model of the DHCP server.  This is
     /// control with the "select" model of the DHCP server.  This is
     /// fully explained in \ref dhcpv4Session.
     /// fully explained in \ref dhcpv4Session.
     int ctrl_socket = cc_session_->getSocketDesc();
     int ctrl_socket = cc_session_->getSocketDesc();
-    cout << "b10-dhcp4: Control session started, socket="
-         << ctrl_socket << endl;
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
+              .arg(ctrl_socket);
     IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
     IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
 }
 }
 
 

+ 26 - 0
src/bin/dhcp4/dhcp4_log.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the top-level component of b10-dhcp4.
+
+#include "dhcp4_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger dhcp4_logger("dhcp4");
+
+} // namespace dhcp
+} // namespace isc
+

+ 59 - 0
src/bin/dhcp4/dhcp4_log.h

@@ -0,0 +1,59 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DHCP4_LOG__H
+#define __DHCP4_LOG__H
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <dhcp4/dhcp4_messages.h>
+
+namespace isc {
+namespace dhcp {
+
+/// \brief DHCP4 Logging
+///
+/// Defines the levels used to output debug messages in the non-library part of
+/// the b10-dhcp4 program.  Higher numbers equate to more verbose (and detailed)
+/// output.
+
+// Debug levels used to log information during startup and shutdown.
+const int DBG_DHCP4_START = DBGLVL_START_SHUT;
+const int DBG_DHCP4_SHUT = DBGLVL_START_SHUT;
+
+// Debug level used to log setting information (such as configuration changes).
+const int DBG_DHCP4_COMMAND = DBGLVL_COMMAND;
+
+// Trace basic operations within the code.
+const int DBG_DHCP4_BASIC = DBGLVL_TRACE_BASIC;
+
+// Trace detailed operations, including errors raised when processing invalid
+// packets.  (These are not logged at severities of WARN or higher for fear
+// that a set of deliberately invalid packets set to the server could overwhelm
+// the logging.)
+const int DBG_DHCP4_DETAIL = DBGLVL_TRACE_DETAIL;
+
+// This level is used to log the contents of packets received and sent.
+const int DBG_DHCP4_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+/// Define the logger for the "dhcp4" module part of b10-dhcp4.  We could define
+/// a logger in each file, but we would want to define a common name to avoid
+/// spelling mistakes, so it is just one small step from there to define a
+/// module-common logger.
+extern isc::log::Logger dhcp4_logger;
+
+} // namespace dhcp4
+} // namespace isc
+
+#endif // __DHCP4_LOG__H

+ 98 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -0,0 +1,98 @@
+# Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% DHCP4_CCSESSION_STARTED control channel session started on socket %1
+A debug message issued during startup after the IPv4 DHCP server has
+successfully established a session with the BIND 10 control channel.
+
+% DHCP4_CCSESSION_STARTING starting control channel session, specfile: %1
+This debug message is issued just before the IPv4 DHCP server attempts
+to establish a session with the BIND 10 control channel.
+
+% DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
+A debug message listing the command (and possible arguments) received
+from the BIND 10 control system by the IPv4 DHCP server.
+
+% DHCP4_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the IPv4 DHCP server has received an
+updated configuration from the BIND 10 configuration system.
+
+% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
+A warning message is issued when an attempt is made to shut down the
+IPv4 DHCP server but it is not running.
+
+% DHCP4_OPEN_SOCKET opening sockets on port %1
+A debug message issued during startup, this indicates that the IPv4 DHCP
+server is about to open sockets on the specified port.
+
+% DHCP4_PACKET_PARSE_FAIL failed to parse incoming packet: %1
+The IPv4 DHCP server has received a packet that it is unable to
+interpret. The reason why the packet is invalid is included in the message.
+
+% DHCP4_PACKET_RECEIVED %1 (type %2) packet received on interface %3
+A debug message noting that the server has received the specified type of
+packet on the specified interface.  Note that a packet marked as UNKNOWN
+may well be a valid DHCP packet, just a type not expected by the server
+(e.g. it will report a received OFFER packet as UNKNOWN).
+
+% DHCP4_PACK_FAIL failed to assemble response correctly
+This error is output if the server failed to assemble the data to be
+returned to the client into a valid packet.  The cause is most likely
+to be a programming error: please raise a bug report.
+
+% DHCP4_QUERY_DATA received packet type %1, data is <%2>
+A debug message listing the data received from the client.
+
+% DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
+A debug message listing the data returned to the client.
+
+% DHCP4_SERVER_FAILED server failed: %1
+The IPv4 DHCP server has encountered a fatal error and is terminating.
+The reason for the failure is included in the message.
+
+% DHCP4_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
+The server has failed to establish communication with the rest of BIND
+10 and is running in stand-alone mode.  (This behavior will change once
+the IPv4 DHCP server is properly integrated with the rest of BIND 10.)
+
+% DHCP4_SHUTDOWN server shutdown
+The IPv4 DHCP server has terminated normally.
+
+% DHCP4_SHUTDOWN_REQUEST shutdown of server requested
+This debug message indicates that a shutdown of the IPv4 server has
+been requested via a call to the 'shutdown' method of the core Dhcpv4Srv
+object.
+
+% DHCP4_SRV_CONSTRUCT_ERROR error creating Dhcpv4Srv object, reason: %1
+This error message indicates that during startup, the construction of a
+core component within the IPv4 DHCP server (the Dhcpv4 server object)
+has failed.  As a result, the server will exit.  The reason for the
+failure is given within the message.
+
+% DHCP4_STANDALONE skipping message queue, running standalone
+This is a debug message indicating that the IPv4 server is running in
+standalone mode, not connected to the message queue.  Standalone mode
+is only useful during program development, and should not be used in a
+production environment.
+
+% DHCP4_STARTING server starting
+This informational message indicates that the IPv4 DHCP server has
+processed any command-line switches and is starting.
+
+% DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
+This is a debug message issued during the IPv4 DHCP server startup.
+It lists some information about the parameters with which the server
+is running.

+ 66 - 27
src/bin/dhcp4/dhcp4_srv.cc

@@ -16,13 +16,15 @@
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option4_addrlst.h>
 
 
-using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::log;
+using namespace std;
 
 
 // These are hardcoded parameters. Currently this is a skeleton server that only
 // These are hardcoded parameters. Currently this is a skeleton server that only
 // grants those options and a single, fixed, hardcoded lease.
 // grants those options and a single, fixed, hardcoded lease.
@@ -35,20 +37,19 @@ const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
 const std::string HARDCODED_SERVER_ID = "192.0.2.1";
 const std::string HARDCODED_SERVER_ID = "192.0.2.1";
 
 
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
-    cout << "Initialization: opening sockets on port " << port << endl;
-
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
     try {
-        // first call to instance() will create IfaceMgr (it's a singleton)
+        // First call to instance() will create IfaceMgr (it's a singleton)
         // it may throw something if things go wrong
         // it may throw something if things go wrong
         IfaceMgr::instance();
         IfaceMgr::instance();
 
 
         /// @todo: instantiate LeaseMgr here once it is imlpemented.
         /// @todo: instantiate LeaseMgr here once it is imlpemented.
-
         IfaceMgr::instance().openSockets4(port);
         IfaceMgr::instance().openSockets4(port);
 
 
         setServerID();
         setServerID();
+
     } catch (const std::exception &e) {
     } catch (const std::exception &e) {
-        cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
+        LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
         shutdown_ = true;
         shutdown_ = true;
         return;
         return;
     }
     }
@@ -57,12 +58,11 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
 }
 }
 
 
 Dhcpv4Srv::~Dhcpv4Srv() {
 Dhcpv4Srv::~Dhcpv4Srv() {
-    cout << "b10-dhcp4: DHCPv4 server terminating." << endl;
     IfaceMgr::instance().closeSockets();
     IfaceMgr::instance().closeSockets();
 }
 }
 
 
 void Dhcpv4Srv::shutdown() {
 void Dhcpv4Srv::shutdown() {
-    cout << "b10-dhcp4: DHCPv4 server shutdown." << endl;
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
     shutdown_ = true;
     shutdown_ = true;
 }
 }
 
 
@@ -79,39 +79,48 @@ Dhcpv4Srv::run() {
         if (query) {
         if (query) {
             try {
             try {
                 query->unpack();
                 query->unpack();
+
             } catch (const std::exception& e) {
             } catch (const std::exception& e) {
-                /// TODO: Printout reasons of failed parsing
-                cout << "Failed to parse incoming packet " << endl;
+                // Failed to parse the packet.
+                LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                          DHCP4_PACKET_PARSE_FAIL).arg(e.what());
                 continue;
                 continue;
             }
             }
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
+                      .arg(serverReceivedPacketName(query->getType()))
+                      .arg(query->getType())
+                      .arg(query->getIface());
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
+                      .arg(query->toText());
 
 
             switch (query->getType()) {
             switch (query->getType()) {
             case DHCPDISCOVER:
             case DHCPDISCOVER:
                 rsp = processDiscover(query);
                 rsp = processDiscover(query);
                 break;
                 break;
+
             case DHCPREQUEST:
             case DHCPREQUEST:
                 rsp = processRequest(query);
                 rsp = processRequest(query);
                 break;
                 break;
+
             case DHCPRELEASE:
             case DHCPRELEASE:
                 processRelease(query);
                 processRelease(query);
                 break;
                 break;
+
             case DHCPDECLINE:
             case DHCPDECLINE:
                 processDecline(query);
                 processDecline(query);
                 break;
                 break;
+
             case DHCPINFORM:
             case DHCPINFORM:
                 processInform(query);
                 processInform(query);
                 break;
                 break;
+
             default:
             default:
-                cout << "Unknown pkt type received:"
-                     << query->getType() << endl;
+                // Only action is to output a message if debug is enabled,
+                // and that will be covered by the debug statement before
+                // the "switch" statement.
+                ;
             }
             }
 
 
-            cout << "Received message type " << int(query->getType()) << endl;
-
-            // TODO: print out received packets only if verbose (or debug)
-            // mode is enabled
-            cout << query->toText();
-
             if (rsp) {
             if (rsp) {
                 if (rsp->getRemoteAddr().toText() == "0.0.0.0") {
                 if (rsp->getRemoteAddr().toText() == "0.0.0.0") {
                     rsp->setRemoteAddr(query->getRemoteAddr());
                     rsp->setRemoteAddr(query->getRemoteAddr());
@@ -127,14 +136,15 @@ Dhcpv4Srv::run() {
                 rsp->setIface(query->getIface());
                 rsp->setIface(query->getIface());
                 rsp->setIndex(query->getIndex());
                 rsp->setIndex(query->getIndex());
 
 
-                cout << "Replying with message type "
-                     << static_cast<int>(rsp->getType()) << ":" << endl;
-                cout << rsp->toText();
-                cout << "----" << endl;
+                LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA,
+                          DHCP4_RESPONSE_DATA)
+                          .arg(rsp->getType()).arg(rsp->toText());
+
                 if (rsp->pack()) {
                 if (rsp->pack()) {
-                    cout << "Packet assembled correctly." << endl;
+                    IfaceMgr::instance().send(rsp);
+                } else {
+                    LOG_ERROR(dhcp4_logger, DHCP4_PACK_FAIL);
                 }
                 }
-                IfaceMgr::instance().send(rsp);
             }
             }
         }
         }
 
 
@@ -266,15 +276,44 @@ Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
 
 
 void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
     /// TODO: Implement this.
     /// TODO: Implement this.
-    cout << "Received RELEASE on " << release->getIface() << " interface." << endl;
 }
 }
 
 
 void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
 void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
     /// TODO: Implement this.
     /// TODO: Implement this.
-    cout << "Received DECLINE on " << decline->getIface() << " interface." << endl;
 }
 }
 
 
 Pkt4Ptr Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
 Pkt4Ptr Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
     /// TODO: Currently implemented echo mode. Implement this for real
     /// TODO: Currently implemented echo mode. Implement this for real
     return (inform);
     return (inform);
 }
 }
+
+const char*
+Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
+    static const char* DISCOVER = "DISCOVER";
+    static const char* REQUEST = "REQUEST";
+    static const char* RELEASE = "RELEASE";
+    static const char* DECLINE = "DECLINE";
+    static const char* INFORM = "INFORM";
+    static const char* UNKNOWN = "UNKNOWN";
+
+    switch (type) {
+    case DHCPDISCOVER:
+        return (DISCOVER);
+
+    case DHCPREQUEST:
+        return (REQUEST);
+
+    case DHCPRELEASE:
+        return (RELEASE);
+
+    case DHCPDECLINE:
+        return (DECLINE);
+
+    case DHCPINFORM:
+        return (INFORM);
+
+    default:
+        ;
+    }
+    return (UNKNOWN);
+}

+ 21 - 4
src/bin/dhcp4/dhcp4_srv.h

@@ -44,7 +44,7 @@ class Dhcpv4Srv : public boost::noncopyable {
     public:
     public:
     /// @brief Default constructor.
     /// @brief Default constructor.
     ///
     ///
-    /// Instantiates necessary services, required to run DHCPv6 server.
+    /// Instantiates necessary services, required to run DHCPv4 server.
     /// In particular, creates IfaceMgr that will be responsible for
     /// In particular, creates IfaceMgr that will be responsible for
     /// network interaction. Will instantiate lease manager, and load
     /// network interaction. Will instantiate lease manager, and load
     /// old or create new DUID. It is possible to specify alternate
     /// old or create new DUID. It is possible to specify alternate
@@ -54,7 +54,7 @@ class Dhcpv4Srv : public boost::noncopyable {
     /// @param port specifies port number to listen on
     /// @param port specifies port number to listen on
     Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
     Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
 
 
-    /// @brief Destructor. Used during DHCPv6 service shutdown.
+    /// @brief Destructor. Used during DHCPv4 service shutdown.
     ~Dhcpv4Srv();
     ~Dhcpv4Srv();
 
 
     /// @brief Main server processing loop.
     /// @brief Main server processing loop.
@@ -70,6 +70,23 @@ class Dhcpv4Srv : public boost::noncopyable {
     /// @brief Instructs the server to shut down.
     /// @brief Instructs the server to shut down.
     void shutdown();
     void shutdown();
 
 
+    /// @brief Return textual type of packet received by server
+    ///
+    /// Returns the name of valid packet received by the server (e.g. DISCOVER).
+    /// If the packet is unknown - or if it is a valid DHCP packet but not one
+    /// expected to be received by the server (such as an OFFER), the string
+    /// "UNKNOWN" is returned.  This method is used in debug messages.
+    ///
+    /// As the operation of the method does not depend on any server state, it
+    /// is declared static.
+    ///
+    /// @param type DHCPv4 packet type
+    ///
+    /// @return Pointer to "const" string containing the packet name.
+    ///         Note that this string is statically allocated and MUST NOT
+    ///         be freed by the caller.
+    static const char* serverReceivedPacketName(uint8_t type);
+
 protected:
 protected:
     /// @brief Processes incoming DISCOVER and returns response.
     /// @brief Processes incoming DISCOVER and returns response.
     ///
     ///
@@ -89,11 +106,11 @@ protected:
     /// is valid, not expired, not reserved, not used by other client and
     /// is valid, not expired, not reserved, not used by other client and
     /// that requesting client is allowed to use it.
     /// that requesting client is allowed to use it.
     ///
     ///
-    /// Returns ACK message, NACK message, or NULL
+    /// Returns ACK message, NAK message, or NULL
     ///
     ///
     /// @param request a message received from client
     /// @param request a message received from client
     ///
     ///
-    /// @return ACK or NACK message
+    /// @return ACK or NAK message
     Pkt4Ptr processRequest(Pkt4Ptr& request);
     Pkt4Ptr processRequest(Pkt4Ptr& request);
 
 
     /// @brief Stub function that will handle incoming RELEASE messages.
     /// @brief Stub function that will handle incoming RELEASE messages.

+ 29 - 31
src/bin/dhcp4/main.cc

@@ -14,13 +14,15 @@
 
 
 #include <config.h>
 #include <config.h>
 #include <iostream>
 #include <iostream>
-#include <log/dummylog.h>
-#include <log/logger_support.h>
-#include <dhcp4/ctrl_dhcp4_srv.h>
+
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 
 
-using namespace std;
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <log/logger_support.h>
+
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace std;
 
 
 /// This file contains entry point (main() function) for standard DHCPv4 server
 /// This file contains entry point (main() function) for standard DHCPv4 server
 /// component for BIND10 framework. It parses command-line arguments and
 /// component for BIND10 framework. It parses command-line arguments and
@@ -37,11 +39,10 @@ const char* const DHCP4_NAME = "b10-dhcp4";
 
 
 void
 void
 usage() {
 usage() {
-    cerr << "Usage:  b10-dhcp4 [-v]"
-         << endl;
-    cerr << "\t-v: verbose output" << endl;
-    cerr << "\t-s: stand-alone mode (don't connect to BIND10)" << endl;
-    cerr << "\t-p number: specify non-standard port number 1-65535 "
+    cerr << "Usage: " << DHCP4_NAME << " [-v] [-s] [-p number]" << endl;
+    cerr << "  -v: verbose output" << endl;
+    cerr << "  -s: stand-alone mode (don't connect to BIND10)" << endl;
+    cerr << "  -p number: specify non-standard port number 1-65535 "
          << "(useful for testing only)" << endl;
          << "(useful for testing only)" << endl;
     exit(EXIT_FAILURE);
     exit(EXIT_FAILURE);
 }
 }
@@ -50,20 +51,21 @@ usage() {
 int
 int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
     int ch;
     int ch;
-    bool verbose_mode = false; // should server be verbose?
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
                                          // useful for testing only.
                                          // useful for testing only.
-    bool stand_alone = false; // should be connect to BIND10 msgq?
+    bool stand_alone = false;  // Should be connect to BIND10 msgq?
+    bool verbose_mode = false; // Should server be verbose?
 
 
     while ((ch = getopt(argc, argv, "vsp:")) != -1) {
     while ((ch = getopt(argc, argv, "vsp:")) != -1) {
         switch (ch) {
         switch (ch) {
         case 'v':
         case 'v':
             verbose_mode = true;
             verbose_mode = true;
-            isc::log::denabled = true;
             break;
             break;
+
         case 's':
         case 's':
             stand_alone = true;
             stand_alone = true;
             break;
             break;
+
         case 'p':
         case 'p':
             try {
             try {
                 port_number = boost::lexical_cast<int>(optarg);
                 port_number = boost::lexical_cast<int>(optarg);
@@ -78,50 +80,46 @@ main(int argc, char* argv[]) {
                 usage();
                 usage();
             }
             }
             break;
             break;
-        case ':':
+
         default:
         default:
             usage();
             usage();
         }
         }
     }
     }
 
 
+    // Check for extraneous parameters.
+    if (argc > optind) {
+        usage();
+    }
+
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(DHCP4_NAME,
     isc::log::initLogger(DHCP4_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
                          isc::log::MAX_DEBUG_LEVEL, NULL);
                          isc::log::MAX_DEBUG_LEVEL, NULL);
+    LOG_INFO(dhcp4_logger, DHCP4_STARTING);
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
+              .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
+              .arg(stand_alone ? "yes" : "no" );
 
 
-    cout << "b10-dhcp4: My pid=" << getpid() << ", binding to port "
-         << port_number << ", verbose " << (verbose_mode?"yes":"no")
-         << ", stand-alone=" << (stand_alone?"yes":"no") << endl;
-
-    if (argc - optind > 0) {
-        usage();
-    }
 
 
     int ret = EXIT_SUCCESS;
     int ret = EXIT_SUCCESS;
-
     try {
     try {
-
-        cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
-
-        /// @todo: pass verbose to the actul server once logging is implemented
         ControlledDhcpv4Srv server(port_number);
         ControlledDhcpv4Srv server(port_number);
-
         if (!stand_alone) {
         if (!stand_alone) {
             try {
             try {
                 server.establishSession();
                 server.establishSession();
             } catch (const std::exception& ex) {
             } catch (const std::exception& ex) {
-                cerr << "Failed to establish BIND10 session. "
-                    "Running in stand-alone mode:" << ex.what() << endl;
+                LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
                 // Let's continue. It is useful to have the ability to run
                 // Let's continue. It is useful to have the ability to run
                 // DHCP server in stand-alone mode, e.g. for testing
                 // DHCP server in stand-alone mode, e.g. for testing
             }
             }
         } else {
         } else {
-            cout << "Skipping connection to the BIND10 msgq." << endl;
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
         }
         }
-
         server.run();
         server.run();
+        LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
+
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
-        cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
+        LOG_FATAL(dhcp4_logger, DHCP4_SERVER_FAILED).arg(ex.what());
         ret = EXIT_FAILURE;
         ret = EXIT_FAILURE;
     }
     }
 
 

+ 3 - 1
src/bin/dhcp4/tests/Makefile.am

@@ -30,7 +30,7 @@ AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
-CLEANFILES = $(builddir)/interfaces.txt
+CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
@@ -47,9 +47,11 @@ if HAVE_GTEST
 TESTS += dhcp4_unittests
 TESTS += dhcp4_unittests
 
 
 dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
 dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
+dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
+nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
 
 
 if USE_CLANGPP
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Disable unused parameter warning caused by some of the

+ 33 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -271,4 +271,36 @@ TEST_F(Dhcpv4SrvTest, processInform) {
     delete srv;
     delete srv;
 }
 }
 
 
+TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
+    // Check all possible packet types
+    for (int itype = 0; itype < 256; ++itype) {
+        uint8_t type = itype;
+
+        switch (type) {
+        case DHCPDECLINE:
+            EXPECT_STREQ("DECLINE", Dhcpv4Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPDISCOVER:
+            EXPECT_STREQ("DISCOVER", Dhcpv4Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPINFORM:
+            EXPECT_STREQ("INFORM", Dhcpv4Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPRELEASE:
+            EXPECT_STREQ("RELEASE", Dhcpv4Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPREQUEST:
+            EXPECT_STREQ("REQUEST", Dhcpv4Srv::serverReceivedPacketName(type));
+            break;
+
+        default:
+            EXPECT_STREQ("UNKNOWN", Dhcpv4Srv::serverReceivedPacketName(type));
+        }
+    }
+}
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 29 - 27
src/bin/dhcp4/tests/dhcp4_test.py

@@ -27,16 +27,27 @@ import fcntl
 
 
 class TestDhcpv4Daemon(unittest.TestCase):
 class TestDhcpv4Daemon(unittest.TestCase):
     def setUp(self):
     def setUp(self):
-        # don't redirect stdout/stderr here as we want to print out things
+        # Don't redirect stdout/stderr here as we want to print out things
         # during the test
         # during the test
-        pass
+        #
+        # However, we do want to set the logging lock directory to somewhere
+        # to which we can write - use the current working directory.  We then
+        # set the appropriate environment variable.  os.putenv() may be not
+        # supported on some platforms as suggested in
+        # http://docs.python.org/release/3.2/library/os.html?highlight=putenv#os.environ:
+        # "If the platform supports the putenv() function...". It was checked
+        # that it does not work on Ubuntu. To overcome this problem we access
+        # os.environ directly.
+        lockdir_envvar = "B10_LOCKFILE_DIR_FROM_BUILD"
+        if lockdir_envvar not in os.environ:
+            os.environ[lockdir_envvar] = os.getcwd()
 
 
     def tearDown(self):
     def tearDown(self):
         pass
         pass
 
 
     def runCommand(self, params, wait=1):
     def runCommand(self, params, wait=1):
         """
         """
-        This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
+        This method runs dhcp4 and returns a tuple: (returncode, stdout, stderr)
         """
         """
         ## @todo: Convert this into generic method and reuse it in dhcp6
         ## @todo: Convert this into generic method and reuse it in dhcp6
 
 
@@ -79,9 +90,9 @@ class TestDhcpv4Daemon(unittest.TestCase):
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
 
 
         # There's potential problem if b10-dhcp4 prints out more
         # There's potential problem if b10-dhcp4 prints out more
-        # than 4k of text
+        # than 16kB of text
         try:
         try:
-            output = os.read(self.stdout_pipes[0], 4096)
+            output = os.read(self.stdout_pipes[0], 16384)
         except OSError:
         except OSError:
             print("No data available from stdout")
             print("No data available from stdout")
             output = ""
             output = ""
@@ -91,7 +102,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
             output = ""
             output = ""
 
 
         try:
         try:
-            error = os.read(self.stderr_pipes[0], 4096)
+            error = os.read(self.stderr_pipes[0], 16384)
         except OSError:
         except OSError:
             print("No data available on stderr")
             print("No data available on stderr")
             error = ""
             error = ""
@@ -128,13 +139,13 @@ class TestDhcpv4Daemon(unittest.TestCase):
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
 
 
         (returncode, output, error) = self.runCommand(["../b10-dhcp4", "-v"])
         (returncode, output, error) = self.runCommand(["../b10-dhcp4", "-v"])
-
-        self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
+        output_text = str(output) + str(error)
+        self.assertEqual(output_text.count("DHCP4_STARTING"), 1)
 
 
     def test_portnumber_0(self):
     def test_portnumber_0(self):
         print("Check that specifying port number 0 is not allowed.")
         print("Check that specifying port number 0 is not allowed.")
 
 
-        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p', '0'])
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-p', '0'])
 
 
         # When invalid port number is specified, return code must not be success
         # When invalid port number is specified, return code must not be success
         self.assertTrue(returncode != 0)
         self.assertTrue(returncode != 0)
@@ -178,28 +189,19 @@ class TestDhcpv4Daemon(unittest.TestCase):
     def test_portnumber_nonroot(self):
     def test_portnumber_nonroot(self):
         print("Check that specifying unprivileged port number will work.")
         print("Check that specifying unprivileged port number will work.")
 
 
-        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
-
-        # When invalid port number is specified, return code must not be success
-        # TODO: Temporarily commented out as socket binding on systems that do not have
-        #       interface detection implemented currently fails.
-        # self.assertTrue(returncode == 0)
-
-        # Check that there is an error message about invalid port number printed on stderr
-        self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
+        # Check that there is a message about running with an unprivileged port
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
+        output_text = str(output) + str(error)
+        self.assertEqual(output_text.count("DHCP4_OPEN_SOCKET opening sockets on port 10057"), 1)
 
 
     def test_skip_msgq(self):
     def test_skip_msgq(self):
         print("Check that connection to BIND10 msgq can be disabled.")
         print("Check that connection to BIND10 msgq can be disabled.")
 
 
-        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
-
-        # When invalid port number is specified, return code must not be success
-        # TODO: Temporarily commented out as socket binding on systems that do not have
-        #       interface detection implemented currently fails.
-        # self.assertTrue(returncode == 0)
-
-        # Check that there is an error message about invalid port number printed on stderr
-        self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
+        # Check that the system outputs a message on one of its streams about running
+        # standalone.
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
+        output_text = str(output) + str(error)
+        self.assertEqual(output_text.count("DHCP4_STANDALONE"), 1)
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 5 - 10
src/bin/dhcp6/.gitignore

@@ -1,11 +1,6 @@
-*~
-Makefile
-Makefile.in
-*.o
-.deps
-.libs
-b10-dhcp6
-spec_config.h
-spec_config.h.pre
-tests/dhcp6_unittests
+/b10-dhcp6
 /b10-dhcp6.8
 /b10-dhcp6.8
+/dhcp6_messages.cc
+/dhcp6_messages.h
+/spec_config.h
+/spec_config.h.pre

+ 13 - 4
src/bin/dhcp6/Makefile.am

@@ -13,7 +13,7 @@ endif
 
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 
-CLEANFILES = spec_config.h
+CLEANFILES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
 
 
 man_MANS = b10-dhcp6.8
 man_MANS = b10-dhcp6.8
 DISTCLEANFILES = $(man_MANS)
 DISTCLEANFILES = $(man_MANS)
@@ -37,11 +37,20 @@ endif
 spec_config.h: spec_config.h.pre
 spec_config.h: spec_config.h.pre
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
 
 
-BUILT_SOURCES = spec_config.h
+dhcp6_messages.h dhcp6_messages.cc: dhcp6_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/dhcp6/dhcp6_messages.mes
+
+BUILT_SOURCES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
+
 pkglibexec_PROGRAMS = b10-dhcp6
 pkglibexec_PROGRAMS = b10-dhcp6
 
 
-b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
+b10_dhcp6_SOURCES  = main.cc
 b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
 b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
+b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
+b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
+
+nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
+EXTRA_DIST += dhcp6_messages.mes
 
 
 if USE_CLANGPP
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Disable unused parameter warning caused by some of the
@@ -49,7 +58,7 @@ if USE_CLANGPP
 b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
 b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
 endif
 endif
 
 
-b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp6_LDADD  = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la

+ 23 - 20
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -13,28 +13,30 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
+
 #include <cassert>
 #include <cassert>
 #include <iostream>
 #include <iostream>
 
 
-#include <cc/session.h>
+#include <asiolink/asiolink.h>
 #include <cc/data.h>
 #include <cc/data.h>
-#include <exceptions/exceptions.h>
+#include <cc/session.h>
 #include <cc/session.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
-#include <util/buffer.h>
-#include <dhcp6/spec_config.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/dhcp6_log.h>
+#include <dhcp6/spec_config.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
-#include <asiolink/asiolink.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
 
 
-using namespace std;
-using namespace isc::util;
-using namespace isc::dhcp;
-using namespace isc::util;
-using namespace isc::data;
+using namespace isc::asiolink;
 using namespace isc::cc;
 using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::config;
-using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::log;
+using namespace isc::util;
+using namespace std;
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -43,7 +45,8 @@ ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
 
 
 ConstElementPtr
 ConstElementPtr
 ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
 ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
-    cout << "b10-dhcp6: Received new config:" << new_config->str() << endl;
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
+              .arg(new_config->str());
     ConstElementPtr answer = isc::config::createAnswer(0,
     ConstElementPtr answer = isc::config::createAnswer(0,
                              "Thank you for sending config.");
                              "Thank you for sending config.");
     return (answer);
     return (answer);
@@ -51,13 +54,14 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
 
 
 ConstElementPtr
 ConstElementPtr
 ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) {
 ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) {
-    cout << "b10-dhcp6: Received new command: [" << command << "], args="
-         << args->str() << endl;
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
+              .arg(command).arg(args->str());
+
     if (command == "shutdown") {
     if (command == "shutdown") {
         if (ControlledDhcpv6Srv::server_) {
         if (ControlledDhcpv6Srv::server_) {
             ControlledDhcpv6Srv::server_->shutdown();
             ControlledDhcpv6Srv::server_->shutdown();
         } else {
         } else {
-            cout << "Server not initialized yet or already shut down." << endl;
+            LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
             ConstElementPtr answer = isc::config::createAnswer(1,
             ConstElementPtr answer = isc::config::createAnswer(1,
                                      "Shutdown failure.");
                                      "Shutdown failure.");
             return (answer);
             return (answer);
@@ -93,10 +97,9 @@ void ControlledDhcpv6Srv::establishSession() {
 
 
     /// @todo: Check if session is not established already. Throw, if it is.
     /// @todo: Check if session is not established already. Throw, if it is.
     
     
-    cout << "b10-dhcp6: my specfile is " << specfile << endl;
-    
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
+              .arg(specfile);
     cc_session_ = new Session(io_service_.get_io_service());
     cc_session_ = new Session(io_service_.get_io_service());
-
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
                                           dhcp6ConfigHandler,
                                           dhcp6ConfigHandler,
                                           dhcp6CommandHandler, false);
                                           dhcp6CommandHandler, false);
@@ -106,8 +109,8 @@ void ControlledDhcpv6Srv::establishSession() {
     /// control with the "select" model of the DHCP server.  This is
     /// control with the "select" model of the DHCP server.  This is
     /// fully explained in \ref dhcpv6Session.
     /// fully explained in \ref dhcpv6Session.
     int ctrl_socket = cc_session_->getSocketDesc();
     int ctrl_socket = cc_session_->getSocketDesc();
-    cout << "b10-dhcp6: Control session started, socket="
-         << ctrl_socket << endl;
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
+              .arg(ctrl_socket);
     IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
     IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
 }
 }
 
 

+ 26 - 0
src/bin/dhcp6/dhcp6_log.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the top-level component of b10-dhcp6.
+
+#include "dhcp6_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger dhcp6_logger("dhcp6");
+
+} // namespace dhcp
+} // namespace isc
+

+ 59 - 0
src/bin/dhcp6/dhcp6_log.h

@@ -0,0 +1,59 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DHCP6_LOG__H
+#define __DHCP6_LOG__H
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <dhcp6/dhcp6_messages.h>
+
+namespace isc {
+namespace dhcp {
+
+/// \brief DHCP6 Logging
+///
+/// Defines the levels used to output debug messages in the non-library part of
+/// the b10-dhcp6 program.  Higher numbers equate to more verbose (and detailed)
+/// output.
+
+// Debug levels used to log information during startup and shutdown.
+const int DBG_DHCP6_START = DBGLVL_START_SHUT;
+const int DBG_DHCP6_SHUT = DBGLVL_START_SHUT;
+
+// Debug level used to log setting information (such as configuration changes).
+const int DBG_DHCP6_COMMAND = DBGLVL_COMMAND;
+
+// Trace basic operations within the code.
+const int DBG_DHCP6_BASIC = DBGLVL_TRACE_BASIC;
+
+// Trace detailed operations, including errors raised when processing invalid
+// packets.  (These are not logged at severities of WARN or higher for fear
+// that a set of deliberately invalid packets set to the server could overwhelm
+// the logging.)
+const int DBG_DHCP6_DETAIL = DBGLVL_TRACE_DETAIL;
+
+// This level is used to log the contents of packets received and sent.
+const int DBG_DHCP6_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+/// Define the logger for the "dhcp6" module part of b10-dhcp6.  We could define
+/// a logger in each file, but we would want to define a common name to avoid
+/// spelling mistakes, so it is just one small step from there to define a
+/// module-common logger.
+extern isc::log::Logger dhcp6_logger;
+
+} // namespace dhcp6
+} // namespace isc
+
+#endif // __DHCP6_LOG__H

+ 101 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -0,0 +1,101 @@
+# Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% DHCP6_CCSESSION_STARTED control channel session started on socket %1
+A debug message issued during startup after the IPv6 DHCP server has
+successfully established a session with the BIND 10 control channel.
+
+% DHCP6_CCSESSION_STARTING starting control channel session, specfile: %1
+This debug message is issued just before the IPv6 DHCP server attempts
+to establish a session with the BIND 10 control channel.
+
+% DHCP6_COMMAND_RECEIVED received command %1, arguments: %2
+A debug message listing the command (and possible arguments) received
+from the BIND 10 control system by the IPv6 DHCP server.
+
+% DHCP6_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the IPv6 DHCP server has received an
+updated configuration from the BIND 10 configuration system.
+
+% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
+A warning message is issued when an attempt is made to shut down the
+IPv6 DHCP server but it is not running.
+
+% DHCP6_NO_INTERFACES failed to detect any network interfaces
+During startup the IPv6 DHCP server failed to detect any network
+interfaces and is therefore shutting down.
+
+% DHCP6_OPEN_SOCKET opening sockets on port %1
+A debug message issued during startup, this indicates that the IPv6 DHCP
+server is about to open sockets on the specified port.
+
+% DHCP6_PACKET_PARSE_FAIL failed to parse incoming packet
+The IPv6 DHCP server has received a packet that it is unable to interpret.
+
+% DHCP6_PACKET_RECEIVED %1 (type %2) packet received
+A debug message noting that the server has received the specified type
+of packet.  Note that a packet marked as UNKNOWN may well be a valid
+DHCP packet, just a type not expected by the server (e.g. it will report
+a received OFFER packet as UNKNOWN).
+
+% DHCP6_PACK_FAIL failed to assemble response correctly
+This error is output if the server failed to assemble the data to be
+returned to the client into a valid packet.  The reason is most likely
+to be to a programming error: please raise a bug report.
+
+% DHCP6_QUERY_DATA received packet length %1, data length %2, data is <%3>
+A debug message listing the data received from the client or relay.
+
+% DHCP6_RESPONSE_DATA responding with packet type %1 data is <%2>
+A debug message listing the data returned to the client.
+
+% DHCP6_SERVER_FAILED server failed: %1
+The IPv6 DHCP server has encountered a fatal error and is terminating.
+The reason for the failure is included in the message.
+
+% DHCP6_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
+The server has failed to establish communication with the rest of BIND
+10 and is running in stand-alone mode.  (This behavior will change once
+the IPv6 DHCP server is properly integrated with the rest of BIND 10.)
+
+% DHCP6_SHUTDOWN server shutdown
+The IPv6 DHCP server has terminated normally.
+
+% DHCP6_SHUTDOWN_REQUEST shutdown of server requested
+This debug message indicates that a shutdown of the IPv6 server has
+been requested via a call to the 'shutdown' method of the core Dhcpv6Srv
+object.
+
+% DHCP6_SRV_CONSTRUCT_ERROR error creating Dhcpv6Srv object, reason: %1
+This error message indicates that during startup, the construction of a
+core component within the IPv6 DHCP server (the Dhcpv6 server object)
+has failed.  As a result, the server will exit.  The reason for the
+failure is given within the message.
+
+% DHCP6_STANDALONE skipping message queue, running standalone
+This is a debug message indicating that the IPv6 server is running in
+standalone mode, not connected to the message queue.  Standalone mode
+is only useful during program development, and should not be used in a
+production environment.
+
+% DHCP6_STARTING server starting
+This informational message indicates that the IPv6 DHCP server has
+processed any command-line switches and is starting.
+
+% DHCP6_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
+This is a debug message issued during the IPv6 DHCP server startup.
+It lists some information about the parameters with which the server
+is running.

+ 88 - 27
src/bin/dhcp6/dhcp6_srv.cc

@@ -14,23 +14,25 @@
 
 
 #include <stdlib.h>
 #include <stdlib.h>
 #include <time.h>
 #include <time.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp6/dhcp6_log.h>
+#include <dhcp6/dhcp6_srv.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
-#include <dhcp/pkt6.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
-#include <dhcp6/dhcp6_srv.h>
-#include <dhcp/option6_ia.h>
-#include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_addrlst.h>
-#include <asiolink/io_address.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/pkt6.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
 #include <util/range_utilities.h>
 
 
-using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
+using namespace isc::dhcp;
 using namespace isc::util;
 using namespace isc::util;
+using namespace std;
 
 
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const uint32_t HARDCODED_T1 = 1500; // in seconds
 const uint32_t HARDCODED_T1 = 1500; // in seconds
@@ -40,14 +42,14 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
-    cout << "Initialization: opening sockets on port " << port << endl;
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
 
 
-    // first call to instance() will create IfaceMgr (it's a singleton)
+    // First call to instance() will create IfaceMgr (it's a singleton)
     // it may throw something if things go wrong
     // it may throw something if things go wrong
     try {
     try {
 
 
         if (IfaceMgr::instance().countIfaces() == 0) {
         if (IfaceMgr::instance().countIfaces() == 0) {
-            cout << "Failed to detect any network interfaces. Aborting." << endl;
+            LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
             shutdown_ = true;
             shutdown_ = true;
             return;
             return;
         }
         }
@@ -59,7 +61,7 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
         /// @todo: instantiate LeaseMgr here once it is imlpemented.
         /// @todo: instantiate LeaseMgr here once it is imlpemented.
 
 
     } catch (const std::exception &e) {
     } catch (const std::exception &e) {
-        cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
+        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
         shutdown_ = true;
         shutdown_ = true;
         return;
         return;
     }
     }
@@ -68,13 +70,11 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
 }
 }
 
 
 Dhcpv6Srv::~Dhcpv6Srv() {
 Dhcpv6Srv::~Dhcpv6Srv() {
-    cout << "DHCPv6 Srv shutdown." << endl;
-
     IfaceMgr::instance().closeSockets();
     IfaceMgr::instance().closeSockets();
 }
 }
 
 
 void Dhcpv6Srv::shutdown() {
 void Dhcpv6Srv::shutdown() {
-    cout << "b10-dhcp6: DHCPv6 server shutdown." << endl;
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
     shutdown_ = true;
     shutdown_ = true;
 }
 }
 
 
@@ -89,42 +89,58 @@ bool Dhcpv6Srv::run() {
 
 
         if (query) {
         if (query) {
             if (!query->unpack()) {
             if (!query->unpack()) {
-                cout << "Failed to parse incoming packet" << endl;
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                          DHCP6_PACKET_PARSE_FAIL);
                 continue;
                 continue;
             }
             }
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
+                      .arg(serverReceivedPacketName(query->getType()))
+                      .arg(query->getType());
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
+                      .arg(query->getType())
+                      .arg(query->getBuffer().getLength())
+                      .arg(query->toText());
+
             switch (query->getType()) {
             switch (query->getType()) {
             case DHCPV6_SOLICIT:
             case DHCPV6_SOLICIT:
                 rsp = processSolicit(query);
                 rsp = processSolicit(query);
                 break;
                 break;
+
             case DHCPV6_REQUEST:
             case DHCPV6_REQUEST:
                 rsp = processRequest(query);
                 rsp = processRequest(query);
                 break;
                 break;
+
             case DHCPV6_RENEW:
             case DHCPV6_RENEW:
                 rsp = processRenew(query);
                 rsp = processRenew(query);
                 break;
                 break;
+
             case DHCPV6_REBIND:
             case DHCPV6_REBIND:
                 rsp = processRebind(query);
                 rsp = processRebind(query);
                 break;
                 break;
+
             case DHCPV6_CONFIRM:
             case DHCPV6_CONFIRM:
                 rsp = processConfirm(query);
                 rsp = processConfirm(query);
                 break;
                 break;
+
             case DHCPV6_RELEASE:
             case DHCPV6_RELEASE:
                 rsp = processRelease(query);
                 rsp = processRelease(query);
                 break;
                 break;
+
             case DHCPV6_DECLINE:
             case DHCPV6_DECLINE:
                 rsp = processDecline(query);
                 rsp = processDecline(query);
                 break;
                 break;
+
             case DHCPV6_INFORMATION_REQUEST:
             case DHCPV6_INFORMATION_REQUEST:
                 rsp = processInfRequest(query);
                 rsp = processInfRequest(query);
                 break;
                 break;
+
             default:
             default:
-                cout << "Unknown pkt type received:"
-                     << query->getType() << endl;
+                // Only action is to output a message if debug is enabled,
+                // and that will be covered by the debug statement before
+                // the "switch" statement.
+                ;
             }
             }
 
 
-            cout << "Received " << query->getBuffer().getLength() << " bytes packet type="
-                 << query->getType() << endl;
-            cout << query->toText();
             if (rsp) {
             if (rsp) {
                 rsp->setRemoteAddr(query->getRemoteAddr());
                 rsp->setRemoteAddr(query->getRemoteAddr());
                 rsp->setLocalAddr(query->getLocalAddr());
                 rsp->setLocalAddr(query->getLocalAddr());
@@ -132,14 +148,16 @@ bool Dhcpv6Srv::run() {
                 rsp->setLocalPort(DHCP6_SERVER_PORT);
                 rsp->setLocalPort(DHCP6_SERVER_PORT);
                 rsp->setIndex(query->getIndex());
                 rsp->setIndex(query->getIndex());
                 rsp->setIface(query->getIface());
                 rsp->setIface(query->getIface());
-                cout << "Replying with:" << rsp->getType() << endl;
-                cout << rsp->toText();
-                cout << "----" << endl;
-                if (!rsp->pack()) {
-                    cout << "Failed to assemble response packet." << endl;
-                    continue;
+
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
+                          DHCP6_RESPONSE_DATA)
+                          .arg(rsp->getType()).arg(rsp->toText());
+
+                if (rsp->pack()) {
+                    IfaceMgr::instance().send(rsp);
+                } else {
+                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL);
                 }
                 }
-                IfaceMgr::instance().send(rsp);
             }
             }
         }
         }
 
 
@@ -350,3 +368,46 @@ Pkt6Ptr Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, infRequest->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, infRequest->getTransid()));
     return reply;
     return reply;
 }
 }
+
+const char*
+Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
+    static const char* CONFIRM = "CONFIRM";
+    static const char* DECLINE = "DECLINE";
+    static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
+    static const char* REBIND = "REBIND";
+    static const char* RELEASE = "RELEASE";
+    static const char* RENEW = "RENEW";
+    static const char* REQUEST = "REQUEST";
+    static const char* SOLICIT = "SOLICIT";
+    static const char* UNKNOWN = "UNKNOWN";
+
+    switch (type) {
+    case DHCPV6_CONFIRM:
+        return (CONFIRM);
+
+    case DHCPV6_DECLINE:
+        return (DECLINE);
+
+    case DHCPV6_INFORMATION_REQUEST:
+        return (INFORMATION_REQUEST);
+
+    case DHCPV6_REBIND:
+        return (REBIND);
+
+    case DHCPV6_RELEASE:
+        return (RELEASE);
+
+    case DHCPV6_RENEW:
+        return (RENEW);
+
+    case DHCPV6_REQUEST:
+        return (REQUEST);
+
+    case DHCPV6_SOLICIT:
+        return (SOLICIT);
+
+    default:
+        ;
+    }
+    return (UNKNOWN);
+}

+ 18 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -69,6 +69,24 @@ public:
 
 
     /// @brief Instructs the server to shut down.
     /// @brief Instructs the server to shut down.
     void shutdown();
     void shutdown();
+
+    /// @brief Return textual type of packet received by server
+    ///
+    /// Returns the name of valid packet received by the server (e.g. SOLICIT).
+    /// If the packet is unknown - or if it is a valid DHCP packet but not one
+    /// expected to be received by the server (such as an ADVERTISE), the string
+    /// "UNKNOWN" is returned.  This method is used in debug messages.
+    ///
+    /// As the operation of the method does not depend on any server state, it
+    /// is declared static.
+    ///
+    /// @param type DHCPv4 packet type
+    ///
+    /// @return Pointer to "const" string containing the packet name.
+    ///         Note that this string is statically allocated and MUST NOT
+    ///         be freed by the caller.
+    static const char* serverReceivedPacketName(uint8_t type);
+
 protected:
 protected:
     /// @brief Processes incoming SOLICIT and returns response.
     /// @brief Processes incoming SOLICIT and returns response.
     ///
     ///

+ 27 - 31
src/bin/dhcp6/main.cc

@@ -14,13 +14,15 @@
 
 
 #include <config.h>
 #include <config.h>
 #include <iostream>
 #include <iostream>
-#include <log/dummylog.h>
-#include <log/logger_support.h>
-#include <dhcp6/ctrl_dhcp6_srv.h>
+
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 
 
-using namespace std;
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/dhcp6_log.h>
+#include <log/logger_support.h>
+
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace std;
 
 
 /// This file contains entry point (main() function) for standard DHCPv6 server
 /// This file contains entry point (main() function) for standard DHCPv6 server
 /// component for BIND10 framework. It parses command-line arguments and
 /// component for BIND10 framework. It parses command-line arguments and
@@ -37,11 +39,10 @@ const char* const DHCP6_NAME = "b10-dhcp6";
 
 
 void
 void
 usage() {
 usage() {
-    cerr << "Usage: b10-dhcp6 [-v]"
-         << endl;
-    cerr << "\t-v: verbose output" << endl;
-    cerr << "\t-s: stand-alone mode (don't connect to BIND10)" << endl;
-    cerr << "\t-p number: specify non-standard port number 1-65535 "
+    cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p number]" << endl;
+    cerr << "  -v: verbose output" << endl;
+    cerr << "  -s: stand-alone mode (don't connect to BIND10)" << endl;
+    cerr << "  -p number: specify non-standard port number 1-65535 "
          << "(useful for testing only)" << endl;
          << "(useful for testing only)" << endl;
     exit(EXIT_FAILURE);
     exit(EXIT_FAILURE);
 }
 }
@@ -52,18 +53,19 @@ main(int argc, char* argv[]) {
     int ch;
     int ch;
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
                                          // useful for testing only.
                                          // useful for testing only.
+    bool stand_alone = false;  // Should be connect to BIND10 msgq?
     bool verbose_mode = false; // Should server be verbose?
     bool verbose_mode = false; // Should server be verbose?
-    bool stand_alone = false; // should be connect to BIND10 msgq?
 
 
     while ((ch = getopt(argc, argv, "vsp:")) != -1) {
     while ((ch = getopt(argc, argv, "vsp:")) != -1) {
         switch (ch) {
         switch (ch) {
         case 'v':
         case 'v':
             verbose_mode = true;
             verbose_mode = true;
-            isc::log::denabled = true;
             break;
             break;
+
         case 's':
         case 's':
             stand_alone = true;
             stand_alone = true;
             break;
             break;
+
         case 'p':
         case 'p':
             try {
             try {
                 port_number = boost::lexical_cast<int>(optarg);
                 port_number = boost::lexical_cast<int>(optarg);
@@ -78,51 +80,45 @@ main(int argc, char* argv[]) {
                 usage();
                 usage();
             }
             }
             break;
             break;
-        case ':':
+
         default:
         default:
             usage();
             usage();
         }
         }
     }
     }
 
 
+    // Check for extraneous parameters.
+    if (argc > optind) {
+        usage();
+    }
+
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(DHCP6_NAME,
     isc::log::initLogger(DHCP6_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
                          isc::log::MAX_DEBUG_LEVEL, NULL);
                          isc::log::MAX_DEBUG_LEVEL, NULL);
-
-    cout << "b10-dhcp6: My pid=" << getpid() << ", binding to port "
-         << port_number << ", verbose " << (verbose_mode?"yes":"no")
-         << ", stand-alone=" << (stand_alone?"yes":"no") << endl;
-
-    if (argc - optind > 0) {
-        usage();
-    }
+    LOG_INFO(dhcp6_logger, DHCP6_STARTING);
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
+              .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
+              .arg(stand_alone ? "yes" : "no" );
 
 
     int ret = EXIT_SUCCESS;
     int ret = EXIT_SUCCESS;
-
     try {
     try {
-
-        cout << "b10-dhcp6: Initiating DHCPv6 server operation." << endl;
-
-        /// @todo: pass verbose to the actual server once logging is implemented
         ControlledDhcpv6Srv server(port_number);
         ControlledDhcpv6Srv server(port_number);
-
         if (!stand_alone) {
         if (!stand_alone) {
             try {
             try {
                 server.establishSession();
                 server.establishSession();
             } catch (const std::exception& ex) {
             } catch (const std::exception& ex) {
-                cerr << "Failed to establish BIND10 session. "
-                    "Running in stand-alone mode:" << ex.what() << endl;
+                LOG_ERROR(dhcp6_logger, DHCP6_SESSION_FAIL).arg(ex.what());
                 // Let's continue. It is useful to have the ability to run 
                 // Let's continue. It is useful to have the ability to run 
                 // DHCP server in stand-alone mode, e.g. for testing
                 // DHCP server in stand-alone mode, e.g. for testing
             }
             }
         } else {
         } else {
-            cout << "Skipping connection to the BIND10 msgq." << endl;
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
         }
         }
-
         server.run();
         server.run();
+        LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
 
 
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
-        cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
+        LOG_FATAL(dhcp6_logger, DHCP6_SERVER_FAILED).arg(ex.what());
         ret = EXIT_FAILURE;
         ret = EXIT_FAILURE;
     }
     }
 
 

+ 1 - 0
src/bin/dhcp6/tests/.gitignore

@@ -0,0 +1 @@
+/dhcp6_unittests

+ 6 - 3
src/bin/dhcp6/tests/Makefile.am

@@ -26,7 +26,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
-CLEANFILES = $(builddir)/interfaces.txt
+CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
@@ -42,10 +42,13 @@ if HAVE_GTEST
 
 
 TESTS += dhcp6_unittests
 TESTS += dhcp6_unittests
 
 
-dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc ../ctrl_dhcp6_srv.cc
-dhcp6_unittests_SOURCES += dhcp6_unittests.cc
+dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
+dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
+dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
+nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
 
 
 if USE_CLANGPP
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Disable unused parameter warning caused by some of the

+ 46 - 1
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -223,4 +223,49 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     // more checks to be implemented
     // more checks to be implemented
 }
 }
 
 
+TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
+    // Check all possible packet types
+    for (int itype = 0; itype < 256; ++itype) {
+        uint8_t type = itype;
+
+        switch (type) {
+        case DHCPV6_CONFIRM:
+            EXPECT_STREQ("CONFIRM", Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPV6_DECLINE:
+            EXPECT_STREQ("DECLINE", Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPV6_INFORMATION_REQUEST:
+            EXPECT_STREQ("INFORMATION_REQUEST",
+                         Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPV6_REBIND:
+            EXPECT_STREQ("REBIND", Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPV6_RELEASE:
+            EXPECT_STREQ("RELEASE", Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPV6_RENEW:
+            EXPECT_STREQ("RENEW", Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPV6_REQUEST:
+            EXPECT_STREQ("REQUEST", Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        case DHCPV6_SOLICIT:
+            EXPECT_STREQ("SOLICIT", Dhcpv6Srv::serverReceivedPacketName(type));
+            break;
+
+        default:
+            EXPECT_STREQ("UNKNOWN", Dhcpv6Srv::serverReceivedPacketName(type));
+        }
+    }
 }
 }
+
+}   // end of anonymous namespace

+ 28 - 25
src/bin/dhcp6/tests/dhcp6_test.py

@@ -27,16 +27,27 @@ import fcntl
 
 
 class TestDhcpv6Daemon(unittest.TestCase):
 class TestDhcpv6Daemon(unittest.TestCase):
     def setUp(self):
     def setUp(self):
-        # don't redirect stdout/stderr here as we want to print out things
+        # Don't redirect stdout/stderr here as we want to print out things
         # during the test
         # during the test
-        pass
+        #
+        # However, we do want to set the logging lock directory to somewhere
+        # to which we can write - use the current working directory.  We then
+        # set the appropriate environment variable.  os.putenv() may be not
+        # supported on some platforms as suggested in
+        # http://docs.python.org/release/3.2/library/os.html?highlight=putenv#os.environ:
+        # "If the platform supports the putenv() function...". It was checked
+        # that it does not work on Ubuntu. To overcome this problem we access
+        # os.environ directly.
+        lockdir_envvar = "B10_LOCKFILE_DIR_FROM_BUILD"
+        if lockdir_envvar not in os.environ:
+            os.environ[lockdir_envvar] = os.getcwd()
 
 
     def tearDown(self):
     def tearDown(self):
         pass
         pass
 
 
     def runCommand(self, params, wait=1):
     def runCommand(self, params, wait=1):
         """
         """
-        This method runs a command and returns a touple: (returncode, stdout, stderr)
+        This method runs a command and returns a tuple: (returncode, stdout, stderr)
         """
         """
         ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
         ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
 
 
@@ -79,9 +90,9 @@ class TestDhcpv6Daemon(unittest.TestCase):
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
 
 
         # There's potential problem if b10-dhcp4 prints out more
         # There's potential problem if b10-dhcp4 prints out more
-        # than 4k of text
+        # than 16k of text
         try:
         try:
-            output = os.read(self.stdout_pipes[0], 4096)
+            output = os.read(self.stdout_pipes[0], 16384)
         except OSError:
         except OSError:
             print("No data available from stdout")
             print("No data available from stdout")
             output = ""
             output = ""
@@ -91,7 +102,7 @@ class TestDhcpv6Daemon(unittest.TestCase):
             output = ""
             output = ""
 
 
         try:
         try:
-            error = os.read(self.stderr_pipes[0], 4096)
+            error = os.read(self.stderr_pipes[0], 16384)
         except OSError:
         except OSError:
             print("No data available on stderr")
             print("No data available on stderr")
             error = ""
             error = ""
@@ -130,8 +141,8 @@ class TestDhcpv6Daemon(unittest.TestCase):
         print("Note: Purpose of some of the tests is to check if DHCPv6 server can be started,")
         print("Note: Purpose of some of the tests is to check if DHCPv6 server can be started,")
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
         (returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
         (returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
-
-        self.assertEqual( str(output).count("b10-dhcp6: Initiating DHCPv6 server operation."), 1)
+        output_text = str(output) + str(error)
+        self.assertEqual(output_text.count("DHCP6_STARTING"), 1)
 
 
     def test_portnumber_0(self):
     def test_portnumber_0(self):
         print("Check that specifying port number 0 is not allowed.")
         print("Check that specifying port number 0 is not allowed.")
@@ -180,27 +191,19 @@ class TestDhcpv6Daemon(unittest.TestCase):
     def test_portnumber_nonroot(self):
     def test_portnumber_nonroot(self):
         print("Check that specifying unprivileged port number will work.")
         print("Check that specifying unprivileged port number will work.")
 
 
-        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-s', '-p', '10547'])
-
-        # When invalid port number is specified, return code must not be success
-        # TODO: Temporarily commented out as socket binding on systems that do not have
-        #       interface detection implemented currently fails.
-        # self.assertTrue(returncode == 0)
-
-        self.assertEqual( str(output).count("opening sockets on port 10547"), 1)
+        # Check that there is a message about running with an unprivileged port
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
+        output_text = str(output) + str(error)
+        self.assertEqual(output_text.count("DHCP6_OPEN_SOCKET opening sockets on port 10547"), 1)
 
 
     def test_skip_msgq(self):
     def test_skip_msgq(self):
         print("Check that connection to BIND10 msgq can be disabled.")
         print("Check that connection to BIND10 msgq can be disabled.")
 
 
-        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-s', '-p', '10547'])
-
-        # When invalid port number is specified, return code must not be success
-        # TODO: Temporarily commented out as socket binding on systems that do not have
-        #       interface detection implemented currently fails.
-        # self.assertTrue(returncode == 0)
-
-        self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
-
+        # Check that the system outputs a message on one of its streams about running
+        # standalone.
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
+        output_text = str(output) + str(error)
+        self.assertEqual(output_text.count("DHCP6_STANDALONE"), 1)
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 48 - 0
src/bin/xfrout/b10-xfrout.xml

@@ -153,6 +153,54 @@
 
 
   </refsect1>
   </refsect1>
 
 
+  <refsect1>
+    <title>STATISTICS DATA</title>
+
+    <para>
+      The statistics data collected by the <command>b10-xfrout</command>
+      daemon for <quote>Xfrout</quote> include:
+    </para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>notifyoutv4</term>
+        <listitem><simpara>
+	 Number of IPv4 notifies per zone name sent out from Xfrout
+	</simpara></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>notifyoutv6</term>
+        <listitem><simpara>
+	 Number of IPv6 notifies per zone name sent out from Xfrout
+	</simpara></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>xfrrej</term>
+        <listitem><simpara>
+         Number of XFR requests per zone name rejected by Xfrout
+        </simpara></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>xfrreqdone</term>
+        <listitem><simpara>
+	 Number of requested zone transfers per zone name completed
+        </simpara></listitem>
+      </varlistentry>
+
+    </variablelist>
+
+    <para>
+      In per-zone counters the special zone name '_SERVER_' exists. It doesn't
+      mean a specific zone. It represents an entire server and its value means
+      a total count of all zones.
+    </para>
+
+  </refsect1>
+
 <!--
 <!--
   <refsect1>
   <refsect1>
     <title>OPTIONS</title>
     <title>OPTIONS</title>

+ 146 - 1
src/bin/xfrout/tests/xfrout_test.py.in

@@ -277,13 +277,23 @@ class TestXfroutSessionBase(unittest.TestCase):
                                        # When not testing ACLs, simply accept
                                        # When not testing ACLs, simply accept
                                        isc.acl.dns.REQUEST_LOADER.load(
                                        isc.acl.dns.REQUEST_LOADER.load(
                                            [{"action": "ACCEPT"}]),
                                            [{"action": "ACCEPT"}]),
-                                       {})
+                                       {},
+                                       counter_xfrrej=self._counter_xfrrej,
+                                       counter_xfrreqdone=self._counter_xfrreqdone)
         self.set_request_type(RRType.AXFR()) # test AXFR by default
         self.set_request_type(RRType.AXFR()) # test AXFR by default
         self.mdata = self.create_request_data()
         self.mdata = self.create_request_data()
         self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
         self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
         # some test replaces a module-wide function.  We should ensure the
         # some test replaces a module-wide function.  We should ensure the
         # original is used elsewhere.
         # original is used elsewhere.
         self.orig_get_rrset_len = xfrout.get_rrset_len
         self.orig_get_rrset_len = xfrout.get_rrset_len
+        self._zone_name_xfrrej = None
+        self._zone_name_xfrreqdone = None
+
+    def _counter_xfrrej(self, zone_name):
+        self._zone_name_xfrrej = zone_name
+
+    def _counter_xfrreqdone(self, zone_name):
+        self._zone_name_xfrreqdone = zone_name
 
 
     def tearDown(self):
     def tearDown(self):
         xfrout.get_rrset_len = self.orig_get_rrset_len
         xfrout.get_rrset_len = self.orig_get_rrset_len
@@ -458,7 +468,28 @@ class TestXfroutSession(TestXfroutSessionBase):
         # ACL checks only with the default ACL
         # ACL checks only with the default ACL
         def acl_setter(acl):
         def acl_setter(acl):
             self.xfrsess._acl = acl
             self.xfrsess._acl = acl
+        self.assertIsNone(self._zone_name_xfrrej)
+        self.check_transfer_acl(acl_setter)
+        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
+
+    def test_transfer_acl_with_nonetype_xfrrej(self):
+        # ACL checks only with the default ACL and NoneType xfrrej
+        # counter
+        def acl_setter(acl):
+            self.xfrsess._acl = acl
+        self.xfrsess._counter_xfrrej = None
+        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
         self.check_transfer_acl(acl_setter)
+        self.assertIsNone(self._zone_name_xfrrej)
+
+    def test_transfer_acl_with_notcallable_xfrrej(self):
+        # ACL checks only with the default ACL and not callable xfrrej
+        # counter
+        def acl_setter(acl):
+            self.xfrsess._acl = acl
+        self.xfrsess._counter_xfrrej = 'NOT CALLABLE'
+        self.assertRaises(TypeError,
+                          self.check_transfer_acl, acl_setter)
 
 
     def test_transfer_zoneacl(self):
     def test_transfer_zoneacl(self):
         # ACL check with a per zone ACL + default ACL.  The per zone ACL
         # ACL check with a per zone ACL + default ACL.  The per zone ACL
@@ -469,7 +500,9 @@ class TestXfroutSession(TestXfroutSessionBase):
             self.xfrsess._zone_config[zone_key]['transfer_acl'] = acl
             self.xfrsess._zone_config[zone_key]['transfer_acl'] = acl
             self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
             self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
                     {"from": "127.0.0.1", "action": "DROP"}])
                     {"from": "127.0.0.1", "action": "DROP"}])
+        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
         self.check_transfer_acl(acl_setter)
+        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
 
 
     def test_transfer_zoneacl_nomatch(self):
     def test_transfer_zoneacl_nomatch(self):
         # similar to the previous one, but the per zone doesn't match the
         # similar to the previous one, but the per zone doesn't match the
@@ -481,7 +514,9 @@ class TestXfroutSession(TestXfroutSessionBase):
                 isc.acl.dns.REQUEST_LOADER.load([
                 isc.acl.dns.REQUEST_LOADER.load([
                     {"from": "127.0.0.1", "action": "DROP"}])
                     {"from": "127.0.0.1", "action": "DROP"}])
             self.xfrsess._acl = acl
             self.xfrsess._acl = acl
+        self.assertIsNone(self._zone_name_xfrrej)
         self.check_transfer_acl(acl_setter)
         self.check_transfer_acl(acl_setter)
+        self.assertEqual(self._zone_name_xfrrej, TEST_ZONE_NAME_STR)
 
 
     def test_get_transfer_acl(self):
     def test_get_transfer_acl(self):
         # set the default ACL.  If there's no specific zone ACL, this one
         # set the default ACL.  If there's no specific zone ACL, this one
@@ -831,9 +866,39 @@ class TestXfroutSession(TestXfroutSessionBase):
         def myreply(msg, sock):
         def myreply(msg, sock):
             self.sock.send(b"success")
             self.sock.send(b"success")
 
 
+        self.assertIsNone(self._zone_name_xfrreqdone)
         self.xfrsess._reply_xfrout_query = myreply
         self.xfrsess._reply_xfrout_query = myreply
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         self.assertEqual(self.sock.readsent(), b"success")
         self.assertEqual(self.sock.readsent(), b"success")
+        self.assertEqual(self._zone_name_xfrreqdone, TEST_ZONE_NAME_STR)
+
+    def test_dns_xfrout_start_with_nonetype_xfrreqdone(self):
+        def noerror(msg, name, rrclass):
+            return Rcode.NOERROR()
+        self.xfrsess._xfrout_setup = noerror
+
+        def myreply(msg, sock):
+            self.sock.send(b"success")
+
+        self.assertIsNone(self._zone_name_xfrreqdone)
+        self.xfrsess._reply_xfrout_query = myreply
+        self.xfrsess._counter_xfrreqdone = None
+        self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
+        self.assertIsNone(self._zone_name_xfrreqdone)
+
+    def test_dns_xfrout_start_with_notcallable_xfrreqdone(self):
+        def noerror(msg, name, rrclass):
+            return Rcode.NOERROR()
+        self.xfrsess._xfrout_setup = noerror
+
+        def myreply(msg, sock):
+            self.sock.send(b"success")
+
+        self.xfrsess._reply_xfrout_query = myreply
+        self.xfrsess._counter_xfrreqdone = 'NOT CALLABLE'
+        self.assertRaises(TypeError,
+                          self.xfrsess.dns_xfrout_start, self.sock,
+                          self.mdata)
 
 
     def test_reply_xfrout_query_axfr(self):
     def test_reply_xfrout_query_axfr(self):
         self.xfrsess._soa = self.soa_rrset
         self.xfrsess._soa = self.soa_rrset
@@ -1153,6 +1218,7 @@ class MyUnixSockServer(UnixSockServer):
         self._common_init()
         self._common_init()
         self._cc = MyCCSession()
         self._cc = MyCCSession()
         self.update_config_data(self._cc.get_full_config())
         self.update_config_data(self._cc.get_full_config())
+        self._counters = {}
 
 
 class TestUnixSockServer(unittest.TestCase):
 class TestUnixSockServer(unittest.TestCase):
     def setUp(self):
     def setUp(self):
@@ -1504,6 +1570,80 @@ class MyXfroutServer(XfroutServer):
         self._unix_socket_server = None
         self._unix_socket_server = None
         # Disable the wait for threads
         # Disable the wait for threads
         self._wait_for_threads = lambda : None
         self._wait_for_threads = lambda : None
+        self._cc.get_module_spec = lambda:\
+            isc.config.module_spec_from_file(xfrout.SPECFILE_LOCATION)
+        # setup an XfroutCount object
+        self._counter = XfroutCounter(
+            self._cc.get_module_spec().get_statistics_spec())
+
+class TestXfroutCounter(unittest.TestCase):
+    def setUp(self):
+        statistics_spec = \
+            isc.config.module_spec_from_file(\
+            xfrout.SPECFILE_LOCATION).get_statistics_spec()
+        self.xfrout_counter = XfroutCounter(statistics_spec)
+        self._counters = isc.config.spec_name_list(\
+            isc.config.find_spec_part(\
+                statistics_spec, XfroutCounter.perzone_prefix)\
+                ['named_set_item_spec']['map_item_spec'])
+        self._started = threading.Event()
+        self._number = 3 # number of the threads
+        self._cycle = 10000 # number of counting per thread
+
+    def test_get_default_statistics_data(self):
+        self.assertEqual(self.xfrout_counter._get_default_statistics_data(),
+                         {XfroutCounter.perzone_prefix: {
+                            XfroutCounter.entire_server: \
+                              dict([(cnt, 0) for cnt in self._counters])
+                         }})
+
+    def setup_incrementer(self, incrementer):
+        self._started.wait()
+        for i in range(self._cycle): incrementer(TEST_ZONE_NAME_STR)
+
+    def start_incrementer(self, incrementer):
+        threads = []
+        for i in range(self._number):
+            threads.append(threading.Thread(\
+                    target=self.setup_incrementer,\
+                        args=(incrementer,)\
+                        ))
+        for th in threads: th.start()
+        self._started.set()
+        for th in threads: th.join()
+
+    def get_count(self, zone_name, counter_name):
+        return isc.cc.data.find(\
+            self.xfrout_counter.get_statistics(),\
+                '%s/%s/%s' % (XfroutCounter.perzone_prefix,\
+                                  zone_name, counter_name))
+
+    def test_incrementers(self):
+        result = { XfroutCounter.entire_server: {},
+                   TEST_ZONE_NAME_STR: {} }
+        for counter_name in self._counters:
+                incrementer = getattr(self.xfrout_counter, 'inc_%s' % counter_name)
+                self.start_incrementer(incrementer)
+                self.assertEqual(self.get_count(\
+                            TEST_ZONE_NAME_STR, counter_name), \
+                                     self._number * self._cycle)
+                self.assertEqual(self.get_count(\
+                        XfroutCounter.entire_server, counter_name), \
+                                     self._number * self._cycle)
+                result[XfroutCounter.entire_server][counter_name] = \
+                    result[TEST_ZONE_NAME_STR][counter_name] = \
+                    self._number * self._cycle
+        self.assertEqual(
+            self.xfrout_counter.get_statistics(),
+            {XfroutCounter.perzone_prefix: result})
+
+    def test_add_perzone_counter(self):
+        for counter_name in self._counters:
+            self.assertRaises(isc.cc.data.DataNotFoundError,\
+                                  self.get_count, TEST_ZONE_NAME_STR, counter_name)
+        self.xfrout_counter._add_perzone_counter(TEST_ZONE_NAME_STR)
+        for counter_name in self._counters:
+            self.assertEqual(self.get_count(TEST_ZONE_NAME_STR, counter_name), 0)
 
 
 class TestXfroutServer(unittest.TestCase):
 class TestXfroutServer(unittest.TestCase):
     def setUp(self):
     def setUp(self):
@@ -1514,6 +1654,11 @@ class TestXfroutServer(unittest.TestCase):
         self.assertTrue(self.xfrout_server._notifier.shutdown_called)
         self.assertTrue(self.xfrout_server._notifier.shutdown_called)
         self.assertTrue(self.xfrout_server._cc.stopped)
         self.assertTrue(self.xfrout_server._cc.stopped)
 
 
+    def test_getstats(self):
+        self.assertEqual(
+            self.xfrout_server.command_handler('getstats', None), \
+                create_answer(0,  {}))
+
 if __name__== "__main__":
 if __name__== "__main__":
     isc.log.resetUnitTestRootLogger()
     isc.log.resetUnitTestRootLogger()
     unittest.main()
     unittest.main()

+ 143 - 9
src/bin/xfrout/xfrout.py.in

@@ -153,7 +153,8 @@ def get_soa_serial(soa_rdata):
 
 
 class XfroutSession():
 class XfroutSession():
     def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
     def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
-                 default_acl, zone_config, client_class=DataSourceClient):
+                 default_acl, zone_config, client_class=DataSourceClient,
+                 counter_xfrrej=None, counter_xfrreqdone=None):
         self._sock_fd = sock_fd
         self._sock_fd = sock_fd
         self._request_data = request_data
         self._request_data = request_data
         self._server = server
         self._server = server
@@ -168,6 +169,10 @@ class XfroutSession():
         self.ClientClass = client_class # parameterize this for testing
         self.ClientClass = client_class # parameterize this for testing
         self._soa = None # will be set in _xfrout_setup or in tests
         self._soa = None # will be set in _xfrout_setup or in tests
         self._jnl_reader = None # will be set to a reader for IXFR
         self._jnl_reader = None # will be set to a reader for IXFR
+        # Set counter handlers for counting Xfr requests. An argument
+        # is required for zone name.
+        self._counter_xfrrej = counter_xfrrej
+        self._counter_xfrreqdone = counter_xfrreqdone
         self._handle()
         self._handle()
 
 
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -270,6 +275,9 @@ class XfroutSession():
                          format_zone_str(zone_name, zone_class))
                          format_zone_str(zone_name, zone_class))
             return None, None
             return None, None
         elif acl_result == REJECT:
         elif acl_result == REJECT:
+            if self._counter_xfrrej is not None:
+                # count rejected Xfr request by each zone name
+                self._counter_xfrrej(zone_name.to_text())
             logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
             logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
                          self._request_type, format_addrinfo(self._remote),
                          self._request_type, format_addrinfo(self._remote),
                          format_zone_str(zone_name, zone_class))
                          format_zone_str(zone_name, zone_class))
@@ -525,6 +533,9 @@ class XfroutSession():
         except Exception as err:
         except Exception as err:
             logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
             logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
                     format_addrinfo(self._remote), zone_str, err)
                     format_addrinfo(self._remote), zone_str, err)
+        if self._counter_xfrreqdone is not None:
+            # count done Xfr requests by each zone name
+            self._counter_xfrreqdone(zone_name.to_text())
         logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
         logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
                     format_addrinfo(self._remote), zone_str)
                     format_addrinfo(self._remote), zone_str)
 
 
@@ -634,7 +645,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
     '''The unix domain socket server which accept xfr query sent from auth server.'''
     '''The unix domain socket server which accept xfr query sent from auth server.'''
 
 
     def __init__(self, sock_file, handle_class, shutdown_event, config_data,
     def __init__(self, sock_file, handle_class, shutdown_event, config_data,
-                 cc):
+                 cc, **counters):
         self._remove_unused_sock_file(sock_file)
         self._remove_unused_sock_file(sock_file)
         self._sock_file = sock_file
         self._sock_file = sock_file
         socketserver_mixin.NoPollMixIn.__init__(self)
         socketserver_mixin.NoPollMixIn.__init__(self)
@@ -644,6 +655,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._common_init()
         self._common_init()
         self._cc = cc
         self._cc = cc
         self.update_config_data(config_data)
         self.update_config_data(config_data)
+        # handlers for statistics use
+        self._counters = counters
 
 
     def _common_init(self):
     def _common_init(self):
         '''Initialization shared with the mock server class used for tests'''
         '''Initialization shared with the mock server class used for tests'''
@@ -798,7 +811,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._lock.release()
         self._lock.release()
         self.RequestHandlerClass(sock_fd, request_data, self,
         self.RequestHandlerClass(sock_fd, request_data, self,
                                  isc.server_common.tsig_keyring.get_keyring(),
                                  isc.server_common.tsig_keyring.get_keyring(),
-                                 self._guess_remote(sock_fd), acl, zone_config)
+                                 self._guess_remote(sock_fd), acl, zone_config,
+                                 **self._counters)
 
 
     def _remove_unused_sock_file(self, sock_file):
     def _remove_unused_sock_file(self, sock_file):
         '''Try to remove the socket file. If the file is being used
         '''Try to remove the socket file. If the file is being used
@@ -926,6 +940,107 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._transfers_counter -= 1
         self._transfers_counter -= 1
         self._lock.release()
         self._lock.release()
 
 
+class XfroutCounter:
+    """A class for handling all statistics counters of Xfrout.  In
+    this class, the structure of per-zone counters is assumed to be
+    like this:
+        zones/example.com./notifyoutv4
+        zones/example.com./notifyoutv6
+        zones/example.com./xfrrej
+        zones/example.com./xfrreqdone
+    """
+    # '_SERVER_' is a special zone name representing an entire
+    # count. It doesn't mean a specific zone, but it means an
+    # entire count in the server.
+    entire_server = '_SERVER_'
+    # zone names are contained under this dirname in the spec file.
+    perzone_prefix = 'zones'
+    def __init__(self, statistics_spec):
+        self._statistics_spec = statistics_spec
+        # holding statistics data for Xfrout module
+        self._statistics_data = {}
+        self._lock = threading.RLock()
+        self._create_perzone_incrementers()
+
+    def get_statistics(self):
+        """Calculates an entire server counts, and returns statistics
+        data format to send out the stats module including each
+        counter. If there is no counts, then it returns an empty
+        dictionary. Locks the thread because it is considered to be
+        invoked by a multi-threading caller."""
+        # If self._statistics_data contains nothing of zone name, it
+        # returns an empty dict.
+        if len(self._statistics_data) == 0: return {}
+        zones = {}
+        with self._lock:
+            zones = self._statistics_data[self.perzone_prefix].copy()
+        # Start calculation for '_SERVER_' counts
+        attrs = self._get_default_statistics_data()[self.perzone_prefix][self.entire_server]
+        statistics_data = {self.perzone_prefix: {}}
+        for attr in attrs:
+            sum_ = 0
+            for name in zones:
+                if name == self.entire_server: continue
+                if attr in zones[name]:
+                    if  name not in statistics_data[self.perzone_prefix]:
+                        statistics_data[self.perzone_prefix][name] = {}
+                    statistics_data[self.perzone_prefix][name].update(
+                        {attr: zones[name][attr]}
+                        )
+                    sum_ += zones[name][attr]
+            if  sum_ > 0:
+                if self.entire_server not in statistics_data[self.perzone_prefix]:
+                    statistics_data[self.perzone_prefix][self.entire_server] = {}
+                statistics_data[self.perzone_prefix][self.entire_server].update({attr: sum_})
+        return statistics_data
+
+    def _get_default_statistics_data(self):
+        """Returns default statistics data from the spec file"""
+        statistics_data = {}
+        for id_ in isc.config.spec_name_list(self._statistics_spec):
+            spec = isc.config.find_spec_part(self._statistics_spec, id_)
+            statistics_data.update({id_: spec['item_default']})
+        return statistics_data
+
+    def _create_perzone_incrementers(self):
+        """Creates increment method of each per-zone counter based on
+        the spec file. Incrementer can be accessed by name
+        "inc_${item_name}".Incrementers are passed to the
+        XfroutSession and NotifyOut class as counter handlers."""
+        # add a new element under the named_set item for the zone
+        zones_spec = isc.config.find_spec_part(
+            self._statistics_spec, self.perzone_prefix)
+        item_list =  isc.config.spec_name_list(\
+            zones_spec['named_set_item_spec']['map_item_spec'])
+        # can be accessed by the name 'inc_xxx'
+        for item in item_list:
+            def __perzone_incrementer(zone_name, counter_name=item, step=1):
+                """A per-zone incrementer for counter_name. Locks the thread
+                because it is considered to be invoked by a multi-threading
+                caller."""
+                with self._lock:
+                    self._add_perzone_counter(zone_name)
+                    self._statistics_data[self.perzone_prefix][zone_name][counter_name] += step
+            setattr(self, 'inc_%s' % item, __perzone_incrementer)
+
+
+    def _add_perzone_counter(self, zone):
+        """Adds named_set-type counter for each zone name"""
+        try:
+            self._statistics_data[self.perzone_prefix][zone]
+        except KeyError:
+            # add a new element under the named_set item for the zone
+            map_spec = isc.config.find_spec_part(
+                self._statistics_spec, '%s/%s' % \
+                    (self.perzone_prefix, zone))['map_item_spec']
+            id_list =  isc.config.spec_name_list(map_spec)
+            for id_ in id_list:
+                spec = isc.config.find_spec_part(map_spec, id_)
+                isc.cc.data.set(self._statistics_data,
+                                '%s/%s/%s' % \
+                                    (self.perzone_prefix, zone, id_),
+                                spec['item_default'])
+
 class XfroutServer:
 class XfroutServer:
     def __init__(self):
     def __init__(self):
         self._unix_socket_server = None
         self._unix_socket_server = None
@@ -933,6 +1048,8 @@ class XfroutServer:
         self._shutdown_event = threading.Event()
         self._shutdown_event = threading.Event()
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._config_data = self._cc.get_full_config()
         self._config_data = self._cc.get_full_config()
+        self._counter = XfroutCounter(
+            self._cc.get_module_spec().get_statistics_spec())
         self._cc.start()
         self._cc.start()
         self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
         self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
         isc.server_common.tsig_keyring.init_keyring(self._cc)
         isc.server_common.tsig_keyring.init_keyring(self._cc)
@@ -941,17 +1058,25 @@ class XfroutServer:
 
 
     def _start_xfr_query_listener(self):
     def _start_xfr_query_listener(self):
         '''Start a new thread to accept xfr query. '''
         '''Start a new thread to accept xfr query. '''
-        self._unix_socket_server = UnixSockServer(self._listen_sock_file,
-                                                  XfroutSession,
-                                                  self._shutdown_event,
-                                                  self._config_data,
-                                                  self._cc)
+        self._unix_socket_server = UnixSockServer(
+            self._listen_sock_file,
+            XfroutSession,
+            self._shutdown_event,
+            self._config_data,
+            self._cc,
+            counter_xfrrej=self._counter.inc_xfrrej,
+            counter_xfrreqdone=self._counter.inc_xfrreqdone
+            )
         listener = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener = threading.Thread(target=self._unix_socket_server.serve_forever)
         listener.start()
         listener.start()
 
 
     def _start_notifier(self):
     def _start_notifier(self):
         datasrc = self._unix_socket_server.get_db_file()
         datasrc = self._unix_socket_server.get_db_file()
-        self._notifier = notify_out.NotifyOut(datasrc)
+        self._notifier = notify_out.NotifyOut(
+            datasrc,
+            counter_notifyoutv4=self._counter.inc_notifyoutv4,
+            counter_notifyoutv6=self._counter.inc_notifyoutv6
+            )
         if 'also_notify' in self._config_data:
         if 'also_notify' in self._config_data:
             for slave in self._config_data['also_notify']:
             for slave in self._config_data['also_notify']:
                 self._notifier.add_slave(slave['address'], slave['port'])
                 self._notifier.add_slave(slave['address'], slave['port'])
@@ -1027,6 +1152,15 @@ class XfroutServer:
             else:
             else:
                 answer = create_answer(1, "Bad command parameter:" + str(args))
                 answer = create_answer(1, "Bad command parameter:" + str(args))
 
 
+        # return statistics data to the stats daemon
+        elif cmd == "getstats":
+            # The log level is here set to debug in order to avoid
+            # that a log becomes too verbose. Because the b10-stats
+            # daemon is periodically asking to the b10-xfrout daemon.
+            logger.debug(DBG_XFROUT_TRACE, \
+                             XFROUT_RECEIVED_GETSTATS_COMMAND)
+            answer = create_answer(0, self._counter.get_statistics())
+
         else:
         else:
             answer = create_answer(1, "Unknown command:" + str(cmd))
             answer = create_answer(1, "Unknown command:" + str(cmd))
 
 

+ 59 - 0
src/bin/xfrout/xfrout.spec.pre.in

@@ -114,6 +114,65 @@
             "item_default": "IN"
             "item_default": "IN"
           } ]
           } ]
         }
         }
+      ],
+      "statistics": [
+        {
+          "item_name": "zones",
+          "item_type": "named_set",
+          "item_optional": false,
+          "item_default": {
+            "_SERVER_" : {
+              "notifyoutv4" : 0,
+              "notifyoutv6" : 0,
+              "xfrrej" : 0,
+              "xfrreqdone" : 0
+            }
+          },
+          "item_title": "Zone names",
+          "item_description": "Zone names for Xfrout statistics",
+          "named_set_item_spec": {
+            "item_name": "zonename",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {},
+            "item_title": "Zone name",
+            "item_description": "Zone name for Xfrout statistics",
+            "map_item_spec": [
+              {
+                "item_name": "notifyoutv4",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "IPv4 notifies",
+                "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+              },
+              {
+                "item_name": "notifyoutv6",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "IPv6 notifies",
+                "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+              },
+              {
+                "item_name": "xfrrej",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "XFR rejected requests",
+                "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+              },
+              {
+                "item_name": "xfrreqdone",
+                "item_type": "integer",
+                "item_optional": false,
+                "item_default": 0,
+                "item_title": "Requested zone transfers",
+                "item_description": "Number of requested zone transfers completed per zone name"
+              }
+            ]
+          }
+        }
       ]
       ]
   }
   }
 }
 }

+ 4 - 0
src/bin/xfrout/xfrout_messages.mes

@@ -107,6 +107,10 @@ received from the configuration manager.
 The xfrout daemon received a command on the command channel that
 The xfrout daemon received a command on the command channel that
 NOTIFY packets should be sent for the given zone.
 NOTIFY packets should be sent for the given zone.
 
 
+% XFROUT_RECEIVED_GETSTATS_COMMAND received command to get statistics data
+The xfrout daemon received a command on the command channel that
+statistics data should be sent to the stats daemon.
+
 % XFROUT_PARSE_QUERY_ERROR error parsing query: %1
 % XFROUT_PARSE_QUERY_ERROR error parsing query: %1
 There was a parse error while reading an incoming query. The parse
 There was a parse error while reading an incoming query. The parse
 error is shown in the log message. A remote client sent a packet we
 error is shown in the log message. A remote client sent a packet we

+ 10 - 13
src/lib/asiolink/io_address.h

@@ -150,18 +150,15 @@ public:
     /// Operations within one protocol family are obvious.
     /// Operations within one protocol family are obvious.
     /// Comparisons between v4 and v6 will allways return v4
     /// Comparisons between v4 and v6 will allways return v4
     /// being smaller. This follows boost::asio::ip implementation
     /// being smaller. This follows boost::asio::ip implementation
-    bool smallerThan(const IOAddress& other) const {
-        if (this->getFamily() < other.getFamily()) {
-            return (true);
-        }
-        if (this->getFamily() > other.getFamily()) {
-            return (false);
-        }
-        if (this->getFamily() == AF_INET6) {
-            return (this->asio_address_.to_v6() < other.asio_address_.to_v6());
-        } else {
-            return (this->asio_address_.to_v4() < other.asio_address_.to_v4());
+    bool lessThan(const IOAddress& other) const {
+        if (this->getFamily() == other.getFamily()) {
+            if (this->getFamily() == AF_INET6) {
+                return (this->asio_address_.to_v6() < other.asio_address_.to_v6());
+            } else {
+                return (this->asio_address_.to_v4() < other.asio_address_.to_v4());
+            }
         }
         }
+        return (this->getFamily() < other.getFamily());
     }
     }
 
 
     /// \brief Checks if one address is smaller or equal than the other
     /// \brief Checks if one address is smaller or equal than the other
@@ -173,7 +170,7 @@ public:
         if (equals(other)) {
         if (equals(other)) {
             return (true);
             return (true);
         }
         }
-        return (smallerThan(other));
+        return (lessThan(other));
     }
     }
 
 
     /// \brief Checks if one address is smaller than the other
     /// \brief Checks if one address is smaller than the other
@@ -182,7 +179,7 @@ public:
     ///
     ///
     /// See \ref smaller_than method for details.
     /// See \ref smaller_than method for details.
     bool operator<(const IOAddress& other) const {
     bool operator<(const IOAddress& other) const {
-        return (smallerThan(other));
+        return (lessThan(other));
     }
     }
 
 
     /// \brief Checks if one address is smaller or equal than the other
     /// \brief Checks if one address is smaller or equal than the other

+ 1 - 1
src/lib/asiolink/tests/io_address_unittest.cc

@@ -100,7 +100,7 @@ TEST(IOAddressTest, uint32) {
     EXPECT_EQ(addr3.toText(), "192.0.2.5");
     EXPECT_EQ(addr3.toText(), "192.0.2.5");
 }
 }
 
 
-TEST(IOAddressTest, compare) {
+TEST(IOAddressTest, lessThanEqual) {
     IOAddress addr1("192.0.2.5");
     IOAddress addr1("192.0.2.5");
     IOAddress addr2("192.0.2.6");
     IOAddress addr2("192.0.2.6");
     IOAddress addr3("0.0.0.0");
     IOAddress addr3("0.0.0.0");

+ 4 - 4
src/lib/config/ccsession.cc

@@ -456,7 +456,7 @@ ModuleCCSession::ModuleCCSession(
 
 
     ConstElementPtr answer, env;
     ConstElementPtr answer, env;
     session_.group_recvmsg(env, answer, false, seq);
     session_.group_recvmsg(env, answer, false, seq);
-    int rcode;
+    int rcode = -1;
     ConstElementPtr err = parseAnswer(rcode, answer);
     ConstElementPtr err = parseAnswer(rcode, answer);
     if (rcode != 0) {
     if (rcode != 0) {
         LOG_ERROR(config_logger, CONFIG_MOD_SPEC_REJECT).arg(answer->str());
         LOG_ERROR(config_logger, CONFIG_MOD_SPEC_REJECT).arg(answer->str());
@@ -535,7 +535,7 @@ ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
         ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
         ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
         // handle config update
         // handle config update
         answer = config_handler_(diff);
         answer = config_handler_(diff);
-        int rcode;
+        int rcode = -1;
         parseAnswer(rcode, answer);
         parseAnswer(rcode, answer);
         if (rcode == 0) {
         if (rcode == 0) {
             ElementPtr local_config = getLocalConfig();
             ElementPtr local_config = getLocalConfig();
@@ -652,7 +652,7 @@ ModuleCCSession::fetchRemoteSpec(const std::string& module, bool is_filename) {
         unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
         unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
         ConstElementPtr env, answer;
         ConstElementPtr env, answer;
         session_.group_recvmsg(env, answer, false, seq);
         session_.group_recvmsg(env, answer, false, seq);
-        int rcode;
+        int rcode = -1;
         ConstElementPtr spec_data = parseAnswer(rcode, answer);
         ConstElementPtr spec_data = parseAnswer(rcode, answer);
         if (rcode == 0 && spec_data) {
         if (rcode == 0 && spec_data) {
             // received OK, construct the spec out of it
             // received OK, construct the spec out of it
@@ -689,7 +689,7 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_name,
 
 
     ConstElementPtr env, answer;
     ConstElementPtr env, answer;
     session_.group_recvmsg(env, answer, false, seq);
     session_.group_recvmsg(env, answer, false, seq);
-    int rcode;
+    int rcode = -1;
     ConstElementPtr new_config = parseAnswer(rcode, answer);
     ConstElementPtr new_config = parseAnswer(rcode, answer);
     ElementPtr local_config;
     ElementPtr local_config;
     if (rcode == 0 && new_config) {
     if (rcode == 0 && new_config) {

+ 1 - 2
src/lib/datasrc/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = memory . tests
+SUBDIRS = . memory tests
 
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
@@ -64,7 +64,6 @@ libb10_datasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libb10-cc.la
-libb10_datasrc_la_LIBADD += memory/libdatasrc_memory.la # convenience library
 libb10_datasrc_la_LIBADD += $(SQLITE_LIBS)
 libb10_datasrc_la_LIBADD += $(SQLITE_LIBS)
 
 
 BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc
 BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc

+ 2 - 0
src/lib/datasrc/memory/.gitignore

@@ -0,0 +1,2 @@
+/memory_messages.cc
+/memory_messages.h

+ 13 - 1
src/lib/datasrc/memory/Makefile.am

@@ -6,7 +6,7 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
-CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
+CLEANFILES = *.gcno *.gcda
 
 
 noinst_LTLIBRARIES = libdatasrc_memory.la
 noinst_LTLIBRARIES = libdatasrc_memory.la
 
 
@@ -16,5 +16,17 @@ libdatasrc_memory_la_SOURCES += treenode_rrset.h treenode_rrset.cc
 libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
 libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
 libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
 libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
 libdatasrc_memory_la_SOURCES += segment_object_holder.h
 libdatasrc_memory_la_SOURCES += segment_object_holder.h
+libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
+libdatasrc_memory_la_SOURCES += logger.h logger.cc
 libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
 libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
+libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
+nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
+
 EXTRA_DIST  = rdata_serialization_priv.cc
 EXTRA_DIST  = rdata_serialization_priv.cc
+
+BUILT_SOURCES = memory_messages.h memory_messages.cc
+memory_messages.h memory_messages.cc: Makefile memory_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/memory/memory_messages.mes
+
+EXTRA_DIST += memory_messages.mes
+CLEANFILES += memory_messages.h memory_messages.cc

+ 1 - 0
src/lib/datasrc/memory/benchmarks/.gitignore

@@ -1 +1,2 @@
 /rdata_reader_bench
 /rdata_reader_bench
+/rrset_render_bench

+ 19 - 6
src/lib/datasrc/memory/domaintree.h

@@ -1153,9 +1153,11 @@ public:
     /// Another special feature of this version is the ability to record
     /// Another special feature of this version is the ability to record
     /// more detailed information regarding the search result.
     /// more detailed information regarding the search result.
     ///
     ///
-    /// This information will be returned via the \c node_path parameter,
-    /// which is an object of class \c DomainTreeNodeChain.
-    /// The passed parameter must be empty.
+    /// This information will be returned via the \c node_path
+    /// parameter, which is an object of class \c DomainTreeNodeChain.
+    /// The passed parameter must be empty if the label sequence is
+    /// absolute. If the label sequence is not absolute, then find()
+    /// will begin from the top of the node chain.
     ///
     ///
     /// On success, the node sequence stored in \c node_path will contain all
     /// On success, the node sequence stored in \c node_path will contain all
     /// the ancestor nodes from the found node towards the root.
     /// the ancestor nodes from the found node towards the root.
@@ -1462,12 +1464,23 @@ DomainTree<T>::find(const isc::dns::LabelSequence& target_labels_orig,
                     bool (*callback)(const DomainTreeNode<T>&, CBARG),
                     bool (*callback)(const DomainTreeNode<T>&, CBARG),
                     CBARG callback_arg) const
                     CBARG callback_arg) const
 {
 {
-    if (!node_path.isEmpty()) {
+    if (node_path.isEmpty() ^ target_labels_orig.isAbsolute()) {
         isc_throw(isc::BadValue,
         isc_throw(isc::BadValue,
-                  "DomainTree::find is given a non empty chain");
+                  "DomainTree::find() is given mismatched node chain"
+                  " and label sequence");
+    }
+
+    DomainTreeNode<T>* node;
+
+    if (!node_path.isEmpty()) {
+        // Get the top node in the node chain
+        node = const_cast<DomainTreeNode<T>*>(node_path.top());
+        // Start searching from its down pointer
+        node = node->getDown();
+    } else {
+        node = root_.get();
     }
     }
 
 
-    DomainTreeNode<T>* node = root_.get();
     Result ret = NOTFOUND;
     Result ret = NOTFOUND;
     dns::LabelSequence target_labels(target_labels_orig);
     dns::LabelSequence target_labels(target_labels_orig);
 
 

+ 25 - 0
src/lib/datasrc/memory/logger.cc

@@ -0,0 +1,25 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/memory/logger.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+isc::log::Logger logger("datasrc_memory");
+
+}
+}
+}

+ 52 - 0
src/lib/datasrc/memory/logger.h

@@ -0,0 +1,52 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_LOGGER_H
+#define DATASRC_MEMORY_LOGGER_H
+
+#include <log/macros.h>
+#include <datasrc/memory/memory_messages.h>
+
+/// \file datasrc/memory/logger.h
+/// \brief Data Source memory library global logger
+///
+/// This holds the logger for the data source memory library. It is a
+/// private header and should not be included in any publicly used
+/// header, only in local cc files.
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+/// \brief The logger for this library
+extern isc::log::Logger logger;
+
+/// \brief Trace basic operations
+const int DBG_TRACE_BASIC = DBGLVL_TRACE_BASIC;
+
+/// \brief Trace data changes and lookups as well
+const int DBG_TRACE_DATA = DBGLVL_TRACE_BASIC_DATA;
+
+/// \brief Detailed even about how the lookups happen
+const int DBG_TRACE_DETAILED = DBGLVL_TRACE_DETAIL;
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_LOGGER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 904 - 0
src/lib/datasrc/memory/memory_client.cc

@@ -0,0 +1,904 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <datasrc/memory/memory_client.h>
+#include <datasrc/memory/logger.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/rdata_serialization.h>
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/domaintree.h>
+#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/memory/treenode_rrset.h>
+
+#include <util/memory_segment_local.h>
+
+#include <datasrc/data_source.h>
+#include <datasrc/factory.h>
+#include <datasrc/result.h>
+
+#include <dns/name.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrsetlist.h>
+#include <dns/masterload.h>
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+
+#include <algorithm>
+#include <map>
+#include <utility>
+#include <cctype>
+#include <cassert>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc::memory;
+using boost::scoped_ptr;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+using detail::SegmentObjectHolder;
+
+namespace {
+// Some type aliases
+typedef DomainTree<std::string> FileNameTree;
+typedef DomainTreeNode<std::string> FileNameNode;
+
+// A functor type used for loading.
+typedef boost::function<void(ConstRRsetPtr)> LoadCallback;
+
+} // end of anonymous namespace
+
+/// Implementation details for \c InMemoryClient hidden from the public
+/// interface.
+///
+/// For now, \c InMemoryClient only contains a \c ZoneTable object, which
+/// consists of (pointers to) \c InMemoryZoneFinder objects, we may add more
+/// member variables later for new features.
+class InMemoryClient::InMemoryClientImpl {
+private:
+    // The deleter for the filenames stored in the tree.
+    struct FileNameDeleter {
+        FileNameDeleter() {}
+        void operator()(std::string* filename) const {
+            delete filename;
+        }
+    };
+
+public:
+    InMemoryClientImpl(util::MemorySegment& mem_sgmt, RRClass rrclass) :
+        mem_sgmt_(mem_sgmt),
+        rrclass_(rrclass),
+        zone_count_(0),
+        zone_table_(ZoneTable::create(mem_sgmt_, rrclass)),
+        file_name_tree_(FileNameTree::create(mem_sgmt_, false))
+    {}
+    ~InMemoryClientImpl() {
+        FileNameDeleter deleter;
+        FileNameTree::destroy(mem_sgmt_, file_name_tree_, deleter);
+
+        ZoneTable::destroy(mem_sgmt_, zone_table_, rrclass_);
+
+        // see above for the assert().
+        assert(mem_sgmt_.allMemoryDeallocated());
+    }
+
+    util::MemorySegment& mem_sgmt_;
+    const RRClass rrclass_;
+    unsigned int zone_count_;
+    ZoneTable* zone_table_;
+    FileNameTree* file_name_tree_;
+    ConstRRsetPtr last_rrset_;
+
+    // Common process for zone load.
+    // rrset_installer is a functor that takes another functor as an argument,
+    // and expected to call the latter for each RRset of the zone.  How the
+    // sequence of the RRsets is generated depends on the internal
+    // details  of the loader: either from a textual master file or from
+    // another data source.
+    // filename is the file name of the master file or empty if the zone is
+    // loaded from another data source.
+    result::Result load(const Name& zone_name, const string& filename,
+                        boost::function<void(LoadCallback)> rrset_installer);
+
+    // Add the necessary magic for any wildcard contained in 'name'
+    // (including itself) to be found in the zone.
+    //
+    // In order for wildcard matching to work correctly in the zone finder,
+    // we must ensure that a node for the wildcarding level exists in the
+    // backend RBTree.
+    // E.g. if the wildcard name is "*.sub.example." then we must ensure
+    // that "sub.example." exists and is marked as a wildcard level.
+    // Note: the "wildcarding level" is for the parent name of the wildcard
+    // name (such as "sub.example.").
+    //
+    // We also perform the same trick for empty wild card names possibly
+    // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
+    void addWildcards(const Name& zone_name, ZoneData& zone_data,
+                      const Name& name)
+    {
+        Name wname(name);
+        const unsigned int labels(wname.getLabelCount());
+        const unsigned int origin_labels(zone_name.getLabelCount());
+        for (unsigned int l = labels;
+             l > origin_labels;
+             --l, wname = wname.split(1)) {
+            if (wname.isWildcard()) {
+                LOG_DEBUG(logger, DBG_TRACE_DATA,
+                          DATASRC_MEMORY_MEM_ADD_WILDCARD).arg(name);
+
+                // Ensure a separate level exists for the "wildcarding" name,
+                // and mark the node as "wild".
+                ZoneNode* node;
+                zone_data.insertName(mem_sgmt_, wname.split(1), &node);
+                node->setFlag(ZoneData::WILDCARD_NODE);
+
+                // Ensure a separate level exists for the wildcard name.
+                // Note: for 'name' itself we do this later anyway, but the
+                // overhead should be marginal because wildcard names should
+                // be rare.
+                zone_data.insertName(mem_sgmt_, wname, &node);
+            }
+        }
+    }
+
+    /*
+     * Does some checks in context of the data that are already in the zone.
+     * Currently checks for forbidden combinations of RRsets in the same
+     * domain (CNAME+anything, DNAME+NS).
+     *
+     * If such condition is found, it throws AddError.
+     */
+    void contextCheck(const Name& zone_name, const AbstractRRset& rrset,
+                      const RdataSet* set) const {
+        // Ensure CNAME and other type of RR don't coexist for the same
+        // owner name except with NSEC, which is the only RR that can coexist
+        // with CNAME (and also RRSIG, which is handled separately)
+        if (rrset.getType() == RRType::CNAME()) {
+            for (const RdataSet* sp = set; sp != NULL; sp = sp->getNext()) {
+                if (sp->type != RRType::NSEC()) {
+                    LOG_ERROR(logger, DATASRC_MEMORY_MEM_CNAME_TO_NONEMPTY).
+                        arg(rrset.getName());
+                    isc_throw(AddError, "CNAME can't be added with "
+                              << sp->type << " RRType for "
+                              << rrset.getName());
+                }
+            }
+        } else if ((rrset.getType() != RRType::NSEC()) &&
+                   (RdataSet::find(set, RRType::CNAME()) != NULL)) {
+            LOG_ERROR(logger,
+                      DATASRC_MEMORY_MEM_CNAME_COEXIST).arg(rrset.getName());
+            isc_throw(AddError, "CNAME and " << rrset.getType() <<
+                      " can't coexist for " << rrset.getName());
+        }
+
+        /*
+         * Similar with DNAME, but it must not coexist only with NS and only in
+         * non-apex domains.
+         * RFC 2672 section 3 mentions that it is implied from it and RFC 2181
+         */
+        if (rrset.getName() != zone_name &&
+            // Adding DNAME, NS already there
+            ((rrset.getType() == RRType::DNAME() &&
+              RdataSet::find(set, RRType::NS()) != NULL) ||
+            // Adding NS, DNAME already there
+            (rrset.getType() == RRType::NS() &&
+             RdataSet::find(set, RRType::DNAME()) != NULL)))
+        {
+            LOG_ERROR(logger, DATASRC_MEMORY_MEM_DNAME_NS).arg(rrset.getName());
+            isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
+                "domain " << rrset.getName());
+        }
+    }
+
+    // Validate rrset before adding it to the zone.  If something is wrong
+    // it throws an exception.  It doesn't modify the zone, and provides
+    // the strong exception guarantee.
+    void addValidation(const Name& zone_name, const ConstRRsetPtr rrset) {
+        if (!rrset) {
+            isc_throw(NullRRset, "The rrset provided is NULL");
+        }
+        if (rrset->getRdataCount() == 0) {
+            isc_throw(AddError, "The rrset provided is empty: " <<
+                      rrset->getName() << "/" << rrset->getType());
+        }
+        // Check for singleton RRs. It should probably handled at a different
+        // layer in future.
+        if ((rrset->getType() == RRType::CNAME() ||
+            rrset->getType() == RRType::DNAME()) &&
+            rrset->getRdataCount() > 1)
+        {
+            // XXX: this is not only for CNAME or DNAME. We should generalize
+            // this code for all other "singleton RR types" (such as SOA) in a
+            // separate task.
+            LOG_ERROR(logger,
+                      DATASRC_MEMORY_MEM_SINGLETON).arg(rrset->getName()).
+                arg(rrset->getType());
+            isc_throw(AddError, "multiple RRs of singleton type for "
+                      << rrset->getName());
+        }
+        // NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this
+        // implementation requests it be so at the moment.
+        if ((rrset->getType() == RRType::NSEC3() ||
+             rrset->getType() == RRType::NSEC3PARAM()) &&
+            rrset->getRdataCount() > 1) {
+            isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for "
+                      << rrset->getName() << " which isn't supported");
+        }
+
+        NameComparisonResult compare(zone_name.compare(rrset->getName()));
+        if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
+            compare.getRelation() != NameComparisonResult::EQUAL)
+        {
+            LOG_ERROR(logger,
+                      DATASRC_MEMORY_MEM_OUT_OF_ZONE).arg(rrset->getName()).
+                arg(zone_name);
+            isc_throw(OutOfZone, "The name " << rrset->getName() <<
+                " is not contained in zone " << zone_name);
+        }
+
+        // Some RR types do not really work well with a wildcard.
+        // Even though the protocol specifically doesn't completely ban such
+        // usage, we refuse to load a zone containing such RR in order to
+        // keep the lookup logic simpler and more predictable.
+        // See RFC4592 and (for DNAME) draft-ietf-dnsext-rfc2672bis-dname
+        // for more technical background.  Note also that BIND 9 refuses
+        // NS at a wildcard, so in that sense we simply provide compatible
+        // behavior.
+        if (rrset->getName().isWildcard()) {
+            if (rrset->getType() == RRType::NS()) {
+                LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_NS).
+                    arg(rrset->getName());
+                isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
+                          rrset->getName());
+            }
+            if (rrset->getType() == RRType::DNAME()) {
+                LOG_ERROR(logger, DATASRC_MEMORY_MEM_WILDCARD_DNAME).
+                    arg(rrset->getName());
+                isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
+                          rrset->getName());
+            }
+        }
+
+        // Owner names of NSEC3 have special format as defined in RFC5155,
+        // and cannot be a wildcard name or must be one label longer than
+        // the zone origin.  While the RFC doesn't prohibit other forms of
+        // names, no sane zone would have such names for NSEC3.
+        // BIND 9 also refuses NSEC3 at wildcard.
+        if (rrset->getType() == RRType::NSEC3() &&
+            (rrset->getName().isWildcard() ||
+             rrset->getName().getLabelCount() !=
+             zone_name.getLabelCount() + 1)) {
+            LOG_ERROR(logger, DATASRC_MEMORY_BAD_NSEC3_NAME).
+                arg(rrset->getName());
+            isc_throw(AddError, "Invalid NSEC3 owner name: " <<
+                      rrset->getName());
+        }
+    }
+
+    void addNSEC3(const ConstRRsetPtr rrset,
+                  const ConstRRsetPtr rrsig,
+                  ZoneData& zone_data) {
+        // We know rrset has exactly one RDATA
+        const generic::NSEC3& nsec3_rdata =
+            dynamic_cast<const generic::NSEC3&>(
+                rrset->getRdataIterator()->getCurrent());
+
+        NSEC3Data* nsec3_data = zone_data.getNSEC3Data();
+        if (nsec3_data == NULL) {
+            nsec3_data = NSEC3Data::create(mem_sgmt_, nsec3_rdata);
+            zone_data.setNSEC3Data(nsec3_data);
+        } else {
+            size_t salt_len = nsec3_data->getSaltLen();
+            const uint8_t* salt_data = nsec3_data->getSaltData();
+            const vector<uint8_t>& salt_data_2 = nsec3_rdata.getSalt();
+
+            if ((nsec3_rdata.getHashalg() != nsec3_data->hashalg) ||
+                (nsec3_rdata.getIterations() != nsec3_data->iterations) ||
+                (salt_data_2.size() != salt_len) ||
+                (std::memcmp(&salt_data_2[0], salt_data, salt_len) != 0)) {
+                isc_throw(AddError,
+                          "NSEC3 with inconsistent parameters: " <<
+                          rrset->toText());
+            }
+        }
+
+        string fst_label = rrset->getName().split(0, 1).toText(true);
+        transform(fst_label.begin(), fst_label.end(), fst_label.begin(),
+                  ::toupper);
+
+        ZoneNode* node;
+        nsec3_data->insertName(mem_sgmt_, Name(fst_label), &node);
+
+        RdataEncoder encoder;
+
+        // We assume that rrsig has already been checked to match rrset
+        // by the caller.
+        RdataSet* set = RdataSet::create(mem_sgmt_, encoder, rrset, rrsig);
+        RdataSet* old_set = node->setData(set);
+        if (old_set != NULL) {
+            RdataSet::destroy(mem_sgmt_, rrclass_, old_set);
+        }
+    }
+
+    void addRdataSet(const Name& zone_name, ZoneData& zone_data,
+                     const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig) {
+        // Only one of these can be passed at a time.
+        assert(!(rrset && rrsig));
+
+        // If rrsig is passed, validate it against the last-saved rrset.
+        if (rrsig) {
+            // The covered RRset should have been saved by now.
+            if (!last_rrset_) {
+                isc_throw(AddError,
+                          "RRSIG is being added, "
+                          "but doesn't follow its covered RR: "
+                          << rrsig->getName());
+            }
+
+            if (rrsig->getName() != last_rrset_->getName()) {
+                isc_throw(AddError,
+                          "RRSIG is being added, "
+                          "but doesn't match the last RR's name: "
+                          << last_rrset_->getName() << " vs. "
+                          << rrsig->getName());
+            }
+
+            // Consistency of other types in rrsig are checked in addRRsig().
+            RdataIteratorPtr rit = rrsig->getRdataIterator();
+            const RRType covered = dynamic_cast<const generic::RRSIG&>(
+                rit->getCurrent()).typeCovered();
+
+            if (covered != last_rrset_->getType()) {
+                isc_throw(AddError,
+                          "RRSIG is being added, "
+                          "but doesn't match the last RR's type: "
+                          << last_rrset_->getType() << " vs. "
+                          << covered);
+            }
+        }
+
+        if (!last_rrset_) {
+            last_rrset_ = rrset;
+            return;
+        }
+
+        if (last_rrset_->getType() == RRType::NSEC3()) {
+            addNSEC3(last_rrset_, rrsig, zone_data);
+        } else {
+            ZoneNode* node;
+            zone_data.insertName(mem_sgmt_, last_rrset_->getName(), &node);
+
+            RdataSet* set = node->getData();
+
+            // Checks related to the surrounding data.
+            // Note: when the check fails and the exception is thrown,
+            // it may break strong exception guarantee.  At the moment
+            // we prefer code simplicity and don't bother to introduce
+            // complicated recovery code.
+            contextCheck(zone_name, *last_rrset_, set);
+
+            if (RdataSet::find(set, last_rrset_->getType()) != NULL) {
+                isc_throw(AddError,
+                          "RRset of the type already exists: "
+                          << last_rrset_->getName() << " (type: "
+                          << last_rrset_->getType() << ")");
+            }
+
+            RdataEncoder encoder;
+            RdataSet *new_set = RdataSet::create(mem_sgmt_, encoder,
+                                                 last_rrset_, rrsig);
+            new_set->next = set;
+            node->setData(new_set);
+
+            // Ok, we just put it in
+
+            // If this RRset creates a zone cut at this node, mark the
+            // node indicating the need for callback in find().
+            if (last_rrset_->getType() == RRType::NS() &&
+                last_rrset_->getName() != zone_name) {
+                node->setFlag(ZoneNode::FLAG_CALLBACK);
+                // If it is DNAME, we have a callback as well here
+            } else if (last_rrset_->getType() == RRType::DNAME()) {
+                node->setFlag(ZoneNode::FLAG_CALLBACK);
+            }
+
+            // If we've added NSEC3PARAM at zone origin, set up NSEC3
+            // specific data or check consistency with already set up
+            // parameters.
+            if (last_rrset_->getType() == RRType::NSEC3PARAM() &&
+                last_rrset_->getName() == zone_name) {
+                // We know rrset has exactly one RDATA
+                const generic::NSEC3PARAM& param =
+                    dynamic_cast<const generic::NSEC3PARAM&>
+                      (last_rrset_->getRdataIterator()->getCurrent());
+
+                NSEC3Data* nsec3_data = zone_data.getNSEC3Data();
+                if (nsec3_data == NULL) {
+                    nsec3_data = NSEC3Data::create(mem_sgmt_, param);
+                    zone_data.setNSEC3Data(nsec3_data);
+                } else {
+                    size_t salt_len = nsec3_data->getSaltLen();
+                    const uint8_t* salt_data = nsec3_data->getSaltData();
+                    const vector<uint8_t>& salt_data_2 = param.getSalt();
+
+                    if ((param.getHashalg() != nsec3_data->hashalg) ||
+                        (param.getIterations() != nsec3_data->iterations) ||
+                        (salt_data_2.size() != salt_len) ||
+                        (std::memcmp(&salt_data_2[0],
+                                     salt_data, salt_len) != 0)) {
+                        isc_throw(AddError,
+                                  "NSEC3PARAM with inconsistent parameters: "
+                                  << last_rrset_->toText());
+                    }
+                }
+            } else if (last_rrset_->getType() == RRType::NSEC()) {
+                // If it is NSEC signed zone, so we put a flag there
+                // (flag is enough)
+                zone_data.setSigned(true);
+            }
+        }
+
+        last_rrset_ = rrset;
+    }
+
+    result::Result addRRsig(const ConstRRsetPtr sig_rrset,
+                            const Name& zone_name, ZoneData& zone_data)
+    {
+        // Check consistency of the type covered.
+        // We know the RRset isn't empty, so the following check is safe.
+        RdataIteratorPtr rit = sig_rrset->getRdataIterator();
+        const RRType covered = dynamic_cast<const generic::RRSIG&>(
+            rit->getCurrent()).typeCovered();
+        for (rit->next(); !rit->isLast(); rit->next()) {
+            if (dynamic_cast<const generic::RRSIG&>(
+                    rit->getCurrent()).typeCovered() != covered) {
+                isc_throw(AddError, "RRSIG contains mixed covered types: "
+                          << sig_rrset->toText());
+            }
+        }
+
+        addRdataSet(zone_name, zone_data, ConstRRsetPtr(), sig_rrset);
+        return (result::SUCCESS);
+    }
+
+    /*
+     * Implementation of longer methods. We put them here, because the
+     * access is without the impl_-> and it will get inlined anyway.
+     */
+
+    // Implementation of InMemoryClient::add()
+    result::Result add(const ConstRRsetPtr& rrset,
+                       const Name& zone_name, ZoneData& zone_data)
+    {
+        // Sanitize input.  This will cause an exception to be thrown
+        // if the input RRset is empty.
+        addValidation(zone_name, rrset);
+
+        // OK, can add the RRset.
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).
+            arg(rrset->getName()).arg(rrset->getType()).arg(zone_name);
+
+        if (rrset->getType() == RRType::NSEC3()) {
+            addRdataSet(zone_name, zone_data, rrset, ConstRRsetPtr());
+            return (result::SUCCESS);
+        }
+
+        // RRSIGs are special in various points, so we handle it in a
+        // separate dedicated method.
+        if (rrset->getType() == RRType::RRSIG()) {
+            return (addRRsig(rrset, zone_name, zone_data));
+        }
+
+        // Add wildcards possibly contained in the owner name to the domain
+        // tree.
+        // Note: this can throw an exception, breaking strong exception
+        // guarantee.  (see also the note for contextCheck() below).
+        addWildcards(zone_name, zone_data, rrset->getName());
+
+        addRdataSet(zone_name, zone_data, rrset, ConstRRsetPtr());
+
+        return (result::SUCCESS);
+    }
+
+    /*
+     * Wrapper around above.
+     */
+    void addFromLoad(const ConstRRsetPtr& set,
+                     const Name& zone_name, ZoneData* zone_data)
+    {
+        switch (add(set, zone_name, *zone_data)) {
+        case result::SUCCESS:
+            return;
+        default:
+            assert(0);
+        }
+    }
+};
+
+result::Result
+InMemoryClient::InMemoryClientImpl::load(
+    const Name& zone_name,
+    const string& filename,
+    boost::function<void(LoadCallback)> rrset_installer)
+{
+    SegmentObjectHolder<ZoneData, RRClass> holder(
+        mem_sgmt_, ZoneData::create(mem_sgmt_, zone_name),
+        rrclass_);
+
+    assert(!last_rrset_);
+
+    try {
+        rrset_installer(boost::bind(&InMemoryClientImpl::addFromLoad, this,
+                                    _1, zone_name, holder.get()));
+        // Add any last RRset that was left
+        addRdataSet(zone_name, *holder.get(),
+                    ConstRRsetPtr(), ConstRRsetPtr());
+    } catch (...) {
+        last_rrset_ = ConstRRsetPtr();
+        throw;
+    }
+
+    assert(!last_rrset_);
+
+    const ZoneNode* origin_node = holder.get()->getOriginNode();
+    const RdataSet* set = origin_node->getData();
+    // If the zone is NSEC3-signed, check if it has NSEC3PARAM
+    if (holder.get()->isNSEC3Signed()) {
+        // Note: origin_data_ is set on creation of ZoneData, and the load
+        // process only adds new nodes (and their data), so this assertion
+        // should hold.
+        if (RdataSet::find(set, RRType::NSEC3PARAM()) == NULL) {
+            LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
+                arg(zone_name).arg(rrclass_);
+        }
+    }
+
+    // When an empty zone file is loaded, the origin doesn't even have
+    // an SOA RR. This condition should be avoided, and hence load()
+    // should throw when an empty zone is loaded.
+    if (RdataSet::find(set, RRType::SOA()) == NULL) {
+        isc_throw(EmptyZone,
+                  "Won't create an empty zone for: " << zone_name);
+    }
+
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
+        arg(zone_name).arg(rrclass_.toText());
+
+    // Set the filename in file_name_tree_ now, so that getFileName()
+    // can use it (during zone reloading).
+    FileNameNode* node(NULL);
+    switch (file_name_tree_->insert(mem_sgmt_, zone_name, &node)) {
+    case FileNameTree::SUCCESS:
+    case FileNameTree::ALREADYEXISTS:
+        // These are OK
+        break;
+    default:
+        // Can Not Happen
+        assert(false);
+    }
+    // node must point to a valid node now
+    assert(node != NULL);
+
+    std::string* tstr = node->setData(new std::string(filename));
+    delete tstr;
+
+    ZoneTable::AddResult result(zone_table_->addZone(mem_sgmt_, rrclass_,
+                                                     zone_name));
+    if (result.code == result::SUCCESS) {
+        // Only increment the zone count if the zone doesn't already
+        // exist.
+        ++zone_count_;
+    }
+
+    ZoneTable::FindResult fr(zone_table_->setZoneData(zone_name,
+                                                      holder.release()));
+    assert(fr.code == result::SUCCESS);
+    if (fr.zone_data != NULL) {
+        ZoneData::destroy(mem_sgmt_, fr.zone_data, rrclass_);
+    }
+
+    return (result.code);
+}
+
+namespace {
+// A wrapper for dns::masterLoad used by load() below.  Essentially it
+// converts the two callback types.  Note the mostly redundant wrapper of
+// boost::bind.  It converts function<void(ConstRRsetPtr)> to
+// function<void(RRsetPtr)> (masterLoad() expects the latter).  SunStudio
+// doesn't seem to do this conversion if we just pass 'callback'.
+void
+masterLoadWrapper(const char* const filename, const Name& origin,
+                  const RRClass& zone_class, LoadCallback callback)
+{
+    masterLoad(filename, origin, zone_class, boost::bind(callback, _1));
+}
+
+// The installer called from Impl::load() for the iterator version of load().
+void
+generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) {
+    ConstRRsetPtr rrset;
+    vector<ConstRRsetPtr> rrsigs; // placeholder for RRSIGs until "commitable".
+
+    // The current internal implementation assumes an RRSIG is always added
+    // after the RRset they cover.  So we store any RRSIGs in 'rrsigs' until
+    // it's safe to add them; based on our assumption if the owner name
+    // changes, all covered RRsets of the previous name should have been
+    // installed and any pending RRSIGs can be added at that point.  RRSIGs
+    // of the last name from the iterator must be added separately.
+    while ((rrset = iterator->getNextRRset()) != NULL) {
+        if (!rrsigs.empty() && rrset->getName() != rrsigs[0]->getName()) {
+            BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
+                callback(sig_rrset);
+            }
+            rrsigs.clear();
+        }
+        if (rrset->getType() == RRType::RRSIG()) {
+            rrsigs.push_back(rrset);
+        } else {
+            callback(rrset);
+        }
+    }
+
+    BOOST_FOREACH(ConstRRsetPtr sig_rrset, rrsigs) {
+        callback(sig_rrset);
+    }
+}
+}
+
+InMemoryClient::InMemoryClient(util::MemorySegment& mem_sgmt,
+                               RRClass rrclass) :
+    impl_(new InMemoryClientImpl(mem_sgmt, rrclass))
+{}
+
+InMemoryClient::~InMemoryClient() {
+    delete impl_;
+}
+
+RRClass
+InMemoryClient::getClass() const {
+    return (impl_->rrclass_);
+}
+
+unsigned int
+InMemoryClient::getZoneCount() const {
+    return (impl_->zone_count_);
+}
+
+isc::datasrc::memory::ZoneTable::FindResult
+InMemoryClient::findZone2(const isc::dns::Name& zone_name) const {
+    LOG_DEBUG(logger, DBG_TRACE_DATA,
+              DATASRC_MEMORY_MEM_FIND_ZONE).arg(zone_name);
+    ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name));
+    return (result);
+}
+
+isc::datasrc::DataSourceClient::FindResult
+InMemoryClient::findZone(const isc::dns::Name&) const {
+    // This variant of findZone() is not implemented and should be
+    // removed eventually. It currently throws an exception. It is
+    // required right now to derive from DataSourceClient.
+    isc_throw(isc::NotImplemented,
+              "This variant of findZone() is not implemented.");
+}
+
+result::Result
+InMemoryClient::load(const isc::dns::Name& zone_name,
+                     const std::string& filename) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD).arg(zone_name).
+        arg(filename);
+
+    return (impl_->load(zone_name, filename,
+                        boost::bind(masterLoadWrapper, filename.c_str(),
+                                    zone_name, getClass(), _1)));
+}
+
+result::Result
+InMemoryClient::load(const isc::dns::Name& zone_name,
+                     ZoneIterator& iterator) {
+    return (impl_->load(zone_name, string(),
+                        boost::bind(generateRRsetFromIterator,
+                                    &iterator, _1)));
+}
+
+const std::string
+InMemoryClient::getFileName(const isc::dns::Name& zone_name) const {
+    FileNameNode* node(NULL);
+    FileNameTree::Result result = impl_->file_name_tree_->find(zone_name,
+                                                               &node);
+    if (result == FileNameTree::EXACTMATCH) {
+        return (*node->getData());
+    } else {
+        return (std::string());
+    }
+}
+
+result::Result
+InMemoryClient::add(const isc::dns::Name& zone_name,
+                    const ConstRRsetPtr& rrset) {
+    assert(!impl_->last_rrset_);
+
+    ZoneTable::FindResult result(impl_->zone_table_->findZone(zone_name));
+    if (result.code != result::SUCCESS) {
+        isc_throw(DataSourceError, "No such zone: " + zone_name.toText());
+    }
+
+    result::Result ret(impl_->add(rrset, zone_name, *result.zone_data));
+    // Add any associated RRSIG too. This has to be done here, as both
+    // the RRset and its RRSIG have to be passed when constructing an
+    // RdataSet.
+    if ((ret == result::SUCCESS) && rrset->getRRsig()) {
+        impl_->add(rrset->getRRsig(), zone_name, *result.zone_data);
+    }
+
+    // Add any last RRset that was left
+    impl_->addRdataSet(zone_name, *result.zone_data,
+                       ConstRRsetPtr(), ConstRRsetPtr());
+
+    assert(!impl_->last_rrset_);
+
+    return (ret);
+}
+
+namespace {
+
+class MemoryIterator : public ZoneIterator {
+private:
+    ZoneChain chain_;
+    const RdataSet* set_node_;
+    const RRClass rrclass_;
+    const ZoneTree& tree_;
+    const ZoneNode* node_;
+    // Only used when separate_rrs_ is true
+    ConstRRsetPtr rrset_;
+    RdataIteratorPtr rdata_iterator_;
+    bool separate_rrs_;
+    bool ready_;
+public:
+    MemoryIterator(const RRClass rrclass,
+                   const ZoneTree& tree, const Name& origin,
+                   bool separate_rrs) :
+        rrclass_(rrclass),
+        tree_(tree),
+        separate_rrs_(separate_rrs),
+        ready_(true)
+    {
+        // Find the first node (origin) and preserve the node chain for future
+        // searches
+        ZoneTree::Result result(tree_.find(origin, &node_, chain_));
+        // It can't happen that the origin is not in there
+        if (result != ZoneTree::EXACTMATCH) {
+            isc_throw(Unexpected,
+                      "In-memory zone corrupted, missing origin node");
+        }
+        // Initialize the iterator if there's somewhere to point to
+        if (node_ != NULL && node_->getData() != NULL) {
+            set_node_ = node_->getData();
+            if (separate_rrs_ && set_node_ != NULL) {
+                rrset_.reset(new TreeNodeRRset(rrclass_,
+                                               node_, set_node_, true));
+                rdata_iterator_ = rrset_->getRdataIterator();
+            }
+        }
+    }
+
+    virtual ConstRRsetPtr getNextRRset() {
+        if (!ready_) {
+            isc_throw(Unexpected, "Iterating past the zone end");
+        }
+        /*
+         * This cycle finds the first nonempty node with yet unused
+         * RdataSset.  If it is NULL, we run out of nodes. If it is
+         * empty, it doesn't contain any RdataSets. If we are at the
+         * end, just get to next one.
+         */
+        while (node_ != NULL &&
+               (node_->getData() == NULL || set_node_ == NULL)) {
+            node_ = tree_.nextNode(chain_);
+            // If there's a node, initialize the iterator and check next time
+            // if the map is empty or not
+            if (node_ != NULL && node_->getData() != NULL) {
+                set_node_ = node_->getData();
+                // New RRset, so get a new rdata iterator
+                if (separate_rrs_ && set_node_ != NULL) {
+                    rrset_.reset(new TreeNodeRRset(rrclass_,
+                                                   node_, set_node_, true));
+                    rdata_iterator_ = rrset_->getRdataIterator();
+                }
+            }
+        }
+        if (node_ == NULL) {
+            // That's all, folks
+            ready_ = false;
+            return (ConstRRsetPtr());
+        }
+
+        if (separate_rrs_) {
+            // For separate rrs, reconstruct a new RRset with just the
+            // 'current' rdata
+            RRsetPtr result(new RRset(rrset_->getName(),
+                                      rrset_->getClass(),
+                                      rrset_->getType(),
+                                      rrset_->getTTL()));
+            result->addRdata(rdata_iterator_->getCurrent());
+            rdata_iterator_->next();
+            if (rdata_iterator_->isLast()) {
+                // all used up, next.
+                set_node_ = set_node_->getNext();
+                // New RRset, so get a new rdata iterator, but only if this
+                // was not the final RRset in the chain
+                if (set_node_ != NULL) {
+                    rrset_.reset(new TreeNodeRRset(rrclass_,
+                                                   node_, set_node_, true));
+                    rdata_iterator_ = rrset_->getRdataIterator();
+                }
+            }
+            return (result);
+        } else {
+            ConstRRsetPtr result(new TreeNodeRRset(rrclass_,
+                                                   node_, set_node_, true));
+
+            // This one is used, move it to the next time for next call
+            set_node_ = set_node_->getNext();
+
+            return (result);
+        }
+    }
+
+    virtual ConstRRsetPtr getSOA() const {
+        isc_throw(NotImplemented, "Not implemented");
+    }
+};
+
+} // End of anonymous namespace
+
+ZoneIteratorPtr
+InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
+    ZoneTable::FindResult result(impl_->zone_table_->findZone(name));
+    if (result.code != result::SUCCESS) {
+        isc_throw(DataSourceError, "No such zone: " + name.toText());
+    }
+
+    return (ZoneIteratorPtr(new MemoryIterator(
+                                getClass(),
+                                result.zone_data->getZoneTree(), name,
+                                separate_rrs)));
+}
+
+ZoneUpdaterPtr
+InMemoryClient::getUpdater(const isc::dns::Name&, bool, bool) const {
+    isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
+}
+
+pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+InMemoryClient::getJournalReader(const isc::dns::Name&, uint32_t,
+                                 uint32_t) const
+{
+    isc_throw(isc::NotImplemented, "Journaling isn't supported for "
+              "in memory data source");
+}
+
+} // end of namespace memory
+} // end of namespace datasrc
+} // end of namespace isc

+ 257 - 0
src/lib/datasrc/memory/memory_client.h

@@ -0,0 +1,257 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_CLIENT_H
+#define DATASRC_MEMORY_CLIENT_H 1
+
+#include <util/memory_segment.h>
+
+#include <datasrc/iterator.h>
+#include <datasrc/client.h>
+#include <datasrc/memory/zone_table.h>
+
+// for isc::datasrc::ZoneTable::FindResult returned by findZone(). This
+// variant of findZone() is not implemented and should be removed
+// eventually.
+#include <datasrc/zonetable.h>
+
+#include <string>
+
+namespace isc {
+
+namespace dns {
+class Name;
+class RRsetList;
+};
+
+namespace datasrc {
+namespace memory {
+
+/// \brief A data source client that holds all necessary data in memory.
+///
+/// The \c InMemoryClient class provides an access to a conceptual data
+/// source that maintains all necessary data in a memory image, thereby
+/// allowing much faster lookups.  The in memory data is a copy of some
+/// real physical source - in the current implementation a list of zones
+/// are populated as a result of \c load() calls; zone data is given in
+/// a standard master file, or as an iterator of some other datasource
+/// including database backed ones.
+///
+/// The InMemoryClient enforces through its interface that all data
+/// loaded to the data source is of the same RR class.  For example, the
+/// \c load() method assumes that the zone being loaded belongs to the
+/// same RR class as the memory::Client instance.
+class InMemoryClient : public DataSourceClient {
+public:
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    //@{
+
+    /// Default constructor.
+    ///
+    /// This constructor internally involves resource allocation, and if
+    /// it fails, a corresponding standard exception will be thrown.
+    /// It never throws an exception otherwise.
+    InMemoryClient(util::MemorySegment& mem_sgmt,
+                   isc::dns::RRClass rrclass);
+
+    /// The destructor.
+    ~InMemoryClient();
+    //@}
+
+    /// \brief Returns the class of the data source client.
+    virtual isc::dns::RRClass getClass() const;
+
+    /// Return the number of zones stored in the client.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The number of zones stored in the client.
+    virtual unsigned int getZoneCount() const;
+
+    /// \brief Load zone from masterfile.
+    ///
+    /// This loads data from masterfile specified by filename. It replaces
+    /// current content. The masterfile parsing ability is kind of limited,
+    /// see isc::dns::masterLoad.
+    ///
+    /// This throws isc::dns::MasterLoadError if there is problem with loading
+    /// (missing file, malformed, it contains different zone, etc - see
+    /// isc::dns::masterLoad for details).
+    ///
+    /// In case of internal problems, OutOfZone, NullRRset or AssertError could
+    /// be thrown, but they should not be expected. Exceptions caused by
+    /// allocation may be thrown as well.
+    ///
+    /// If anything is thrown, the previous content is preserved (so it can
+    /// be used to update the data, but if user makes a typo, the old one
+    /// is kept).
+    ///
+    /// \param filename The master file to load.
+    ///
+    /// \todo We may need to split it to some kind of build and commit/abort.
+    ///     This will probably be needed when a better implementation of
+    ///     configuration reloading is written.
+    result::Result load(const isc::dns::Name& zone_name,
+                        const std::string& filename);
+
+    /// \brief Load zone from another data source.
+    ///
+    /// This is similar to the other version, but zone's RRsets are provided
+    /// by an iterator of another data source.  On successful load, the
+    /// internal filename will be cleared.
+    ///
+    /// This implementation assumes the iterator produces combined RRsets,
+    /// that is, there should exactly one RRset for the same owner name and
+    /// RR type.  This means the caller is expected to create the iterator
+    /// with \c separate_rrs being \c false.  This implementation also assumes
+    /// RRsets of different names are not mixed; so if the iterator produces
+    /// an RRset of a different name than that of the previous RRset, that
+    /// previous name must never appear in the subsequent sequence of RRsets.
+    /// Note that the iterator API does not ensure this.  If the underlying
+    /// implementation does not follow it, load() will fail.  Note, however,
+    /// that this whole interface is tentative.  in-memory zone loading will
+    /// have to be revisited fundamentally, and at that point this restriction
+    /// probably won't matter.
+    result::Result load(const isc::dns::Name& zone_name,
+                        ZoneIterator& iterator);
+
+    /// Return the master file name of the zone
+    ///
+    /// This method returns the name of the zone's master file to be loaded.
+    /// The returned string will be an empty unless the data source client has
+    /// successfully loaded the \c zone_name zone from a file before.
+    ///
+    /// This method should normally not throw an exception.  But the creation
+    /// of the return string may involve a resource allocation, and if it
+    /// fails, the corresponding standard exception will be thrown.
+    ///
+    /// \return The name of the zone file corresponding to the zone, or
+    /// an empty string if the client hasn't loaded the \c zone_name
+    /// zone from a file before.
+    const std::string getFileName(const isc::dns::Name& zone_name) const;
+
+    /// \brief Inserts an rrset into the zone.
+    ///
+    /// It puts another RRset into the zone.
+    ///
+    /// In the current implementation, this method doesn't allow an existing
+    /// RRset to be updated or overridden.  So the caller must make sure that
+    /// all RRs of the same type and name must be given in the form of a
+    /// single RRset.  The current implementation will also require that
+    /// when an RRSIG is added, the RRset to be covered has already been
+    /// added.  These restrictions are probably too strict when this data
+    /// source accepts various forms of input, so they should be revisited
+    /// later.
+    ///
+    /// Except for NullRRset and OutOfZone, this method does not guarantee
+    /// strong exception safety (it is currently not needed, if it is needed
+    /// in future, it should be implemented).
+    ///
+    /// \throw NullRRset \c rrset is a NULL pointer.
+    /// \throw OutOfZone The owner name of \c rrset is outside of the
+    /// origin of the zone.
+    /// \throw AddError Other general errors.
+    /// \throw Others This method might throw standard allocation exceptions.
+    ///
+    /// \param rrset The set to add.
+    /// \return SUCCESS or EXIST (if an rrset for given name and type already
+    ///    exists).
+    result::Result add(const isc::dns::Name& zone_name,
+                       const isc::dns::ConstRRsetPtr& rrset);
+
+    /// \brief RRset is NULL exception.
+    ///
+    /// This is thrown if the provided RRset parameter is NULL.
+    struct NullRRset : public InvalidParameter {
+        NullRRset(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
+    /// \brief Zone is empty exception.
+    ///
+    /// This is thrown if we have an empty zone created as a result of
+    /// load().
+    struct EmptyZone : public InvalidParameter {
+        EmptyZone(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
+    /// \brief General failure exception for \c add().
+    ///
+    /// This is thrown against general error cases in adding an RRset
+    /// to the zone.
+    ///
+    /// Note: this exception would cover cases for \c OutOfZone or
+    /// \c NullRRset.  We'll need to clarify and unify the granularity
+    /// of exceptions eventually.  For now, exceptions are added as
+    /// developers see the need for it.
+    struct AddError : public InvalidParameter {
+        AddError(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
+    /// Returns a \c ZoneTable result that best matches the given name.
+    ///
+    /// This derived version of the method never throws an exception.
+    /// For other details see \c DataSourceClient::findZone().
+    virtual isc::datasrc::memory::ZoneTable::FindResult
+    findZone2(const isc::dns::Name& name) const;
+
+    // This variant of findZone() is not implemented and should be
+    // removed eventually. It currently throws an exception. It is
+    // required right now to derive from DataSourceClient.
+    virtual isc::datasrc::DataSourceClient::FindResult
+    findZone(const isc::dns::Name& name) const;
+
+    /// \brief Implementation of the getIterator method
+    virtual isc::datasrc::ZoneIteratorPtr
+    getIterator(const isc::dns::Name& name, bool separate_rrs = false) const;
+
+    /// In-memory data source doesn't write back persistently, so this
+    /// derived method will result in a NotImplemented exception.
+    ///
+    /// \note We plan to use a database-based data source as a backend
+    /// persistent storage for an in-memory data source.  When it's
+    /// implemented we may also want to allow the user of the in-memory client
+    /// to update via its updater (this may or may not be a good idea and
+    /// is subject to further discussions).
+    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name& name,
+                                      bool replace, bool journaling = false)
+        const;
+
+    virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+    getJournalReader(const isc::dns::Name& zone, uint32_t begin_serial,
+                     uint32_t end_serial) const;
+
+private:
+    // TODO: Do we still need the PImpl if nobody should manipulate this class
+    // directly any more (it should be handled through DataSourceClient)?
+    class InMemoryClientImpl;
+    InMemoryClientImpl* impl_;
+};
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_CLIENT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 90 - 0
src/lib/datasrc/memory/memory_messages.mes

@@ -0,0 +1,90 @@
+# Copyright (C) 2011  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::datasrc::memory
+
+# \brief Messages for the data source memory library
+
+% DATASRC_MEMORY_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'
+The software refuses to load NSEC3 records into a wildcard domain or
+the owner name has two or more labels below the zone origin.
+It isn't explicitly forbidden, but no sane zone wouldn have such names
+for NSEC3.  BIND 9 also refuses NSEC3 at wildcard, so this behavior is
+compatible with BIND 9.
+
+% DATASRC_MEMORY_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
+Debug information. An RRset is being added to the in-memory data source.
+
+% DATASRC_MEMORY_MEM_ADD_WILDCARD adding wildcards for '%1'
+This is a debug message issued during the processing of a wildcard
+name. The internal domain name tree is scanned and some nodes are
+specially marked to allow the wildcard lookup to succeed.
+
+% DATASRC_MEMORY_MEM_ADD_ZONE adding zone '%1/%2'
+Debug information. A zone is being added into the in-memory data source.
+
+% DATASRC_MEMORY_MEM_CNAME_COEXIST can't add data to CNAME in domain '%1'
+This is the same problem as in MEM_CNAME_TO_NONEMPTY, but it happened the
+other way around -- adding some other data to CNAME.
+
+% DATASRC_MEMORY_MEM_CNAME_TO_NONEMPTY can't add CNAME to domain with other data in '%1'
+Someone or something tried to add a CNAME into a domain that already contains
+some other data. But the protocol forbids coexistence of CNAME with anything
+(RFC 1034, section 3.6.2). This indicates a problem with provided data.
+
+% DATASRC_MEMORY_MEM_DNAME_NS DNAME and NS can't coexist in non-apex domain '%1'
+A request was made for DNAME and NS records to be put into the same
+domain which is not the apex (the top of the zone). This is forbidden
+by RFC 2672 (section 3) and indicates a problem with provided data.
+
+% DATASRC_MEMORY_MEM_DUP_RRSET duplicate RRset '%1/%2'
+An RRset is being inserted into in-memory data source for a second time.  The
+original version must be removed first. Note that loading master files where an
+RRset is split into multiple locations is not supported yet.
+
+% DATASRC_MEMORY_MEM_FIND_ZONE looking for zone '%1'
+Debug information. A zone object for this zone is being searched for in the
+in-memory data source.
+
+% DATASRC_MEMORY_MEM_LOAD loading zone '%1' from file '%2'
+Debug information. The content of master file is being loaded into the memory.
+
+% DATASRC_MEMORY_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2
+The in-memory data source has loaded a zone signed with NSEC3 RRs,
+but it doesn't have a NSEC3PARAM RR at the zone origin.  It's likely that
+the zone is somehow broken, but this RR is not necessarily needed for
+handling lookups with NSEC3 in this data source, so it accepts the given
+content of the zone.  Nevertheless the administrator should look into
+the integrity of the zone data.
+
+% DATASRC_MEMORY_MEM_OUT_OF_ZONE domain '%1' doesn't belong to zone '%2'
+It was attempted to add the domain into a zone that shouldn't have it
+(eg. the domain is not subdomain of the zone origin). This indicates a
+problem with provided data.
+
+% DATASRC_MEMORY_MEM_SINGLETON trying to add multiple RRs for domain '%1' and type '%2'
+Some resource types are singletons -- only one is allowed in a domain
+(for example CNAME or SOA). This indicates a problem with provided data.
+
+% DATASRC_MEMORY_MEM_WILDCARD_DNAME DNAME record in wildcard domain '%1'
+The software refuses to load DNAME records into a wildcard domain.  It isn't
+explicitly forbidden, but the protocol is ambiguous about how this should
+behave and BIND 9 refuses that as well. Please describe your intention using
+different tools.
+
+% DATASRC_MEMORY_MEM_WILDCARD_NS NS record in wildcard domain '%1'
+The software refuses to load NS records into a wildcard domain.  It isn't
+explicitly forbidden, but the protocol is ambiguous about how this should
+behave and BIND 9 refuses that as well. Please describe your intention using
+different tools.

+ 8 - 0
src/lib/datasrc/memory/tests/Makefile.am

@@ -1,6 +1,9 @@
+SUBDIRS = testdata .
+
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
@@ -24,18 +27,23 @@ run_unittests_SOURCES += domaintree_unittest.cc
 run_unittests_SOURCES += treenode_rrset_unittest.cc
 run_unittests_SOURCES += treenode_rrset_unittest.cc
 run_unittests_SOURCES += zone_table_unittest.cc
 run_unittests_SOURCES += zone_table_unittest.cc
 run_unittests_SOURCES += zone_data_unittest.cc
 run_unittests_SOURCES += zone_data_unittest.cc
+run_unittests_SOURCES += zone_finder_unittest.cc
+run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
 run_unittests_SOURCES += memory_segment_test.h
 run_unittests_SOURCES += memory_segment_test.h
 run_unittests_SOURCES += segment_object_holder_unittest.cc
 run_unittests_SOURCES += segment_object_holder_unittest.cc
+run_unittests_SOURCES += memory_client_unittest.cc
 
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 
 
 run_unittests_LDADD = $(builddir)/../libdatasrc_memory.la
 run_unittests_LDADD = $(builddir)/../libdatasrc_memory.la
+run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libb10-datasrc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libb10-testutils.la
 run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libb10-testutils.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 run_unittests_LDADD += $(GTEST_LDADD)
 run_unittests_LDADD += $(GTEST_LDADD)
 endif
 endif
 
 

+ 136 - 0
src/lib/datasrc/memory/tests/domaintree_unittest.cc

@@ -454,6 +454,142 @@ TEST_F(DomainTreeTest, callbackLabelSequence) {
     performCallbackTest(dtree, mem_sgmt_, ls1, ls2);
     performCallbackTest(dtree, mem_sgmt_, ls1, ls2);
 }
 }
 
 
+TEST_F(DomainTreeTest, findInSubTree) {
+    // For the version that takes a node chain, the chain must be empty.
+    DomainTreeNodeChain<int> chain;
+    bool flag;
+
+    // Searching for a non-absolute (right-stripped) label sequence when
+    // chain is empty should throw.
+    const Name n0("w.y.d.e.f");
+    LabelSequence ls0(n0);
+    ls0.stripRight(1);
+    EXPECT_THROW(dtree_expose_empty_node.find(ls0, &cdtnode, chain,
+                                              testCallback, &flag),
+                 isc::BadValue);
+
+    // First, find a sub-tree node
+    chain.clear();
+    const LabelSequence ls1(n0);
+    DomainTree<int>::Result result =
+        dtree_expose_empty_node.find(ls1, &cdtnode, chain,
+                                     testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(n0, chain.getAbsoluteName());
+
+    // Searching for an absolute label sequence when chain is already
+    // populated should throw.
+    const Name n2a("o");
+    const LabelSequence ls2a(n2a);
+    EXPECT_THROW(dtree_expose_empty_node.find(ls2a, &cdtnode, chain,
+                                              testCallback, &flag),
+                 isc::BadValue);
+
+    // Now, find "o.w.y.d.e.f." by right-stripping the "w.y.d.e.f."
+    // suffix to "o" (non-absolute).
+    const Name n2("o.w.y.d.e.f");
+    LabelSequence ls2(n2);
+    ls2.stripRight(6);
+    EXPECT_EQ("o", ls2.toText());
+
+    result = dtree_expose_empty_node.find(ls2, &cdtnode, chain,
+                                          testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(n2, chain.getAbsoluteName());
+
+    // Another test. Start with "d.e.f." node.
+    chain.clear();
+    const Name n3("d.e.f");
+    const LabelSequence ls3(n3);
+    result =
+        dtree_expose_empty_node.find(ls3, &cdtnode, chain,
+                                     testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(n3, chain.getAbsoluteName());
+
+    // Now, find "o.w.y.d.e.f." by right-stripping the "w.y.d.e.f."
+    // suffix to "o.w.y" (non-absolute).
+    const Name n4("o.w.y.d.e.f");
+    LabelSequence ls4(n2);
+    ls4.stripRight(4);
+    EXPECT_EQ("o.w.y", ls4.toText());
+
+    result = dtree_expose_empty_node.find(ls4, &cdtnode, chain,
+                                          testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(n4, chain.getAbsoluteName());
+}
+
+TEST_F(DomainTreeTest, findInSubTreeSameLabelSequence) {
+    // For the version that takes a node chain, the chain must be empty.
+    DomainTreeNodeChain<int> chain;
+    bool flag;
+
+    const Name n1("c.g.h");
+
+    // First insert a "c.g.h." node.
+    dtree_expose_empty_node.insert(mem_sgmt_, n1, &dtnode);
+
+    /* Now, the tree looks like:
+     *
+     *             .
+     *             |
+     *             b
+     *           /   \
+     *          a    d.e.f
+     *              /  |  \____
+     *             c   |       \
+     *                 |        g.h
+     *                 |         |
+     *                w.y        i
+     *              /  |  \     / \
+     *             x   |   z   c   k
+     *                 |   |
+     *                 p   j
+     *               /   \
+     *              o     q
+     */
+
+    // Make a non-absolute label sequence. We will search for this same
+    // sequence in two places in the tree.
+    LabelSequence ls1(n1);
+    ls1.stripRight(3);
+    EXPECT_EQ("c", ls1.toText());
+
+    // First, find "g.h."
+    const Name n2("g.h");
+    const LabelSequence ls2(n2);
+    DomainTree<int>::Result result =
+        dtree_expose_empty_node.find(ls2, &cdtnode, chain,
+                                     testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(n2, chain.getAbsoluteName());
+
+    // Now, find "c.g.h." by searching just the non-absolute ls1 label
+    // sequence.
+    result = dtree_expose_empty_node.find(ls1, &cdtnode, chain,
+                                          testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(n1, chain.getAbsoluteName());
+
+    // Now, find "." (the root node)
+    chain.clear();
+    const Name n3(".");
+    const LabelSequence ls3(n3);
+    result =
+        dtree_expose_empty_node.find(ls3, &cdtnode, chain,
+                                     testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(n3, chain.getAbsoluteName());
+
+    // Now, find "c." by searching just the non-absolute ls1 label
+    // sequence.
+    result = dtree_expose_empty_node.find(ls1, &cdtnode, chain,
+                                          testCallback, &flag);
+    EXPECT_EQ(DomainTree<int>::EXACTMATCH, result);
+    EXPECT_EQ(Name("c."), chain.getAbsoluteName());
+}
+
 TEST_F(DomainTreeTest, chainLevel) {
 TEST_F(DomainTreeTest, chainLevel) {
     TestDomainTreeNodeChain chain;
     TestDomainTreeNodeChain chain;
 
 

+ 740 - 0
src/lib/datasrc/memory/tests/memory_client_unittest.cc

@@ -0,0 +1,740 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <util/memory_segment_local.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/masterload.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrsetlist.h>
+#include <dns/rrttl.h>
+#include <dns/masterload.h>
+
+#include <datasrc/result.h>
+#include <datasrc/data_source.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_table.h>
+#include <datasrc/memory/memory_client.h>
+
+#include <testutils/dnsmessage_test.h>
+
+#include <gtest/gtest.h>
+
+#include <new>                  // for bad_alloc
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc;
+using namespace isc::datasrc::memory;
+using namespace isc::testutils;
+
+namespace {
+// Memory segment specified for tests.  It normally behaves like a "local"
+// memory segment.  If "throw count" is set to non 0 via setThrowCount(),
+// it continues the normal behavior up to the specified number of calls to
+// allocate(), and throws an exception at the next call.
+class TestMemorySegment : public isc::util::MemorySegmentLocal {
+public:
+    TestMemorySegment() : throw_count_(0) {}
+    virtual void* allocate(size_t size) {
+        if (throw_count_ > 0) {
+            if (--throw_count_ == 0) {
+                throw std::bad_alloc();
+            }
+        }
+        return (isc::util::MemorySegmentLocal::allocate(size));
+    }
+    void setThrowCount(size_t count) { throw_count_ = count; }
+
+private:
+    size_t throw_count_;
+};
+
+const char* rrset_data[] = {
+    "example.org. 3600 IN SOA   ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600",
+    "a.example.org.		   	 3600 IN A	192.168.0.1",
+    "a.example.org.		   	 3600 IN MX	10 mail.example.org.",
+    NULL
+};
+
+class MockIterator : public ZoneIterator {
+private:
+    MockIterator() :
+        rrset_data_ptr_(rrset_data)
+    {
+    }
+
+    const char** rrset_data_ptr_;
+
+public:
+    virtual ConstRRsetPtr getNextRRset() {
+        if (*rrset_data_ptr_ == NULL) {
+             return (ConstRRsetPtr());
+        }
+
+        RRsetPtr result(textToRRset(*rrset_data_ptr_,
+                                    RRClass::IN(), Name("example.org")));
+        rrset_data_ptr_++;
+
+        return (result);
+    }
+
+    virtual ConstRRsetPtr getSOA() const {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+
+    static ZoneIteratorPtr makeIterator(void) {
+        return (ZoneIteratorPtr(new MockIterator()));
+    }
+};
+
+class MemoryClientTest : public ::testing::Test {
+protected:
+    MemoryClientTest() : zclass_(RRClass::IN()),
+                         client_(new InMemoryClient(mem_sgmt_, zclass_))
+    {}
+    ~MemoryClientTest() {
+        if (client_ != NULL) {
+            delete client_;
+        }
+    }
+    void TearDown() {
+        delete client_;
+        client_ = NULL;
+        EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
+    }
+    const RRClass zclass_;
+    TestMemorySegment mem_sgmt_;
+    InMemoryClient* client_;
+};
+
+TEST_F(MemoryClientTest, loadRRsetDoesntMatchOrigin) {
+    // Attempting to load example.org to example.com zone should result
+    // in an exception.
+    EXPECT_THROW(client_->load(Name("example.com"),
+                               TEST_DATA_DIR "/example.org-empty.zone"),
+                 MasterLoadError);
+}
+
+TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak1) {
+    // Attempting to load broken example.org zone should result in an
+    // exception. This should not leak ZoneData and other such
+    // allocations.
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR "/example.org-broken1.zone"),
+                 MasterLoadError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak2) {
+    // Attempting to load broken example.org zone should result in an
+    // exception. This should not leak ZoneData and other such
+    // allocations.
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR "/example.org-broken2.zone"),
+                 MasterLoadError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadNonExistentZoneFile) {
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR "/somerandomfilename"),
+                 MasterLoadError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadEmptyZoneFileThrows) {
+    // When an empty zone file is loaded, the origin doesn't even have
+    // an SOA RR. This condition should be avoided, and hence load()
+    // should throw when an empty zone is loaded.
+
+    EXPECT_EQ(0, client_->getZoneCount());
+
+    EXPECT_THROW(client_->load(Name("."),
+                               TEST_DATA_DIR "/empty.zone"),
+                 InMemoryClient::EmptyZone);
+
+    EXPECT_EQ(0, client_->getZoneCount());
+
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, load) {
+    // This is a simple load check for a "full" and correct zone that
+    // should not result in any exceptions.
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org.zone");
+}
+
+TEST_F(MemoryClientTest, loadFromIterator) {
+    client_->load(Name("example.org"),
+                  *MockIterator::makeIterator());
+
+    ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
+
+    // First we have the SOA
+    ConstRRsetPtr rrset(iterator->getNextRRset());
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::SOA(), rrset->getType());
+
+    // RRType::MX() RRset
+    rrset = iterator->getNextRRset();
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::MX(), rrset->getType());
+
+    // RRType::A() RRset
+    rrset = iterator->getNextRRset();
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+
+    // There's nothing else in this iterator
+    EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
+
+    // Iterating past the end should result in an exception
+    EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
+}
+
+TEST_F(MemoryClientTest, loadMemoryAllocationFailures) {
+    // Just to check that things get cleaned up
+
+    for (int i = 1; i < 16; i++) {
+        mem_sgmt_.setThrowCount(i);
+        EXPECT_THROW(client_->load(Name("example.org"),
+                                   TEST_DATA_DIR "/example.org.zone"),
+                     std::bad_alloc);
+    }
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadNSEC3Signed) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-nsec3-signed.zone");
+}
+
+TEST_F(MemoryClientTest, loadNSEC3SignedNoParam) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-nsec3-signed-no-param.zone");
+}
+
+TEST_F(MemoryClientTest, loadReloadZone) {
+    // Because we reload the same zone, also check that the zone count
+    // doesn't increase.
+    EXPECT_EQ(0, client_->getZoneCount());
+
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-empty.zone");
+    EXPECT_EQ(1, client_->getZoneCount());
+
+    // Reload zone with same data
+
+    client_->load(Name("example.org"),
+                  client_->getFileName(Name("example.org")));
+    EXPECT_EQ(1, client_->getZoneCount());
+
+    isc::datasrc::memory::ZoneTable::FindResult
+        result(client_->findZone2(Name("example.org")));
+    EXPECT_EQ(result::SUCCESS, result.code);
+    EXPECT_NE(static_cast<ZoneData*>(NULL),
+              result.zone_data);
+
+    /* Check SOA */
+    const ZoneNode* node = result.zone_data->getOriginNode();
+    EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
+
+    const RdataSet* set = node->getData();
+    EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+    EXPECT_EQ(RRType::SOA(), set->type);
+
+    set = set->getNext();
+    EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
+
+    /* Check ns1.example.org */
+    const ZoneTree& tree = result.zone_data->getZoneTree();
+    ZoneTree::Result zresult(tree.find(Name("ns1.example.org"), &node));
+    EXPECT_NE(ZoneTree::EXACTMATCH, zresult);
+
+    // Reload zone with different data
+
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-rrsigs.zone");
+    EXPECT_EQ(1, client_->getZoneCount());
+
+    isc::datasrc::memory::ZoneTable::FindResult
+        result2(client_->findZone2(Name("example.org")));
+    EXPECT_EQ(result::SUCCESS, result2.code);
+    EXPECT_NE(static_cast<ZoneData*>(NULL),
+              result2.zone_data);
+
+    /* Check SOA */
+    node = result2.zone_data->getOriginNode();
+    EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
+
+    set = node->getData();
+    EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+    EXPECT_EQ(RRType::SOA(), set->type);
+
+    set = set->getNext();
+    EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
+
+    /* Check ns1.example.org */
+    const ZoneTree& tree2 = result2.zone_data->getZoneTree();
+    ZoneTree::Result zresult2(tree2.find(Name("ns1.example.org"), &node));
+    EXPECT_EQ(ZoneTree::EXACTMATCH, zresult2);
+    EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
+
+    set = node->getData();
+    EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+    EXPECT_EQ(RRType::AAAA(), set->type);
+
+    set = set->getNext();
+    EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+    EXPECT_EQ(RRType::A(), set->type);
+
+    set = set->getNext();
+    EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
+
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadDuplicateType) {
+    // This should not result in any exceptions:
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-duplicate-type.zone");
+
+    // This should throw:
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-duplicate-type-bad.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadMultipleCNAMEThrows) {
+    // Multiple CNAME RRs should throw.
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-multiple-cname.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadMultipleDNAMEThrows) {
+    // Multiple DNAME RRs should throw.
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-multiple-dname.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadMultipleNSEC3Throws) {
+    // Multiple NSEC3 RRs should throw.
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-multiple-nsec3.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadMultipleNSEC3PARAMThrows) {
+    // Multiple NSEC3PARAM RRs should throw.
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-multiple-nsec3param.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadOutOfZoneThrows) {
+    // Out of zone names should throw.
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-out-of-zone.zone"),
+                 MasterLoadError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadWildcardNSThrows) {
+    // Wildcard NS names should throw
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-wildcard-ns.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadWildcardDNAMEThrows) {
+    // Wildcard DNAME names should throw
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-wildcard-dname.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadWildcardNSEC3Throws) {
+    // Wildcard NSEC3 names should throw
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-wildcard-nsec3.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadNSEC3WithFewerLabelsThrows) {
+    // NSEC3 names with labels != (origin_labels + 1) should throw
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-nsec3-fewer-labels.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadNSEC3WithMoreLabelsThrows) {
+    // NSEC3 names with labels != (origin_labels + 1) should throw
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-nsec3-more-labels.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadCNAMEAndNotNSECThrows) {
+    // CNAME and not NSEC should throw
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-cname-and-not-nsec-1.zone"),
+                 InMemoryClient::AddError);
+
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-cname-and-not-nsec-2.zone"),
+                 InMemoryClient::AddError);
+
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadDNAMEAndNSApex1) {
+    // DNAME + NS (apex) is OK
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR
+                  "/example.org-dname-ns-apex-1.zone");
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadDNAMEAndNSApex2) {
+    // DNAME + NS (apex) is OK (reverse order)
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR
+                  "/example.org-dname-ns-apex-2.zone");
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex1) {
+    // DNAME + NS (non-apex) must throw
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-dname-ns-nonapex-1.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
+    // DNAME + NS (non-apex) must throw (reverse order)
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-dname-ns-nonapex-2.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) {
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-rrsig-follows-nothing.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadRRSIGNameUnmatched) {
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-rrsig-name-unmatched.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadRRSIGTypeUnmatched) {
+    EXPECT_THROW(client_->load(Name("example.org"),
+                               TEST_DATA_DIR
+                               "/example.org-rrsig-type-unmatched.zone"),
+                 InMemoryClient::AddError);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, loadRRSIGs) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-rrsigs.zone");
+    EXPECT_EQ(1, client_->getZoneCount());
+}
+
+TEST_F(MemoryClientTest, loadRRSIGsRdataMixedCoveredTypes) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-rrsigs.zone");
+
+    RRsetPtr rrset(new RRset(Name("example.org"),
+                             RRClass::IN(), RRType::A(), RRTTL(3600)));
+    rrset->addRdata(in::A("192.0.2.1"));
+    rrset->addRdata(in::A("192.0.2.2"));
+
+    RRsetPtr rrsig(new RRset(Name("example.org"), zclass_,
+                             RRType::RRSIG(), RRTTL(300)));
+    rrsig->addRdata(generic::RRSIG("A 5 3 3600 20000101000000 20000201000000 "
+                                   "12345 example.org. FAKEFAKEFAKE"));
+    rrsig->addRdata(generic::RRSIG("NS 5 3 3600 20000101000000 20000201000000 "
+                                   "54321 example.org. FAKEFAKEFAKEFAKE"));
+    rrset->addRRsig(rrsig);
+
+    EXPECT_THROW(client_->add(Name("example.org"), rrset),
+                 InMemoryClient::AddError);
+
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, getZoneCount) {
+    EXPECT_EQ(0, client_->getZoneCount());
+    client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+    EXPECT_EQ(1, client_->getZoneCount());
+}
+
+TEST_F(MemoryClientTest, getFileNameForNonExistentZone) {
+    // Zone "example.org." doesn't exist
+    EXPECT_TRUE(client_->getFileName(Name("example.org.")).empty());
+}
+
+TEST_F(MemoryClientTest, getFileName) {
+    client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+    EXPECT_EQ(TEST_DATA_DIR "/example.org-empty.zone",
+              client_->getFileName(Name("example.org")));
+}
+
+TEST_F(MemoryClientTest, getIteratorForNonExistentZone) {
+    // Zone "." doesn't exist
+    EXPECT_THROW(client_->getIterator(Name(".")), DataSourceError);
+}
+
+TEST_F(MemoryClientTest, getIterator) {
+    client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+    ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
+
+    // First we have the SOA
+    ConstRRsetPtr rrset_soa(iterator->getNextRRset());
+    EXPECT_TRUE(rrset_soa);
+    EXPECT_EQ(RRType::SOA(), rrset_soa->getType());
+
+    // There's nothing else in this iterator
+    EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
+
+    // Iterating past the end should result in an exception
+    EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
+}
+
+TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-multiple.zone");
+
+    // separate_rrs = false
+    ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
+
+    // First we have the SOA
+    ConstRRsetPtr rrset(iterator->getNextRRset());
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::SOA(), rrset->getType());
+
+    // Only one RRType::A() RRset
+    rrset = iterator->getNextRRset();
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+
+    // There's nothing else in this zone
+    EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
+
+
+    // separate_rrs = true
+    ZoneIteratorPtr iterator2(client_->getIterator(Name("example.org"), true));
+
+    // First we have the SOA
+    rrset = iterator2->getNextRRset();
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::SOA(), rrset->getType());
+
+    // First RRType::A() RRset
+    rrset = iterator2->getNextRRset();
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+
+    // Second RRType::A() RRset
+    rrset = iterator2->getNextRRset();
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+
+    // There's nothing else in this iterator
+    EXPECT_EQ(ConstRRsetPtr(), iterator2->getNextRRset());
+}
+
+TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
+    client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+    ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
+
+    // This method is not implemented.
+    EXPECT_THROW(iterator->getSOA(), isc::NotImplemented);
+}
+
+TEST_F(MemoryClientTest, addRRsetToNonExistentZoneThrows) {
+    // The zone "example.org" doesn't exist, so we can't add an RRset to
+    // it.
+    RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
+                               RRTTL(300)));
+    rrset_a->addRdata(rdata::in::A("192.0.2.1"));
+    EXPECT_THROW(client_->add(Name("example.org"), rrset_a), DataSourceError);
+}
+
+TEST_F(MemoryClientTest, addOutOfZoneThrows) {
+    // Out of zone names should throw.
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-empty.zone");
+
+    RRsetPtr rrset_a(new RRset(Name("a.example.com"),
+                               RRClass::IN(), RRType::A(), RRTTL(300)));
+    rrset_a->addRdata(rdata::in::A("192.0.2.1"));
+
+    EXPECT_THROW(client_->add(Name("example.org"), rrset_a),
+                 OutOfZone);
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, addNullRRsetThrows) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-rrsigs.zone");
+
+    EXPECT_THROW(client_->add(Name("example.org"), ConstRRsetPtr()),
+                 InMemoryClient::NullRRset);
+
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, addEmptyRRsetThrows) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-rrsigs.zone");
+
+    RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
+                               RRTTL(300)));
+    EXPECT_THROW(client_->add(Name("example.org"), rrset_a),
+                 InMemoryClient::AddError);
+
+    // Teardown checks for memory segment leaks
+}
+
+TEST_F(MemoryClientTest, add) {
+    client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+
+    // Add another RRset
+    RRsetPtr rrset_a(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
+                               RRTTL(300)));
+    rrset_a->addRdata(rdata::in::A("192.0.2.1"));
+    client_->add(Name("example.org"), rrset_a);
+
+    ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
+
+    // First we have the SOA
+    ConstRRsetPtr rrset(iterator->getNextRRset());
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::A(), rrset->getType());
+
+    rrset = iterator->getNextRRset();
+    EXPECT_TRUE(rrset);
+    EXPECT_EQ(RRType::SOA(), rrset->getType());
+
+    // There's nothing else in this zone
+    EXPECT_EQ(ConstRRsetPtr(), iterator->getNextRRset());
+}
+
+TEST_F(MemoryClientTest, findZoneThrowsNotImplemented) {
+    // This method is not implemented.
+    EXPECT_THROW(client_->findZone(Name(".")),
+                 isc::NotImplemented);
+}
+
+TEST_F(MemoryClientTest, findZone2) {
+    client_->load(Name("example.org"),
+                  TEST_DATA_DIR "/example.org-rrsigs.zone");
+
+    isc::datasrc::memory::ZoneTable::FindResult
+        result(client_->findZone2(Name("example.com")));
+    EXPECT_EQ(result::NOTFOUND, result.code);
+    EXPECT_EQ(static_cast<ZoneData*>(NULL),
+              result.zone_data);
+
+    isc::datasrc::memory::ZoneTable::FindResult
+        result2(client_->findZone2(Name("example.org")));
+    EXPECT_EQ(result::SUCCESS, result2.code);
+    EXPECT_NE(static_cast<ZoneData*>(NULL),
+              result2.zone_data);
+
+    /* Check SOA */
+    const ZoneNode* node = result2.zone_data->getOriginNode();
+    EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
+
+    const RdataSet* set = node->getData();
+    EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+    EXPECT_EQ(RRType::SOA(), set->type);
+
+    set = set->getNext();
+    EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
+
+    /* Check ns1.example.org */
+    const ZoneTree& tree = result2.zone_data->getZoneTree();
+    ZoneTree::Result result3(tree.find(Name("ns1.example.org"), &node));
+    EXPECT_EQ(ZoneTree::EXACTMATCH, result3);
+    EXPECT_NE(static_cast<const ZoneNode*>(NULL), node);
+
+    set = node->getData();
+    EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+    EXPECT_EQ(RRType::AAAA(), set->type);
+
+    set = set->getNext();
+    EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+    EXPECT_EQ(RRType::A(), set->type);
+
+    set = set->getNext();
+    EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
+}
+
+TEST_F(MemoryClientTest, getUpdaterThrowsNotImplemented) {
+    // This method is not implemented.
+    EXPECT_THROW(client_->getUpdater(Name("."), false, false),
+                 isc::NotImplemented);
+}
+
+TEST_F(MemoryClientTest, getJournalReaderNotImplemented) {
+    // This method is not implemented.
+    EXPECT_THROW(client_->getJournalReader(Name("."), 0, 0),
+                 isc::NotImplemented);
+}
+}

+ 3 - 0
src/lib/datasrc/memory/tests/run_unittests.cc

@@ -14,10 +14,13 @@
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <util/unittests/run_all.h>
 #include <util/unittests/run_all.h>
+#include <log/logger_support.h>
 
 
 int
 int
 main(int argc, char* argv[]) {
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     ::testing::InitGoogleTest(&argc, argv);
 
 
+    isc::log::initLogger();
+
     return (isc::util::unittests::run_all());
     return (isc::util::unittests::run_all());
 }
 }

+ 32 - 0
src/lib/datasrc/memory/tests/testdata/Makefile.am

@@ -0,0 +1,32 @@
+CLEANFILES = *.copied
+
+EXTRA_DIST =  empty.zone
+EXTRA_DIST += example.org.zone
+EXTRA_DIST += example.org-empty.zone
+
+EXTRA_DIST += example.org-broken1.zone
+EXTRA_DIST += example.org-broken2.zone
+EXTRA_DIST += example.org-cname-and-not-nsec-1.zone
+EXTRA_DIST += example.org-cname-and-not-nsec-2.zone
+EXTRA_DIST += example.org-dname-ns-apex-1.zone
+EXTRA_DIST += example.org-dname-ns-apex-2.zone
+EXTRA_DIST += example.org-dname-ns-nonapex-1.zone
+EXTRA_DIST += example.org-dname-ns-nonapex-2.zone
+EXTRA_DIST += example.org-duplicate-type-bad.zone
+EXTRA_DIST += example.org-duplicate-type.zone
+EXTRA_DIST += example.org-multiple-cname.zone
+EXTRA_DIST += example.org-multiple-dname.zone
+EXTRA_DIST += example.org-multiple-nsec3.zone
+EXTRA_DIST += example.org-multiple-nsec3param.zone
+EXTRA_DIST += example.org-multiple.zone
+EXTRA_DIST += example.org-nsec3-fewer-labels.zone example.org-nsec3-more-labels.zone
+EXTRA_DIST += example.org-nsec3-signed-no-param.zone
+EXTRA_DIST += example.org-nsec3-signed.zone
+EXTRA_DIST += example.org-out-of-zone.zone
+EXTRA_DIST += example.org-rrsig-follows-nothing.zone
+EXTRA_DIST += example.org-rrsig-name-unmatched.zone
+EXTRA_DIST += example.org-rrsig-type-unmatched.zone
+EXTRA_DIST += example.org-rrsigs.zone
+EXTRA_DIST += example.org-wildcard-dname.zone
+EXTRA_DIST += example.org-wildcard-ns.zone
+EXTRA_DIST += example.org-wildcard-nsec3.zone

+ 0 - 0
src/lib/datasrc/memory/tests/testdata/empty.zone


+ 1 - 0
src/lib/datasrc/memory/tests/testdata/example.org-broken1.zone

@@ -0,0 +1 @@
+This is a broken zone that should not parse.

+ 5 - 0
src/lib/datasrc/memory/tests/testdata/example.org-broken2.zone

@@ -0,0 +1,5 @@
+;; broken example.org zone, where some RRs are OK, but others aren't
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 73 3600 300 3600000 3600
+ns1.example.org.		      3600 IN A		192.0.2.1
+ns2.example.org.		      3600 IN A		192.0.2.2
+ns2.a.example.com.		      3600 IN AAAA

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-cname-and-not-nsec-1.zone

@@ -0,0 +1,4 @@
+;; CNAME + other is an error
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091009 7200 3600 2592000 1200
+a.example.org.				      7200  IN A	192.168.0.1
+a.example.org.				      3600  IN CNAME	foo.example.com.

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-cname-and-not-nsec-2.zone

@@ -0,0 +1,4 @@
+;; CNAME + other is an error
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091007 7200 3600 2592000 1200
+a.example.org.				      3600  IN CNAME	foo.example.com.
+a.example.org.				      7200  IN A	192.168.0.1

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-apex-1.zone

@@ -0,0 +1,4 @@
+;; DNAME + NS (apex)
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091015 7200 3600 2592000 1200
+example.org.				      3600  IN DNAME	foo.example.com.
+example.org.				      3600  IN NS	bar.example.com.

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-apex-2.zone

@@ -0,0 +1,4 @@
+;; DNAME + NS (apex)
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091016 7200 3600 2592000 1200
+example.org.				      3600  IN NS	bar.example.com.
+example.org.				      3600  IN DNAME	foo.example.com.

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-nonapex-1.zone

@@ -0,0 +1,4 @@
+;; DNAME + NS (non-apex)
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091014 7200 3600 2592000 1200
+ns1.example.org.			      3600  IN DNAME	foo.example.com.
+ns1.example.org.			      3600  IN NS	bar.example.com.

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-dname-ns-nonapex-2.zone

@@ -0,0 +1,4 @@
+;; DNAME + NS (non-apex)
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091015 7200 3600 2592000 1200
+ns1.example.org.			      3600  IN NS	bar.example.com.
+ns1.example.org.			      3600  IN DNAME	foo.example.com.

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-duplicate-type-bad.zone

@@ -0,0 +1,4 @@
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 77 3600 300 3600000 3600
+ns1.example.org.		      3600 IN A	 	192.168.0.1
+ns1.example.org.		      3600 IN AAAA 	::1
+ns1.example.org.		      3600 IN A	 	192.168.0.2

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-duplicate-type.zone

@@ -0,0 +1,4 @@
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 76 3600 300 3600000 3600
+ns1.example.org.		      3600 IN A	 	192.168.0.1
+ns1.example.org.		      3600 IN A	 	192.168.0.2
+ns1.example.org.		      3600 IN AAAA 	::1

+ 2 - 0
src/lib/datasrc/memory/tests/testdata/example.org-empty.zone

@@ -0,0 +1,2 @@
+;; empty example.org zone
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600

+ 3 - 0
src/lib/datasrc/memory/tests/testdata/example.org-multiple-cname.zone

@@ -0,0 +1,3 @@
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+ns1.example.org.		      3600 IN CNAME	foo.example.com.
+ns1.example.org.		      3600 IN CNAME	bar.example.com.

+ 3 - 0
src/lib/datasrc/memory/tests/testdata/example.org-multiple-dname.zone

@@ -0,0 +1,3 @@
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 79 3600 300 3600000 3600
+ns1.example.org.		      3600 IN DNAME	foo.example.com.
+ns1.example.org.		      3600 IN DNAME	bar.example.com.

+ 3 - 0
src/lib/datasrc/memory/tests/testdata/example.org-multiple-nsec3.zone

@@ -0,0 +1,3 @@
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012090702 7200 3600 2592000 1200
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM

+ 3 - 0
src/lib/datasrc/memory/tests/testdata/example.org-multiple-nsec3param.zone

@@ -0,0 +1,3 @@
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012090700 7200 3600 2592000 1200
+example.org.				      0	IN NSEC3PARAM	1 0 10 AABBCCDD
+example.org.				      0	IN NSEC3PARAM	1 0 10 AABBCCDD

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-multiple.zone

@@ -0,0 +1,4 @@
+;; Multiple RDATA for testing separate RRs iterator
+example.org.  		   	 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+a.example.org.		   	 3600 IN A	192.168.0.1
+a.example.org.		   	 3600 IN A	192.168.0.2

+ 3 - 0
src/lib/datasrc/memory/tests/testdata/example.org-nsec3-fewer-labels.zone

@@ -0,0 +1,3 @@
+;; NSEC3 names with labels != (origin_labels + 1)
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091001 7200 3600 2592000 1200
+example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG

+ 3 - 0
src/lib/datasrc/memory/tests/testdata/example.org-nsec3-more-labels.zone

@@ -0,0 +1,3 @@
+;; NSEC3 names with labels != (origin_labels + 1)
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012091002 7200 3600 2592000 1200
+a.b.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG

+ 15 - 0
src/lib/datasrc/memory/tests/testdata/example.org-nsec3-signed-no-param.zone

@@ -0,0 +1,15 @@
+;; This file intentionally removes NSEC3PARAM from example.org.nsec3-signed
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
+example.org.				      86400 IN RRSIG	SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
+example.org.				      86400 IN NS	ns.example.org.
+example.org.				      86400 IN RRSIG	NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
+example.org.				      86400 IN DNSKEY	256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
+example.org.				      86400 IN RRSIG	DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
+;; example.org.				      0	IN NSEC3PARAM	1 0 10 AABBCCDD
+;; example.org.				      0	IN RRSIG	NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
+ns.example.org.				      86400 IN A	192.0.2.1
+ns.example.org.				      86400 IN RRSIG	A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=

+ 14 - 0
src/lib/datasrc/memory/tests/testdata/example.org-nsec3-signed.zone

@@ -0,0 +1,14 @@
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
+example.org.				      86400 IN RRSIG	SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
+example.org.				      86400 IN NS	ns.example.org.
+example.org.				      86400 IN RRSIG	NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
+example.org.				      86400 IN DNSKEY	256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
+example.org.				      86400 IN RRSIG	DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
+example.org.				      0	IN NSEC3PARAM	1 0 10 AABBCCDD
+example.org.				      0	IN RRSIG	NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
+ns.example.org.				      86400 IN A	192.0.2.1
+ns.example.org.				      86400 IN RRSIG	A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=

+ 5 - 0
src/lib/datasrc/memory/tests/testdata/example.org-out-of-zone.zone

@@ -0,0 +1,5 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 75 3600 300 3600000 3600
+a.example.com.	  		      3600 IN A	 	192.168.0.1

+ 5 - 0
src/lib/datasrc/memory/tests/testdata/example.org-rrsig-follows-nothing.zone

@@ -0,0 +1,5 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 68 3600 300 3600000 3600
+ns1.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE

+ 6 - 0
src/lib/datasrc/memory/tests/testdata/example.org-rrsig-name-unmatched.zone

@@ -0,0 +1,6 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 70 3600 300 3600000 3600
+ns1.example.org.		      3600 IN A		192.0.2.1
+ns2.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE

+ 6 - 0
src/lib/datasrc/memory/tests/testdata/example.org-rrsig-type-unmatched.zone

@@ -0,0 +1,6 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 72 3600 300 3600000 3600
+ns1.example.org.		      3600 IN AAAA	2001:db8::1
+ns1.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE

+ 8 - 0
src/lib/datasrc/memory/tests/testdata/example.org-rrsigs.zone

@@ -0,0 +1,8 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 74 3600 300 3600000 3600
+ns1.example.org.		      3600 IN A	 	192.168.0.1
+ns1.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org.		      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org.		      3600 IN AAAA 	::1

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-wildcard-dname.zone

@@ -0,0 +1,4 @@
+;; test zone file with wildcard DNAME names
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 79 3600 300 3600000 3600
+*.example.org. 3600 IN DNAME dname.example.com.

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-wildcard-ns.zone

@@ -0,0 +1,4 @@
+;; test zone file with wildcard NS names
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+*.example.org.			      3600 IN NS	ns1.example.org.

+ 4 - 0
src/lib/datasrc/memory/tests/testdata/example.org-wildcard-nsec3.zone

@@ -0,0 +1,4 @@
+;; test zone file with wildcard NS names
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 79 3600 300 3600000 3600
+*.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG

+ 81 - 0
src/lib/datasrc/memory/tests/testdata/example.org.zone

@@ -0,0 +1,81 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 67 3600 300 3600000 3600
+example.org.			      3600 IN NS	ns1.example.org.
+example.org.			      3600 IN NS	ns2.example.org.
+example.org.			      3600 IN MX	1 mx1.example.org.
+example.org.			      3600 IN MX	2 mx2.example.org.
+example.org.			      3600 IN MX	3 mx.a.example.org.
+
+ns1.example.org.		      3600 IN A		192.0.2.1
+ns1.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org.		      3600 IN AAAA	2001:db8::1
+ns1.example.org.		      3600 IN RRSIG	AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+ns2.example.org.		      3600 IN A		192.0.2.2
+ns2.example.org.		      3600 IN TXT	"text data"
+
+mx1.example.org.		      3600 IN A		192.0.2.10
+mx2.example.org.		      3600 IN AAAA	2001:db8::10
+
+;; delegation
+a.example.org.			      3600 IN NS	ns1.a.example.org.
+a.example.org.			      3600 IN NS	ns2.a.example.org.
+a.example.org.			      3600 IN NS	ns.example.com.
+
+ns1.a.example.org.		      3600 IN A		192.0.2.5
+ns2.a.example.org.		      3600 IN A		192.0.2.6
+ns2.a.example.org.		      3600 IN AAAA	2001:db8::6
+mx.a.example.org.		      3600 IN A		192.0.2.7
+
+;; delegation, one of its NS names is at zone cut.
+b.example.org.			      3600 IN NS	ns.b.example.org.
+b.example.org.			      3600 IN NS	b.example.org.
+b.example.org.			      3600 IN AAAA	2001:db8::8
+
+ns.b.example.org.		      3600 IN A		192.0.2.9
+
+;; The MX name is at a zone cut.  shouldn't be included in the
+;; additional section.
+mxatcut.example.org.		      3600 IN MX	1 b.example.org.
+
+;; delegation, one of its NS names is under a DNAME delegation point;
+;; another is at that point; and yet another is under DNAME below a
+;; zone cut.
+c.example.org. 	      	      3600 IN NS	ns.dname.example.org.
+c.example.org. 	      	      3600 IN NS	dname.example.org.
+c.example.org.      	      3600 IN NS	ns.deepdname.example.org.
+ns.dname.example.org.		      3600 IN A		192.0.2.11
+dname.example.org.		      3600 IN A		192.0.2.12
+ns.deepdname.example.org.	      3600 IN AAAA	2001:db8::9
+
+;; delegation, one of its NS name is at an empty non terminal.
+d.example.org. 	      	      3600 IN NS	ns.empty.example.org.
+d.example.org. 	      	      3600 IN NS	ns1.example.org.
+;; by adding these two we can create an empty RB node for
+;; ns.empty.example.org in the in-memory zone
+foo.ns.empty.example.org.     3600 IN A		192.0.2.13
+bar.ns.empty.example.org.     3600 IN A		192.0.2.14
+
+;; delegation; the NS name matches a wildcard (and there's no exact
+;; match).  One of the NS names matches an empty wildcard node, for
+;; which no additional record should be provided (or any other
+;; disruption should happen).
+e.example.org. 	      	      3600 IN NS	ns.wild.example.org.
+e.example.org. 	      	      3600 IN NS	ns.emptywild.example.org.
+e.example.org. 	      	      3600 IN NS	ns2.example.org.
+*.wild.example.org.	      3600 IN A		192.0.2.15
+a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
+
+;; additional for an answer RRset (MX) as a result of wildcard
+;; expansion
+*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
+
+;; CNAME
+alias.example.org. 3600 IN CNAME cname.example.org.
+
+;; DNAME
+dname.example.org. 3600 IN DNAME dname.example.com.
+
+;; DNAME under a NS (strange one)
+deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.

+ 9 - 8
src/lib/datasrc/memory/tests/treenode_rrset_unittest.cc

@@ -143,11 +143,13 @@ protected:
 void
 void
 checkBasicFields(const AbstractRRset& actual_rrset, const Name& expected_name,
 checkBasicFields(const AbstractRRset& actual_rrset, const Name& expected_name,
                  const RRClass& expected_class, const RRType& expected_type,
                  const RRClass& expected_class, const RRType& expected_type,
+                 const uint32_t expected_ttl,
                  size_t expected_rdatacount, size_t expected_sigcount)
                  size_t expected_rdatacount, size_t expected_sigcount)
 {
 {
     EXPECT_EQ(expected_name, actual_rrset.getName());
     EXPECT_EQ(expected_name, actual_rrset.getName());
     EXPECT_EQ(expected_class, actual_rrset.getClass());
     EXPECT_EQ(expected_class, actual_rrset.getClass());
     EXPECT_EQ(expected_type, actual_rrset.getType());
     EXPECT_EQ(expected_type, actual_rrset.getType());
+    EXPECT_EQ(RRTTL(expected_ttl), actual_rrset.getTTL());
     EXPECT_EQ(expected_rdatacount, actual_rrset.getRdataCount());
     EXPECT_EQ(expected_rdatacount, actual_rrset.getRdataCount());
     EXPECT_EQ(expected_sigcount, actual_rrset.getRRsigDataCount());
     EXPECT_EQ(expected_sigcount, actual_rrset.getRRsigDataCount());
 }
 }
@@ -176,30 +178,30 @@ createRRset(const Name& realname, const RRClass& rrclass, const ZoneNode* node,
 TEST_F(TreeNodeRRsetTest, create) {
 TEST_F(TreeNodeRRsetTest, create) {
     // Constructed with RRSIG, and it should be visible.
     // Constructed with RRSIG, and it should be visible.
     checkBasicFields(*createRRset(rrclass_, www_node_, a_rdataset_, true),
     checkBasicFields(*createRRset(rrclass_, www_node_, a_rdataset_, true),
-                     www_name_, rrclass_, RRType::A(), 2, 1);
+                     www_name_, rrclass_, RRType::A(), 3600, 2, 1);
     // Constructed with RRSIG, and it should be invisible.
     // Constructed with RRSIG, and it should be invisible.
     checkBasicFields(*createRRset(rrclass_, www_node_, a_rdataset_, false),
     checkBasicFields(*createRRset(rrclass_, www_node_, a_rdataset_, false),
-                     www_name_, rrclass_, RRType::A(), 2, 0);
+                     www_name_, rrclass_, RRType::A(), 3600, 2, 0);
     // Constructed without RRSIG, and it would be visible (but of course won't)
     // Constructed without RRSIG, and it would be visible (but of course won't)
     checkBasicFields(*createRRset(rrclass_, origin_node_, ns_rdataset_, true),
     checkBasicFields(*createRRset(rrclass_, origin_node_, ns_rdataset_, true),
-                     origin_name_, rrclass_, RRType::NS(), 1, 0);
+                     origin_name_, rrclass_, RRType::NS(), 3600, 1, 0);
     // Constructed without RRSIG, and it should be visible
     // Constructed without RRSIG, and it should be visible
     checkBasicFields(*createRRset(rrclass_, origin_node_, ns_rdataset_, false),
     checkBasicFields(*createRRset(rrclass_, origin_node_, ns_rdataset_, false),
-                     origin_name_, rrclass_, RRType::NS(), 1, 0);
+                     origin_name_, rrclass_, RRType::NS(), 3600, 1, 0);
     // RRSIG-only case (note the RRset's type is covered type)
     // RRSIG-only case (note the RRset's type is covered type)
     checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
     checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
                                   true),
                                   true),
-                     www_name_, rrclass_, RRType::TXT(), 0, 1);
+                     www_name_, rrclass_, RRType::TXT(), 3600, 0, 1);
     // RRSIG-only case (note the RRset's type is covered type), but it's
     // RRSIG-only case (note the RRset's type is covered type), but it's
     // invisible
     // invisible
     checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
     checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
                                   false),
                                   false),
-                     www_name_, rrclass_, RRType::TXT(), 0, 0);
+                     www_name_, rrclass_, RRType::TXT(), 3600, 0, 0);
     // Wildcard substitution
     // Wildcard substitution
     checkBasicFields(*createRRset(match_name_, rrclass_,
     checkBasicFields(*createRRset(match_name_, rrclass_,
                                   wildcard_node_, wildcard_rdataset_,
                                   wildcard_node_, wildcard_rdataset_,
                                   true),
                                   true),
-                     match_name_, rrclass_, RRType::A(), 2, 1);
+                     match_name_, rrclass_, RRType::A(), 3600, 2, 1);
 }
 }
 
 
 // The following two templated functions are helper to encapsulate the
 // The following two templated functions are helper to encapsulate the
@@ -572,7 +574,6 @@ TEST_F(TreeNodeRRsetTest, unexpectedMethods) {
 
 
     TreeNodeRRset rrset(rrclass_, www_node_, a_rdataset_, true);
     TreeNodeRRset rrset(rrclass_, www_node_, a_rdataset_, true);
 
 
-    EXPECT_THROW(rrset.getTTL(), isc::Unexpected);
     EXPECT_THROW(rrset.setTTL(RRTTL(0)), isc::Unexpected);
     EXPECT_THROW(rrset.setTTL(RRTTL(0)), isc::Unexpected);
     EXPECT_THROW(rrset.setName(Name("example")), isc::Unexpected);
     EXPECT_THROW(rrset.setName(Name("example")), isc::Unexpected);
     EXPECT_THROW(rrset.addRdata(createRdata(RRType::A(), rrclass_, "0.0.0.0")),
     EXPECT_THROW(rrset.addRdata(createRdata(RRType::A(), rrclass_, "0.0.0.0")),

File diff suppressed because it is too large
+ 1481 - 0
src/lib/datasrc/memory/tests/zone_finder_unittest.cc


+ 7 - 1
src/lib/datasrc/memory/treenode_rrset.cc

@@ -58,7 +58,13 @@ TreeNodeRRset::getName() const {
 
 
 const RRTTL&
 const RRTTL&
 TreeNodeRRset::getTTL() const {
 TreeNodeRRset::getTTL() const {
-    isc_throw(Unexpected, "unexpected method called on TreeNodeRRset");
+    if (ttl_ == NULL) {
+        util::InputBuffer ttl_buffer(rdataset_->getTTLData(),
+                                     sizeof(uint32_t));
+        ttl_ = new RRTTL(ttl_buffer);
+    }
+
+    return (*ttl_);
 }
 }
 
 
 void
 void

+ 7 - 4
src/lib/datasrc/memory/treenode_rrset.h

@@ -112,7 +112,7 @@ public:
                   const RdataSet* rdataset, bool dnssec_ok) :
                   const RdataSet* rdataset, bool dnssec_ok) :
         node_(node), rdataset_(rdataset),
         node_(node), rdataset_(rdataset),
         rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
         rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
-        dnssec_ok_(dnssec_ok), name_(NULL), realname_(NULL)
+        dnssec_ok_(dnssec_ok), name_(NULL), realname_(NULL), ttl_(NULL)
     {}
     {}
 
 
     /// \brief Constructor for wildcard-expanded owner name.
     /// \brief Constructor for wildcard-expanded owner name.
@@ -132,11 +132,13 @@ public:
                   bool dnssec_ok) :
                   bool dnssec_ok) :
         node_(node), rdataset_(rdataset),
         node_(node), rdataset_(rdataset),
         rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
         rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
-        dnssec_ok_(dnssec_ok), name_(NULL), realname_(new dns::Name(realname))
+        dnssec_ok_(dnssec_ok), name_(NULL), realname_(new dns::Name(realname)),
+	ttl_(NULL)
     {}
     {}
 
 
     virtual ~TreeNodeRRset() {
     virtual ~TreeNodeRRset() {
         delete realname_;
         delete realname_;
+        delete ttl_;
         delete name_;
         delete name_;
     }
     }
 
 
@@ -154,8 +156,6 @@ public:
     }
     }
 
 
     /// \brief Specialized version of \c getTTL() for \c TreeNodeRRset.
     /// \brief Specialized version of \c getTTL() for \c TreeNodeRRset.
-    ///
-    /// It throws \c isc::Unexpected unconditionally.
     virtual const dns::RRTTL& getTTL() const;
     virtual const dns::RRTTL& getTTL() const;
 
 
     /// \brief Specialized version of \c setName() for \c TreeNodeRRset.
     /// \brief Specialized version of \c setName() for \c TreeNodeRRset.
@@ -257,8 +257,11 @@ private:
     const bool dnssec_ok_;
     const bool dnssec_ok_;
     mutable dns::Name* name_;
     mutable dns::Name* name_;
     const dns::Name* const realname_;
     const dns::Name* const realname_;
+    mutable dns::RRTTL* ttl_;
 };
 };
 
 
+typedef boost::shared_ptr<TreeNodeRRset> TreeNodeRRsetPtr;
+
 } // namespace memory
 } // namespace memory
 } // namespace datasrc
 } // namespace datasrc
 } // namespace isc
 } // namespace isc

+ 1 - 0
src/lib/datasrc/memory/zone_data.h

@@ -43,6 +43,7 @@ namespace memory {
 
 
 typedef DomainTree<RdataSet> ZoneTree;
 typedef DomainTree<RdataSet> ZoneTree;
 typedef DomainTreeNode<RdataSet> ZoneNode;
 typedef DomainTreeNode<RdataSet> ZoneNode;
+typedef DomainTreeNodeChain<RdataSet> ZoneChain;
 
 
 /// \brief NSEC3 data for a DNS zone.
 /// \brief NSEC3 data for a DNS zone.
 ///
 ///

+ 600 - 0
src/lib/datasrc/memory/zone_finder.cc

@@ -0,0 +1,600 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/memory/zone_finder.h>
+#include <datasrc/memory/domaintree.h>
+#include <datasrc/memory/treenode_rrset.h>
+
+#include <datasrc/zone.h>
+#include <datasrc/data_source.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+#include <datasrc/logger.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+using namespace isc::datasrc;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+namespace {
+/// Creates a TreeNodeRRsetPtr for the given RdataSet at the given Node, for
+/// the given RRClass
+///
+/// We should probably have some pool so these  do not need to be allocated
+/// dynamically.
+///
+/// \param node The ZoneNode found by the find() calls
+/// \param rdataset The RdataSet to create the RRsetPtr for
+/// \param rrclass The RRClass as passed by the client
+/// \param realname If given, the TreeNodeRRset is created with this name
+///                 (e.g. for wildcard substitution)
+///
+/// Returns an empty TreeNodeRRsetPtr if node is NULL or if rdataset is NULL.
+TreeNodeRRsetPtr
+createTreeNodeRRset(const ZoneNode* node,
+                    const RdataSet* rdataset,
+                    const RRClass& rrclass,
+                    const Name* realname = NULL)
+{
+    if (node != NULL && rdataset != NULL) {
+        if (realname != NULL) {
+            return TreeNodeRRsetPtr(new TreeNodeRRset(*realname, rrclass, node,
+                                                      rdataset, true));
+        } else {
+            return TreeNodeRRsetPtr(new TreeNodeRRset(rrclass, node,
+                                                      rdataset, true));
+        }
+    } else {
+        return TreeNodeRRsetPtr();
+    }
+}
+
+/// Maintain intermediate data specific to the search context used in
+/// \c find().
+///
+/// It will be passed to \c cutCallback() (see below) and record a possible
+/// zone cut node and related RRset (normally NS or DNAME).
+struct FindState {
+    FindState(bool glue_ok) :
+        zonecut_node_(NULL),
+        dname_node_(NULL),
+        rrset_(NULL),
+        glue_ok_(glue_ok)
+    {}
+
+    // These will be set to a domain node of the highest delegation point,
+    // if any.  In fact, we could use a single variable instead of both.
+    // But then we would need to distinquish these two cases by something
+    // else and it seemed little more confusing when this was written.
+    const ZoneNode* zonecut_node_;
+    const ZoneNode* dname_node_;
+
+    // Delegation RRset (NS or DNAME), if found.
+    const RdataSet* rrset_;
+
+    // Whether to continue search below a delegation point.
+    // Set at construction time.
+    const bool glue_ok_;
+};
+
+// A callback called from possible zone cut nodes and nodes with DNAME.
+// This will be passed from findNode() to \c RBTree::find().
+bool cutCallback(const ZoneNode& node, FindState* state) {
+    // We need to look for DNAME first, there's allowed case where
+    // DNAME and NS coexist in the apex. DNAME is the one to notice,
+    // the NS is authoritative, not delegation (corner case explicitly
+    // allowed by section 3 of 2672)
+    const RdataSet* found_dname = RdataSet::find(node.getData(),
+                                                 RRType::DNAME());
+
+    if (found_dname != NULL) {
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_DNAME_ENCOUNTERED);
+        state->dname_node_ = &node;
+        state->rrset_ = found_dname;
+        return (true);
+    }
+
+    // Look for NS
+    const RdataSet* found_ns = RdataSet::find(node.getData(), RRType::NS());
+    if (found_ns != NULL) {
+        // We perform callback check only for the highest zone cut in the
+        // rare case of nested zone cuts.
+        if (state->zonecut_node_ != NULL) {
+            return (false);
+        }
+
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
+
+        // BIND 9 checks if this node is not the origin.  That's probably
+        // because it can support multiple versions for dynamic updates
+        // and IXFR, and it's possible that the callback is called at
+        // the apex and the DNAME doesn't exist for a particular version.
+        // It cannot happen for us (at least for now), so we don't do
+        // that check.
+        state->zonecut_node_ = &node;
+        state->rrset_ = found_ns;
+
+        // Unless glue is allowed the search stops here, so we return
+        // false; otherwise return true to continue the search.
+        return (!state->glue_ok_);
+    }
+
+    // This case should not happen because we enable callback only
+    // when we add an RR searched for above.
+    assert(0);
+    // This is here to avoid warning (therefore compilation error)
+    // in case assert is turned off. Otherwise we could get "Control
+    // reached end of non-void function".
+    return (false);
+}
+
+// convenience function to fill in the final details
+//
+// Set up ZoneFinderResultContext object as a return value of find(),
+// taking into account wildcard matches and DNSSEC information.  We set
+// the NSEC/NSEC3 flag when applicable regardless of the find option; the
+// caller would simply ignore these when they didn't request DNSSEC
+// related results.
+//
+// Also performs the conversion of node + RdataSet into a TreeNodeRRsetPtr
+//
+// if wild is true, the RESULT_WILDCARD flag will be set.
+// If qname is not NULL, this is the query name, to be used in wildcard
+// substitution instead of the Node's name).
+isc::datasrc::memory::ZoneFinderResultContext
+createFindResult(const RRClass& rrclass,
+                 const ZoneData& zone_data,
+                 ZoneFinder::Result code,
+                 const RdataSet* rrset,
+                 const ZoneNode* node,
+                 bool wild = false,
+                 const Name* qname = NULL) {
+    ZoneFinder::FindResultFlags flags = ZoneFinder::RESULT_DEFAULT;
+    const Name* rename = NULL;
+
+    if (wild) {
+        flags = flags | ZoneFinder::RESULT_WILDCARD;
+        // only use the rename qname if wild is true
+        rename = qname;
+    }
+    if (code == ZoneFinder::NXRRSET || code == ZoneFinder::NXDOMAIN || wild) {
+        if (zone_data.isNSEC3Signed()) {
+            flags = flags | ZoneFinder::RESULT_NSEC3_SIGNED;
+        } else if (zone_data.isSigned()) {
+            flags = flags | ZoneFinder::RESULT_NSEC_SIGNED;
+        }
+    }
+
+    return (ZoneFinderResultContext(code, createTreeNodeRRset(node, rrset,
+                                                              rrclass, rename),
+                                    flags, node));
+}
+
+// A helper function for NSEC-signed zones.  It searches the zone for
+// the "closest" NSEC corresponding to the search context stored in
+// node_path (it should contain sufficient information to identify the
+// previous name of the query name in the zone).  In some cases the
+// immediate closest name may not have NSEC (when it's under a zone cut
+// for glue records, or even when the zone is partly broken), so this
+// method continues the search until it finds a name that has NSEC,
+// and returns the one found first.  Due to the prerequisite (see below),
+// it should always succeed.
+//
+// node_path must store valid search context (in practice, it's expected
+// to be set by findNode()); otherwise the underlying RBTree implementation
+// throws.
+//
+// If the zone is not considered NSEC-signed or DNSSEC records were not
+// required in the original search context (specified in options), this
+// method doesn't bother to find NSEC, and simply returns NULL.  So, by
+// definition of "NSEC-signed", when it really tries to find an NSEC it
+// should succeed; there should be one at least at the zone origin.
+const RdataSet*
+getClosestNSEC(const ZoneData& zone_data,
+               ZoneChain& node_path,
+               const ZoneNode** nsec_node,
+               ZoneFinder::FindOptions options)
+{
+    if (!zone_data.isSigned() ||
+        (options & ZoneFinder::FIND_DNSSEC) == 0 ||
+        zone_data.isNSEC3Signed()) {
+        return (NULL);
+    }
+
+    const ZoneNode* prev_node;
+    while ((prev_node = zone_data.getZoneTree().previousNode(node_path))
+           != NULL) {
+        if (!prev_node->isEmpty()) {
+            const RdataSet* found =
+                RdataSet::find(prev_node->getData(), RRType::NSEC());
+            if (found != NULL) {
+                *nsec_node = prev_node;
+                return (found);
+            }
+        }
+    }
+    // This must be impossible and should be an internal bug.
+    // See the description at the method declaration.
+    assert(false);
+    // Even though there is an assert here, strict compilers
+    // will still need some return value.
+    return (NULL);
+}
+
+// A helper function for the NXRRSET case in find().  If the zone is
+// NSEC-signed and DNSSEC records are requested, try to find NSEC
+// on the given node, and return it if found; return NULL for all other
+// cases.
+const RdataSet*
+getNSECForNXRRSET(const ZoneData& zone_data,
+                  ZoneFinder::FindOptions options,
+                  const ZoneNode* node)
+{
+    if (zone_data.isSigned() &&
+        !zone_data.isNSEC3Signed() &&
+        (options & ZoneFinder::FIND_DNSSEC) != 0) {
+        const RdataSet* found = RdataSet::find(node->getData(),
+                                               RRType::NSEC());
+        if (found != NULL) {
+            return (found);
+        }
+    }
+    return (NULL);
+}
+
+// Structure to hold result data of the findNode() call
+class FindNodeResult {
+public:
+    // Bitwise flags to represent supplemental information of the
+    // search result:
+    // Search resulted in a wildcard match.
+    static const unsigned int FIND_WILDCARD = 1;
+    // Search encountered a zone cut due to NS but continued to look for
+    // a glue.
+    static const unsigned int FIND_ZONECUT = 2;
+
+    FindNodeResult(ZoneFinder::Result code_param,
+                   const ZoneNode* node_param,
+                   const RdataSet* rrset_param,
+                   unsigned int flags_param = 0) :
+        code(code_param),
+        node(node_param),
+        rrset(rrset_param),
+        flags(flags_param)
+    {}
+    const ZoneFinder::Result code;
+    const ZoneNode* node;
+    const RdataSet* rrset;
+    const unsigned int flags;
+};
+
+// Implementation notes: this method identifies an ZoneNode that best matches
+// the give name in terms of DNS query handling.  In many cases,
+// DomainTree::find() will result in EXACTMATCH or PARTIALMATCH (note that
+// the given name is generally expected to be contained in the zone, so
+// even if it doesn't exist, it should at least match the zone origin).
+// If it finds an exact match, that's obviously the best one.  The partial
+// match case is more complicated.
+//
+// We first need to consider the case where search hits a delegation point,
+// either due to NS or DNAME.  They are indicated as either dname_node_ or
+// zonecut_node_ being non NULL.  Usually at most one of them will be
+// something else than NULL (it might happen both are NULL, in which case we
+// consider it NOT FOUND). There's one corner case when both might be
+// something else than NULL and it is in case there's a DNAME under a zone
+// cut and we search in glue OK mode ‒ in that case we don't stop on the
+// domain with NS and ignore it for the answer, but it gets set anyway. Then
+// we find the DNAME and we need to act by it, therefore we first check for
+// DNAME and then for NS. In all other cases it doesn't matter, as at least
+// one of them is NULL.
+//
+// Next, we need to check if the ZoneTree search stopped at a node for a
+// subdomain of the search name (so the comparison result that stopped the
+// search is "SUPERDOMAIN"), it means the stopping node is an empty
+// non-terminal node.  In this case the search name is considered to exist
+// but no data should be found there.
+//
+// If none of above is the case, we then consider whether there's a matching
+// wildcard.  DomainTree::find() records the node if it encounters a
+// "wildcarding" node, i.e., the immediate ancestor of a wildcard name
+// (e.g., wild.example.com for *.wild.example.com), and returns it if it
+// doesn't find any node that better matches the query name.  In this case
+// we'll check if there's indeed a wildcard below the wildcarding node.
+//
+// Note, first, that the wildcard is checked after the empty
+// non-terminal domain case above, because if that one triggers, it
+// means we should not match according to 4.3.3 of RFC 1034 (the query
+// name is known to exist).
+//
+// Before we try to find a wildcard, we should check whether there's
+// an existing node that would cancel the wildcard match.  If
+// DomainTree::find() stopped at a node which has a common ancestor
+// with the query name, it might mean we are comparing with a
+// non-wildcard node. In that case, we check which part is common. If
+// we have something in common that lives below the node we got (the
+// one above *), then we should cancel the match according to section
+// 4.3.3 of RFC 1034 (as the name between the wildcard domain and the
+// query name is known to exist).
+//
+// If there's no node below the wildcarding node that shares a common ancestor
+// of the query name, we can conclude the wildcard is the best match.
+// We'll then identify the wildcard node via an incremental search.  Note that
+// there's no possibility that the query name is at an empty non terminal
+// node below the wildcarding node at this stage; that case should have been
+// caught above.
+//
+// If none of the above succeeds, we conclude the name doesn't exist in
+// the zone, and throw an OutOfZone exception.
+FindNodeResult findNode(const ZoneData& zone_data,
+                        const Name& name,
+                        ZoneChain& node_path,
+                        ZoneFinder::FindOptions options)
+{
+    ZoneNode* node = NULL;
+    FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
+
+    const ZoneTree& tree(zone_data.getZoneTree());
+    ZoneTree::Result result = tree.find(isc::dns::LabelSequence(name),
+                                        &node, node_path, cutCallback,
+                                        &state);
+    const unsigned int zonecut_flag =
+        (state.zonecut_node_ != NULL) ? FindNodeResult::FIND_ZONECUT : 0;
+    if (result == ZoneTree::EXACTMATCH) {
+        return (FindNodeResult(ZoneFinder::SUCCESS, node, state.rrset_,
+                               zonecut_flag));
+    } else if (result == ZoneTree::PARTIALMATCH) {
+        assert(node != NULL);
+        if (state.dname_node_ != NULL) { // DNAME
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
+                arg(state.dname_node_->getName());
+            return (FindNodeResult(ZoneFinder::DNAME, state.dname_node_,
+                                   state.rrset_));
+        }
+        if (state.zonecut_node_ != NULL) { // DELEGATION due to NS
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
+                arg(state.zonecut_node_->getName());
+            return (FindNodeResult(ZoneFinder::DELEGATION,
+                                   state.zonecut_node_,
+                                   state.rrset_));
+        }
+        if (node_path.getLastComparisonResult().getRelation() ==
+            NameComparisonResult::SUPERDOMAIN) { // empty node, so NXRRSET
+            LOG_DEBUG(logger, DBG_TRACE_DATA,
+                      DATASRC_MEM_SUPER_STOP).arg(name);
+            const ZoneNode* nsec_node;
+            const RdataSet* nsec_rds = getClosestNSEC(zone_data,
+                                                      node_path,
+                                                      &nsec_node,
+                                                      options);
+            return (FindNodeResult(ZoneFinder::NXRRSET, nsec_node,
+                                   nsec_rds));
+        }
+        // Nothing really matched.
+
+        // May be a wildcard, but check only if not disabled
+        if (node->getFlag(ZoneData::WILDCARD_NODE) &&
+            (options & ZoneFinder::NO_WILDCARD) == 0) {
+            if (node_path.getLastComparisonResult().getRelation() ==
+                NameComparisonResult::COMMONANCESTOR) {
+                // This means, e.g., we have *.wild.example and
+                // bar.foo.wild.example and are looking for
+                // baz.foo.wild.example. The common ancestor, foo.wild.example,
+                // should cancel wildcard.  Treat it as NXDOMAIN.
+                LOG_DEBUG(logger, DBG_TRACE_DATA,
+                          DATASRC_MEM_WILDCARD_CANCEL).arg(name);
+                    const ZoneNode* nsec_node;
+                    const RdataSet* nsec_rds = getClosestNSEC(zone_data,
+                                                              node_path,
+                                                              &nsec_node,
+                                                              options);
+                    return (FindNodeResult(ZoneFinder::NXDOMAIN, nsec_node,
+                                           nsec_rds));
+            }
+            uint8_t ls_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+
+            // Create the wildcard name (i.e. take "*" and extend it
+            // with all node labels down to the wildcard node
+            LabelSequence wildcard_ls(LabelSequence::WILDCARD(), ls_buf);
+            const ZoneNode* extend_with = node;
+            while (extend_with != NULL) {
+                wildcard_ls.extend(extend_with->getLabels(), ls_buf);
+                extend_with = extend_with->getUpperNode();
+            }
+
+            // Clear the node_path so that we don't keep incorrect (NSEC)
+            // context
+            node_path.clear();
+            ZoneTree::Result result = tree.find(LabelSequence(wildcard_ls),
+                                                &node, node_path, cutCallback,
+                                                &state);
+            // Otherwise, why would the domain_flag::WILD be there if
+            // there was no wildcard under it?
+            assert(result == ZoneTree::EXACTMATCH);
+            return (FindNodeResult(ZoneFinder::SUCCESS, node, state.rrset_,
+                        FindNodeResult::FIND_WILDCARD | zonecut_flag));
+        }
+
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).arg(name);
+        const ZoneNode* nsec_node;
+        const RdataSet* nsec_rds = getClosestNSEC(zone_data, node_path,
+                                                  &nsec_node, options);
+        return (FindNodeResult(ZoneFinder::NXDOMAIN, nsec_node, nsec_rds));
+    } else {
+        // If the name is neither an exact or partial match, it is
+        // out of bailiwick, which is considered an error.
+        isc_throw(OutOfZone, name.toText() << " not in " <<
+                             zone_data.getOriginNode()->getName());
+    }
+}
+
+} // end anonymous namespace
+
+// Specialization of the ZoneFinder::Context for the in-memory finder.
+class InMemoryZoneFinder::Context : public ZoneFinder::Context {
+public:
+    /// \brief Constructor.
+    ///
+    /// Note that we don't have a specific constructor for the findAll() case.
+    /// For (successful) type ANY query, found_node points to the
+    /// corresponding RB node, which is recorded within this specialized
+    /// context.
+    Context(ZoneFinder& finder, ZoneFinder::FindOptions options,
+            ZoneFinderResultContext result) :
+        ZoneFinder::Context(finder, options,
+                            ResultContext(result.code, result.rrset,
+                                          result.flags)),
+        rrset_(result.rrset), found_node_(result.found_node)
+    {}
+
+private:
+    const TreeNodeRRsetPtr rrset_;
+    const ZoneNode* const found_node_;
+};
+
+boost::shared_ptr<ZoneFinder::Context>
+InMemoryZoneFinder::find(const isc::dns::Name& name,
+                const isc::dns::RRType& type,
+                const FindOptions options)
+{
+    return ZoneFinderContextPtr(new Context(*this, options,
+                                            find_internal(name,
+                                                          type,
+                                                          NULL,
+                                                          options)));
+}
+
+boost::shared_ptr<ZoneFinder::Context>
+InMemoryZoneFinder::findAll(const isc::dns::Name& name,
+        std::vector<isc::dns::ConstRRsetPtr>& target,
+        const FindOptions options)
+{
+    return ZoneFinderContextPtr(new Context(*this, options,
+                                            find_internal(name,
+                                                          RRType::ANY(),
+                                                          &target,
+                                                          options)));
+}
+
+ZoneFinderResultContext
+InMemoryZoneFinder::find_internal(const isc::dns::Name& name,
+                                  const isc::dns::RRType& type,
+                                  std::vector<ConstRRsetPtr>* target,
+                                  const FindOptions options)
+{
+    // Get the node.  All other cases than an exact match are handled
+    // in findNode().  We simply construct a result structure and return.
+    ZoneChain node_path;
+    const FindNodeResult node_result =
+        findNode(zone_data_, name, node_path, options);
+    if (node_result.code != SUCCESS) {
+        return (createFindResult(rrclass_, zone_data_, node_result.code,
+                                 node_result.rrset, node_result.node));
+    }
+
+    const ZoneNode* node = node_result.node;
+    assert(node != NULL);
+
+    // We've found an exact match, may or may not be a result of wildcard.
+    const bool wild = ((node_result.flags &
+                        FindNodeResult::FIND_WILDCARD) != 0);
+
+    // If there is an exact match but the node is empty, it's equivalent
+    // to NXRRSET.
+    if (node->isEmpty()) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
+            arg(name);
+        const ZoneNode* nsec_node;
+        const RdataSet* nsec_rds = getClosestNSEC(zone_data_, node_path,
+                                                  &nsec_node, options);
+        return (createFindResult(rrclass_, zone_data_, NXRRSET,
+                                 nsec_rds,
+                                 nsec_node,
+                                 wild));
+    }
+
+    const RdataSet* found;
+
+    // If the node callback is enabled, this may be a zone cut.  If it
+    // has a NS RR, we should return a delegation, but not in the apex.
+    // There is one exception: the case for DS query, which should always
+    // be considered in-zone lookup.
+    if (node->getFlag(ZoneNode::FLAG_CALLBACK) &&
+            node != zone_data_.getOriginNode() && type != RRType::DS()) {
+        found = RdataSet::find(node->getData(), RRType::NS());
+        if (found != NULL) {
+            LOG_DEBUG(logger, DBG_TRACE_DATA,
+                      DATASRC_MEM_EXACT_DELEGATION).arg(name);
+            return (createFindResult(rrclass_, zone_data_, DELEGATION,
+                                     found, node, wild, &name));
+        }
+    }
+
+    // Handle type any query
+    if (target != NULL && node->getData() != NULL) {
+        // Empty domain will be handled as NXRRSET by normal processing
+        const RdataSet* cur_rds = node->getData();
+        while (cur_rds != NULL) {
+            target->push_back(createTreeNodeRRset(node, cur_rds, rrclass_,
+                                                  &name));
+            cur_rds = cur_rds->getNext();
+        }
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
+            arg(name);
+        return (createFindResult(rrclass_, zone_data_, SUCCESS, NULL, node,
+                                 wild, &name));
+    }
+
+    const RdataSet* currds = node->getData();
+    while (currds != NULL) {
+        currds = currds->getNext();
+    }
+    found = RdataSet::find(node->getData(), type);
+    if (found != NULL) {
+        // Good, it is here
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
+            arg(type);
+        return (createFindResult(rrclass_, zone_data_, SUCCESS, found, node,
+                                 wild, &name));
+    } else {
+        // Next, try CNAME.
+        found = RdataSet::find(node->getData(), RRType::CNAME());
+        if (found != NULL) {
+
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
+            return (createFindResult(rrclass_, zone_data_, CNAME, found, node,
+                                     wild, &name));
+        }
+    }
+    // No exact match or CNAME.  Get NSEC if necessary and return NXRRSET.
+    return (createFindResult(rrclass_, zone_data_, NXRRSET,
+                             getNSECForNXRRSET(zone_data_, options, node),
+                             node, wild, &name));
+}
+
+isc::datasrc::ZoneFinder::FindNSEC3Result
+InMemoryZoneFinder::findNSEC3(const isc::dns::Name& name, bool recursive) {
+    (void)name;
+    (void)recursive;
+    isc_throw(isc::NotImplemented, "not completed yet! please implement me");
+}
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc

+ 128 - 0
src/lib/datasrc/memory/zone_finder.h

@@ -0,0 +1,128 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_ZONE_FINDER_H
+#define DATASRC_MEMORY_ZONE_FINDER_H 1
+
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/treenode_rrset.h>
+
+#include <datasrc/zone.h>
+#include <dns/name.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+class ZoneFinderResultContext {
+public:
+    /// \brief Constructor
+    ///
+    /// The first three parameters correspond to those of
+    /// ZoneFinder::ResultContext.  If node is non NULL, it specifies the
+    /// found RBNode in the search.
+    ZoneFinderResultContext(ZoneFinder::Result code_param,
+                            TreeNodeRRsetPtr rrset_param,
+                            ZoneFinder::FindResultFlags flags_param,
+                            const ZoneNode* node) :
+        code(code_param), rrset(rrset_param), flags(flags_param),
+        found_node(node)
+    {}
+
+    const ZoneFinder::Result code;
+    const TreeNodeRRsetPtr rrset;
+    const ZoneFinder::FindResultFlags flags;
+    const ZoneNode* const found_node;
+};
+
+/// A derived zone finder class intended to be used with the memory data
+/// source, using ZoneData for its contents.
+class InMemoryZoneFinder : boost::noncopyable, public ZoneFinder {
+public:
+    /// \brief Constructor.
+    ///
+    /// Since ZoneData does not keep RRClass information, but this
+    /// information is needed in order to construct actual RRsets,
+    /// this needs to be passed here (the datasource client should
+    /// have this information). In the future, this may be replaced
+    /// by some construction to pull TreeNodeRRsets from a pool, but
+    /// currently, these are created dynamically with the given RRclass
+    ///
+    /// \param zone_data The ZoneData containing the zone.
+    /// \param rrclass The RR class of the zone
+    InMemoryZoneFinder(const ZoneData& zone_data,
+                       const isc::dns::RRClass& rrclass) :
+        zone_data_(zone_data),
+        rrclass_(rrclass)
+    {}
+
+    /// \brief Find an RRset in the datasource
+    virtual boost::shared_ptr<ZoneFinder::Context> find(
+        const isc::dns::Name& name,
+        const isc::dns::RRType& type,
+        const FindOptions options = FIND_DEFAULT);
+
+    /// \brief Version of find that returns all types at once
+    ///
+    /// It acts the same as find, just that when the correct node is found,
+    /// all the RRsets are filled into the target parameter instead of being
+    /// returned by the result.
+    virtual boost::shared_ptr<ZoneFinder::Context> findAll(
+        const isc::dns::Name& name,
+        std::vector<isc::dns::ConstRRsetPtr>& target,
+        const FindOptions options = FIND_DEFAULT);
+
+    /// Look for NSEC3 for proving (non)existence of given name.
+    ///
+    /// See documentation in \c Zone.
+    virtual FindNSEC3Result
+    findNSEC3(const isc::dns::Name& name, bool recursive);
+
+    /// \brief Returns the origin of the zone.
+    virtual isc::dns::Name getOrigin() const {
+        return zone_data_.getOriginNode()->getName();
+    }
+
+    /// \brief Returns the RR class of the zone.
+    virtual isc::dns::RRClass getClass() const {
+        return rrclass_;
+    }
+
+
+private:
+    /// \brief In-memory version of finder context.
+    ///
+    /// The implementation (and any specialized interface) is completely local
+    /// to the InMemoryZoneFinder class, so it's defined as private
+    class Context;
+
+    /// Actual implementation for both find() and findAll()
+    ZoneFinderResultContext find_internal(
+        const isc::dns::Name& name,
+        const isc::dns::RRType& type,
+        std::vector<isc::dns::ConstRRsetPtr>* target,
+        const FindOptions options =
+        FIND_DEFAULT);
+
+    const ZoneData& zone_data_;
+    const isc::dns::RRClass& rrclass_;
+};
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_ZONE_FINDER_H

+ 14 - 0
src/lib/datasrc/memory/zone_table.cc

@@ -132,6 +132,20 @@ ZoneTable::findZone(const Name& name) const {
     return (FindResult(my_result, node->getData()));
     return (FindResult(my_result, node->getData()));
 }
 }
 
 
+ZoneTable::FindResult
+ZoneTable::setZoneData(const Name& name, ZoneData* data)
+{
+    ZoneTableNode* node(NULL);
+
+    ZoneTableTree::Result result(zones_->find(name, &node));
+
+    if (result != ZoneTableTree::EXACTMATCH) {
+        return (FindResult(result::NOTFOUND, NULL));
+    } else {
+        return (FindResult(result::SUCCESS, node->setData(data)));
+    }
+}
+
 } // end of namespace memory
 } // end of namespace memory
 } // end of namespace datasrc
 } // end of namespace datasrc
 } // end of namespace isc
 } // end of namespace isc

+ 12 - 2
src/lib/datasrc/memory/zone_table.h

@@ -86,11 +86,11 @@ public:
     /// \brief Result data of findZone() method.
     /// \brief Result data of findZone() method.
     struct FindResult {
     struct FindResult {
         FindResult(result::Result param_code,
         FindResult(result::Result param_code,
-                   const ZoneData* param_zone_data) :
+                   ZoneData* param_zone_data) :
             code(param_code), zone_data(param_zone_data)
             code(param_code), zone_data(param_zone_data)
         {}
         {}
         const result::Result code;
         const result::Result code;
-        const ZoneData* const zone_data;
+        ZoneData* const zone_data;
     };
     };
 
 
 private:
 private:
@@ -185,6 +185,16 @@ public:
     /// \return A \c FindResult object enclosing the search result (see above).
     /// \return A \c FindResult object enclosing the search result (see above).
     FindResult findZone(const isc::dns::Name& name) const;
     FindResult findZone(const isc::dns::Name& name) const;
 
 
+    /// Override the ZoneData for a node (zone) in the zone tree.
+    ///
+    /// \throw none
+    ///
+    /// \param name A domain name for which the zone data is set.
+    /// \param data The new zone data to set.
+    /// \return A \c FindResult object containing the old data if the
+    /// zone was found.
+    FindResult setZoneData(const isc::dns::Name& name, ZoneData* data);
+
 private:
 private:
     boost::interprocess::offset_ptr<ZoneTableTree> zones_;
     boost::interprocess::offset_ptr<ZoneTableTree> zones_;
 };
 };

+ 3 - 0
src/lib/dhcp/Makefile.am

@@ -30,6 +30,9 @@ libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 
 
 libb10_dhcpsrv_la_SOURCES  = cfgmgr.cc cfgmgr.h
 libb10_dhcpsrv_la_SOURCES  = cfgmgr.cc cfgmgr.h
+libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
+libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
+libb10_dhcpsrv_la_SOURCES += triplet.h
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)

+ 42 - 8
src/lib/dhcp/addr_utilities.cc

@@ -14,46 +14,80 @@
 
 
 #include <dhcp/addr_utilities.h>
 #include <dhcp/addr_utilities.h>
 
 
+using namespace isc::asiolink;
+
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
 isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
 isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
-                                           uint8_t len) {
+                                            uint8_t len) {
 
 
     static char bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
     static char bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
-    uint8_t packed[16];
+    uint8_t packed[V6ADDRESS_LEN];
 
 
+    // First we copy the whole address as 16 bytes.
     memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
     memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
 
 
+    // If the length is divisible by 8, it is simple. We just zero out the host
+    // part. Otherwise we need to handle the byte that has to be partially
+    // zeroed.
     if (len % 8 != 0) {
     if (len % 8 != 0) {
+
+        // Get the appropriate mask. It has relevant bits (those that should
+        // stay) set and irrelevant (those that should be wiped) cleared.
         uint8_t mask = bitMask[len % 8];
         uint8_t mask = bitMask[len % 8];
+
+        // Let's leave only whatever the mask says should not be cleared.
         packed[len / 8] = packed[len / 8] & mask;
         packed[len / 8] = packed[len / 8] & mask;
-        len = (len/8 + 1) * 8;
+
+        // Since we have just dealt with this byte, let's move the prefix length
+        // to the beginning of the next byte (len is expressed in bits).
+        len = (len / 8 + 1) * 8;
     }
     }
-    for (int i = len / 8; i < 16; ++i) {
+
+    // Clear out the remaining bits.
+    for (int i = len / 8; i < sizeof(packed); ++i) {
         packed[i] = 0x0;
         packed[i] = 0x0;
     }
     }
 
 
+    // Finally, let's wrap this into nice and easy IOAddress object.
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
 }
 }
 
 
 isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
 isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
-                                          uint8_t len) {
+                                           uint8_t len) {
 
 
     static char bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
     static char bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
-    uint8_t packed[16];
+    uint8_t packed[V6ADDRESS_LEN];
 
 
+    // First we copy the whole address as 16 bytes.
     memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
     memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
 
 
+    // if the length is divisible by 8, it is simple. We just fill the host part
+    // with ones. Otherwise we need to handle the byte that has to be partially
+    // zeroed.
     if (len % 8 != 0) {
     if (len % 8 != 0) {
+        // Get the appropriate mask. It has relevant bits (those that should
+        // stay) set and irrelevant (those that should be set to 1) cleared.
         uint8_t mask = bitMask[len % 8];
         uint8_t mask = bitMask[len % 8];
+
+        // Let's set those irrelevant bits with 1. It would be perhaps
+        // easier to not use negation here and invert bitMask content. However,
+        // with this approach, we can use the same mask in first and last
+        // address calculations.
         packed[len / 8] = packed[len / 8] | ~mask;
         packed[len / 8] = packed[len / 8] | ~mask;
-        len = (len/8 + 1) * 8;
+
+        // Since we have just dealt with this byte, let's move the prefix length
+        // to the beginning of the next byte (len is expressed in bits).
+        len = (len / 8 + 1) * 8;
     }
     }
-    for (int i = len / 8; i < 16; ++i) {
+
+    // Finally set remaining bits to 1.
+    for (int i = len / 8; i < sizeof(packed); ++i) {
         packed[i] = 0xff;
         packed[i] = 0xff;
     }
     }
 
 
+    // Finally, let's wrap this into nice and easy IOAddress object.
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
 }
 }
 
 

+ 8 - 4
src/lib/dhcp/addr_utilities.h

@@ -18,32 +18,36 @@ namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
 /// This code is based on similar code from the Dibbler project. I, Tomasz Mrugalski,
 /// This code is based on similar code from the Dibbler project. I, Tomasz Mrugalski,
-/// as a sole creater of that code hereby release it under BSD license for the benefit
+/// as a sole creator of that code hereby release it under BSD license for the benefit
 /// of the BIND10 project.
 /// of the BIND10 project.
 
 
 /// @brief returns a first address in a given prefix
 /// @brief returns a first address in a given prefix
 ///
 ///
 /// Example: For 2001:db8:1::deaf:beef and length /120 the function will return
 /// Example: For 2001:db8:1::deaf:beef and length /120 the function will return
-/// 2001:db8:1::dead:bee0. See also @ref lastAddrInPrefix.
+/// 2001:db8:1::dead:be00. See also @ref lastAddrInPrefix.
+///
+/// @todo It currently works for v6 only and will throw if v4 address is passed.
 ///
 ///
 /// @param prefix and address that belongs to a prefix
 /// @param prefix and address that belongs to a prefix
 /// @param len prefix length
 /// @param len prefix length
 ///
 ///
 /// @return first address from a prefix
 /// @return first address from a prefix
 isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
 isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
-                                           uint8_t len);
+                                            uint8_t len);
 
 
 /// @brief returns a last address in a given prefix
 /// @brief returns a last address in a given prefix
 ///
 ///
 /// Example: For 2001:db8:1::deaf:beef and length /112 the function will return
 /// Example: For 2001:db8:1::deaf:beef and length /112 the function will return
 /// 2001:db8:1::dead:ffff. See also @ref firstAddrInPrefix.
 /// 2001:db8:1::dead:ffff. See also @ref firstAddrInPrefix.
 ///
 ///
+/// @todo It currently works for v6 only and will throw if v4 address is passed.
+///
 /// @param prefix and address that belongs to a prefix
 /// @param prefix and address that belongs to a prefix
 /// @param len prefix length
 /// @param len prefix length
 ///
 ///
 /// @return first address from a prefix
 /// @return first address from a prefix
 isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
 isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
-                                          uint8_t len);
+                                           uint8_t len);
 
 
 };
 };
 };
 };

+ 7 - 119
src/lib/dhcp/cfgmgr.cc

@@ -12,7 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-#include <dhcp/addr_utilities.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/cfgmgr.h>
 #include <dhcp/cfgmgr.h>
 
 
@@ -22,125 +21,7 @@ using namespace isc::util;
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-Pool::Pool(const isc::asiolink::IOAddress& first,
-           const isc::asiolink::IOAddress& last)
-    :id_(getNextID()), first_(first), last_(last) {
-}
-
-bool Pool::inRange(const isc::asiolink::IOAddress& addr) {
-    return ( first_.smallerEqual(addr) && addr.smallerEqual(last_) );
-}
-
-Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
-             const isc::asiolink::IOAddress& last)
-    :Pool(first, last), type_(type), prefix_len_(0) {
-
-    // check if specified address boundaries are sane
-    if (first.getFamily() != AF_INET6 || last.getFamily() != AF_INET6) {
-        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
-    }
-
-    if (last < first) {
-        isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
-        // This check is a bit strict. If we decide that it is too strict,
-        // we need to comment it and uncomment lines below.
-        // On one hand, letting the user specify 2001::f - 2001::1 is nice, but
-        // on the other hand, 2001::1 may be a typo and the user really meant
-        // 2001::1:0 (or 1something), so a at least a warning would be useful.
-
-        // first_  = last;
-        // last_ = first;
-    }
-
-    // TYPE_PD is not supported by this constructor. first-last style
-    // parameters are for IA and TA only. There is another dedicated
-    // constructor for that (it uses prefix/length)
-    if ((type != TYPE_IA) && (type != TYPE_TA)) {
-        isc_throw(BadValue, "Invalid Pool6 type specified");
-    }
-}
-
-Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
-             uint8_t prefix_len)
-    :Pool(prefix, IOAddress("::")),
-     type_(type), prefix_len_(prefix_len) {
-
-    // check if the prefix is sane
-    if (prefix.getFamily() != AF_INET6) {
-        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
-    }
 
 
-    // check if the prefix length is sane
-    if (prefix_len == 0 || prefix_len > 128) {
-        isc_throw(BadValue, "Invalid prefix length");
-    }
-
-    // Let's now calculate the last address in defined pool
-    last_ = lastAddrInPrefix(prefix, prefix_len);
-}
-
-Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
-               const Triplet<uint32_t>& t1,
-               const Triplet<uint32_t>& t2,
-               const Triplet<uint32_t>& valid_lifetime)
-    :id_(getNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
-     t2_(t2), valid_(valid_lifetime) {
-    if ( (prefix.getFamily() == AF_INET6 && len > 128) ||
-         (prefix.getFamily() == AF_INET && len > 32) ) {
-        isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
-    }
-}
-
-bool Subnet::inRange(const isc::asiolink::IOAddress& addr) {
-    IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
-    IOAddress last = lastAddrInPrefix(prefix_, prefix_len_);
-
-    return ( (first <= addr) && (addr <= last) );
-}
-
-Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
-                 const Triplet<uint32_t>& t1,
-                 const Triplet<uint32_t>& t2,
-                 const Triplet<uint32_t>& preferred_lifetime,
-                 const Triplet<uint32_t>& valid_lifetime)
-    :Subnet(prefix, length, t1, t2, valid_lifetime),
-     preferred_(preferred_lifetime){
-    if (prefix.getFamily() != AF_INET6) {
-        isc_throw(BadValue, "Invalid prefix " << prefix.toText()
-                  << " specified in subnet6");
-    }
-}
-
-void Subnet6::addPool6(const Pool6Ptr& pool) {
-    IOAddress first_addr = pool->getFirstAddress();
-    IOAddress last_addr = pool->getLastAddress();
-
-    if (!inRange(first_addr) || !inRange(last_addr)) {
-        isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
-                  << " does not belong in this (" << prefix_ << "/" << prefix_len_
-                  << ") subnet6");
-    }
-
-    pools_.push_back(pool);
-}
-
-Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
-    Pool6Ptr candidate;
-    for (Pool6Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
-
-        // if we won't find anything better, then let's just use the first pool
-        if (!candidate) {
-            candidate = *pool;
-        }
-
-        // if the client provided a pool and there's a pool that hint is valid in,
-        // then let's use that pool
-        if ((*pool)->inRange(hint)) {
-            return (*pool);
-        }
-    }
-    return (candidate);
-}
 
 
 
 
 CfgMgr&
 CfgMgr&
@@ -153,6 +34,13 @@ Subnet6Ptr
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 
 
     // If there's only one subnet configured, let's just use it
     // If there's only one subnet configured, let's just use it
+    // The idea is to keep small deployments easy. In a small network - one
+    // router that also runs DHCPv6 server. Users specifies a single pool and
+    // expects it to just work. Without this, the server would complain that it
+    // doesn't have IP address on its interfaces that matches that
+    // configuration. Such requirement makes sense in IPv4, but not in IPv6.
+    // The server does not need to have a global address (using just link-local
+    // is ok for DHCPv6 server) from the pool it serves.
     if (subnets6_.size() == 1) {
     if (subnets6_.size() == 1) {
         return (subnets6_[0]);
         return (subnets6_[0]);
     }
     }

+ 2 - 321
src/lib/dhcp/cfgmgr.h

@@ -23,331 +23,12 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
+#include <dhcp/pool.h>
+#include <dhcp/subnet.h>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-class Pool6;
-
-class Subnet6;
-
-/// @brief this template specifes a parameter value
-///
-/// This template class is used to store configuration parameters, like lifetime or T1.
-/// It defines 3 parameters: min, default, and max value. There are 2 constructors:
-/// - simple (just one value that sets all parameters)
-/// - extended (that sets default value and two thresholds)
-/// It will be used with integer types. It provides necessary operators, so
-/// it can be assigned to a plain integer or integer assigned to a Triplet.
-/// See TripletTest.operator test for details on an easy Triplet usage.
-template <class T>
-class Triplet {
-public:
-
-    /// @brief base type to Triple conversion
-    ///
-    /// Typically: uint32_t to Triplet assignment. It is very convenient
-    /// to be able to simply write Triplet<uint32_t> x = 7;
-    Triplet<T>& operator = (T base_type) {
-        return Triplet<T>(base_type);
-    }
-
-    /// @brief triplet to base type conversion
-    ///
-    /// Typically: Triplet to uint32_t assignment. It is very convenient
-    /// to be able to simply write uint32_t z = x; (where x is a Triplet)
-    operator T () const {
-        return (default_);
-    }
-
-    /// @brief sets a fixed value
-    ///
-    /// This constructor assigns a fixed (i.e. no range, just a single value)
-    /// value.
-    Triplet(T value)
-        :min_(value), default_(value), max_(value) {
-    }
-
-    /// @brief sets the default value and thresholds
-    ///
-    /// @throw BadValue if min <= def <= max rule is violated
-    Triplet(T min, T def, T max)
-        :min_(min), default_(def), max_(max) {
-        if ( (min_>def) || (def > max_) ) {
-            isc_throw(BadValue, "Invalid triplet values.");
-        }
-    }
-
-    /// @brief returns a minimum allowed value
-    T getMin() const { return min_;}
-
-    /// @brief returns the default value
-    T get() const { return default_;}
-
-    /// @brief returns value with a hint
-    ///
-    /// DHCP protocol treats any values sent by a client as hints.
-    /// This is a method that implements that. We can assign any value
-    /// from configured range that client asks.
-    T get(T hint) const {
-        if (hint <= min_) {
-            return (min_);
-        }
-
-        if (hint >= max_) {
-            return (max_);
-        }
-
-        return (hint);
-    }
-
-    /// @brief returns a maximum allowed value
-    T getMax() const { return max_; }
-
-protected:
-
-    /// @brief the minimum value
-    T min_;
-
-    /// @brief the default value
-    T default_;
-
-    /// @brief the maximum value
-    T max_;
-};
-
-
-/// @brief base class for Pool4 and Pool6
-///
-/// Stores information about pool of IPv4 or IPv6 addresses.
-/// That is a basic component of a configuration.
-class Pool {
-
-public:
-
-    /// @brief returns Pool-id
-    ///
-    /// Pool-id is an unique value that can be used to identify a pool.
-    uint32_t getId() const {
-        return (id_);
-    }
-
-    /// @brief Returns the first address in a pool.
-    ///
-    /// @return first address in a pool
-    const isc::asiolink::IOAddress& getFirstAddress() const {
-        return (first_);
-    }
-
-    /// @brief Returns the last address in a pool.
-    /// @return last address in a pool
-    const isc::asiolink::IOAddress& getLastAddress() const {
-        return (last_);
-    }
-
-    /// @brief Checks if a given address is in the range.
-    ///
-    /// @return true, if the address is in pool
-    bool inRange(const isc::asiolink::IOAddress& addr);
-
-protected:
-
-    /// @brief protected constructor
-    ///
-    /// This constructor is protected to prevent anyone from instantiating
-    /// Pool class directly. Instances of Pool4 and Pool6 should be created
-    /// instead.
-    Pool(const isc::asiolink::IOAddress& first,
-         const isc::asiolink::IOAddress& last);
-
-    /// @brief returns the next unique Pool-ID
-    ///
-    /// @return the next unique Pool-ID
-    static uint32_t getNextID() {
-        static uint32_t id = 0;
-        return (id++);
-    }
-
-    /// @brief pool-id
-    ///
-    /// This ID is used to indentify this specific pool.
-    uint32_t id_;
-
-    /// @brief The first address in a pool
-    isc::asiolink::IOAddress first_;
-
-    /// @brief The last address in a pool
-    isc::asiolink::IOAddress last_;
-
-    /// @brief Comments field
-    ///
-    /// @todo: This field is currently not used.
-    std::string comments_;
-};
-
-/// @brief Pool information for IPv6 addresses and prefixes
-///
-/// It holds information about pool6, i.e. a range of IPv6 address space that
-/// is configured for DHCP allocation.
-class Pool6 : public Pool {
-public:
-
-    /// @brief specifies Pool type
-    ///
-    /// Currently there are 3 pool types defined in DHCPv6:
-    /// - Non-temporary addresses (conveyed in IA_NA)
-    /// - Temporary addresses (conveyed in IA_TA)
-    /// - Delegated Prefixes (conveyed in IA_PD)
-    /// There is a new one being worked on (IA_PA, see draft-ietf-dhc-host-gen-id), but
-    /// support for it is not planned for now.
-    typedef enum {
-        TYPE_IA,
-        TYPE_TA,
-        TYPE_PD
-    }  Pool6Type;
-
-    /// @brief the constructor for Pool6 "min-max" style definition
-    ///
-    /// @param first the first address in a pool
-    /// @param last the last address in a pool
-    Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
-          const isc::asiolink::IOAddress& last);
-
-    /// @brief the constructor for Pool6 "prefix/len" style definition
-    ///
-    /// @param prefix specifies prefix of the pool
-    /// @param prefix_len specifies length of the prefix of the pool
-    Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
-          uint8_t prefix_len);
-
-    /// @brief returns pool type
-    ///
-    /// @return pool type
-    Pool6Type getType() const {
-        return (type_);
-    }
-
-protected:
-    /// @brief defines a pool type
-    Pool6Type type_;
-
-    /// @brief prefix length
-    /// used by TYPE_PD only (zeroed for other types)
-    uint8_t prefix_len_;
-};
-
-/// @brief a pointer an IPv6 Pool
-typedef boost::shared_ptr<Pool6> Pool6Ptr;
-
-/// @brief a container for IPv6 Pools
-typedef std::vector<Pool6Ptr> Pool6Collection;
-
-/// @brief a base class for Subnet4 and Subnet6
-///
-/// This class presents a common base for IPv4 and IPv6 subnets.
-/// In a physical sense, a subnet defines a single network link with all devices
-/// attached to it. In most cases all devices attached to a single link can
-/// share the same parameters. Therefore Subnet holds several values that are
-/// typically shared by all hosts: renew timer (T1), rebind timer (T2) and
-/// leased addresses lifetime (valid-lifetime).
-///
-/// @todo: Implement support for options here
-class Subnet {
-public:
-    /// @brief checks if specified address is in range
-    bool inRange(const isc::asiolink::IOAddress& addr);
-
-    /// @brief return valid-lifetime for addresses in that prefix
-    Triplet<uint32_t> getValid() const {
-        return (valid_);
-    }
-
-    /// @brief returns T1 (renew timer), expressed in seconds
-    Triplet<uint32_t> getT1() const {
-        return (t1_);
-    }
-
-    /// @brief returns T2 (rebind timer), expressed in seconds
-    Triplet<uint32_t> getT2() const {
-        return (t2_);
-    }
-
-protected:
-    /// @brief protected constructor
-    //
-    /// By making the constructor protected, we make sure that noone will
-    /// ever instantiate that class. Pool4 and Pool6 should be used instead.
-    Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
-           const Triplet<uint32_t>& t1,
-           const Triplet<uint32_t>& t2,
-           const Triplet<uint32_t>& valid_lifetime);
-
-    /// @brief returns the next unique Subnet-ID
-    ///
-    /// @return the next unique Subnet-ID
-    static uint32_t getNextID() {
-        static uint32_t id = 0;
-        return (id++);
-    }
-
-    /// @brief subnet-id
-    ///
-    /// Subnet-id is a unique value that can be used to find or identify
-    /// a Subnet4 or Subnet6.
-    uint32_t id_;
-
-    /// @brief a prefix of the subnet
-    isc::asiolink::IOAddress prefix_;
-
-    /// @brief a prefix length of the subnet
-    uint8_t prefix_len_;
-
-    /// @brief a tripet (min/default/max) holding allowed renew timer values
-    Triplet<uint32_t> t1_;
-
-    /// @brief a tripet (min/default/max) holding allowed rebind timer values
-    Triplet<uint32_t> t2_;
-
-    /// @brief a tripet (min/default/max) holding allowed valid lifetime values
-    Triplet<uint32_t> valid_;
-};
-
-/// @brief A configuration holder for IPv6 subnet.
-///
-/// This class represents an IPv6 subnet.
-class Subnet6 : public Subnet {
-public:
-    Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
-            const Triplet<uint32_t>& t1,
-            const Triplet<uint32_t>& t2,
-            const Triplet<uint32_t>& preferred_lifetime,
-            const Triplet<uint32_t>& valid_lifetime);
-
-    Triplet<uint32_t> getPreferred() const {
-        return (preferred_);
-    }
-
-    Pool6Ptr getPool6(const isc::asiolink::IOAddress& hint =
-                      isc::asiolink::IOAddress("::"));
-
-    void addPool6(const Pool6Ptr& pool);
-
-    const Pool6Collection& getPools() const {
-        return pools_;
-    }
-
-protected:
-    /// collection of pools in that list
-    Pool6Collection pools_;
-
-    Triplet<uint32_t> preferred_;
-};
-
-/// @brief A pointer to a Subnet6 object
-typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
-
-/// @brief A collection of Subnet6 objects
-typedef std::vector<Subnet6Ptr> Subnet6Collection;
 
 
 /// @brief Configuration Manager
 /// @brief Configuration Manager
 ///
 ///

+ 29 - 13
src/lib/dhcp/iface_mgr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -727,6 +727,13 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
     m.msg_control = &control_buf_[0];
     m.msg_control = &control_buf_[0];
     m.msg_controllen = control_buf_len_;
     m.msg_controllen = control_buf_len_;
     struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m);
     struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m);
+
+    // FIXME: Code below assumes that cmsg is not NULL, but
+    // CMSG_FIRSTHDR() is coded to return NULL as a possibility.  The
+    // following assertion should never fail, but if it did and you came
+    // here, fix the code. :)
+    assert(cmsg != NULL);
+
     cmsg->cmsg_level = IPPROTO_IPV6;
     cmsg->cmsg_level = IPPROTO_IPV6;
     cmsg->cmsg_type = IPV6_PKTINFO;
     cmsg->cmsg_type = IPV6_PKTINFO;
     cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
     cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
@@ -813,8 +820,12 @@ IfaceMgr::send(const Pkt4Ptr& pkt)
 
 
 
 
 boost::shared_ptr<Pkt4>
 boost::shared_ptr<Pkt4>
-IfaceMgr::receive4(uint32_t timeout) {
-
+IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
+    // Sanity check for microsecond timeout.
+    if (timeout_usec >= 1000000) {
+        isc_throw(BadValue, "fractional timeout must be shorter than"
+                  " one million microseconds");
+    }
     const SocketInfo* candidate = 0;
     const SocketInfo* candidate = 0;
     IfaceCollection::const_iterator iface;
     IfaceCollection::const_iterator iface;
     fd_set sockets;
     fd_set sockets;
@@ -854,13 +865,13 @@ IfaceMgr::receive4(uint32_t timeout) {
         names << session_socket_ << "(session)";
         names << session_socket_ << "(session)";
     }
     }
 
 
-    /// @todo: implement sub-second precision one day
     struct timeval select_timeout;
     struct timeval select_timeout;
-    select_timeout.tv_sec = timeout;
-    select_timeout.tv_usec = 0;
+    select_timeout.tv_sec = timeout_sec;
+    select_timeout.tv_usec = timeout_usec;
 
 
     cout << "Trying to receive data on sockets: " << names.str()
     cout << "Trying to receive data on sockets: " << names.str()
-         << ". Timeout is " << timeout << " seconds." << endl;
+         << ". Timeout is " << timeout_sec << "." << setw(6) << setfill('0')
+         << timeout_usec << " seconds." << endl;
     int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
     int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
     cout << "select returned " << result << endl;
     cout << "select returned " << result << endl;
 
 
@@ -983,7 +994,12 @@ IfaceMgr::receive4(uint32_t timeout) {
     return (pkt);
     return (pkt);
 }
 }
 
 
-Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
+Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
+    // Sanity check for microsecond timeout.
+    if (timeout_usec >= 1000000) {
+        isc_throw(BadValue, "fractional timeout must be shorter than"
+                  " one million microseconds");
+    }
 
 
     const SocketInfo* candidate = 0;
     const SocketInfo* candidate = 0;
     fd_set sockets;
     fd_set sockets;
@@ -1023,13 +1039,13 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
         names << session_socket_ << "(session)";
         names << session_socket_ << "(session)";
     }
     }
 
 
-    cout << "Trying to receive data on sockets:" << names.str()
-         << ".Timeout is " << timeout << " seconds." << endl;
+    cout << "Trying to receive data on sockets: " << names.str()
+         << ". Timeout is " << timeout_sec << "." << setw(6) << setfill('0')
+         << timeout_usec << " seconds." << endl;
 
 
-    /// @todo: implement sub-second precision one day
     struct timeval select_timeout;
     struct timeval select_timeout;
-    select_timeout.tv_sec = timeout;
-    select_timeout.tv_usec = 0;
+    select_timeout.tv_sec = timeout_sec;
+    select_timeout.tv_usec = timeout_usec;
 
 
     int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
     int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
 
 

+ 11 - 5
src/lib/dhcp/iface_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -364,10 +364,13 @@ public:
     /// to not wait infinitely, but rather do something useful
     /// to not wait infinitely, but rather do something useful
     /// (e.g. remove expired leases)
     /// (e.g. remove expired leases)
     ///
     ///
-    /// @param timeout specifies timeout (in seconds)
+    /// @param timeout_sec specifies integral part of the timeout (in seconds)
+    /// @param timeout_usec specifies fractional part of the timeout
+    /// (in microseconds)
     ///
     ///
+    /// @throw isc::BadValue if timeout_usec is greater than one million
     /// @return Pkt6 object representing received packet (or NULL)
     /// @return Pkt6 object representing received packet (or NULL)
-    Pkt6Ptr receive6(uint32_t timeout);
+    Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec = 0);
 
 
     /// @brief Tries to receive IPv4 packet over open IPv4 sockets.
     /// @brief Tries to receive IPv4 packet over open IPv4 sockets.
     ///
     ///
@@ -375,10 +378,13 @@ public:
     /// If reception is successful and all information about its sender
     /// If reception is successful and all information about its sender
     /// are obtained, Pkt4 object is created and returned.
     /// are obtained, Pkt4 object is created and returned.
     ///
     ///
-    /// @param timeout specifies timeout (in seconds)
+    /// @param timeout_sec specifies integral part of the timeout (in seconds)
+    /// @param timeout_usec specifies fractional part of the timeout
+    /// (in microseconds)
     ///
     ///
+    /// @throw isc::BadValue if timeout_usec is greater than one million
     /// @return Pkt4 object representing received packet (or NULL)
     /// @return Pkt4 object representing received packet (or NULL)
-    Pkt4Ptr receive4(uint32_t timeout);
+    Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec = 0);
 
 
     /// Opens UDP/IP socket and binds it to address, interface and port.
     /// Opens UDP/IP socket and binds it to address, interface and port.
     ///
     ///

+ 87 - 0
src/lib/dhcp/pool.cc

@@ -0,0 +1,87 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/io_address.h>
+#include <dhcp/addr_utilities.h>
+#include <dhcp/pool.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+Pool::Pool(const isc::asiolink::IOAddress& first,
+           const isc::asiolink::IOAddress& last)
+    :id_(getNextID()), first_(first), last_(last) {
+}
+
+bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
+    return (first_.smallerEqual(addr) && addr.smallerEqual(last_));
+}
+
+Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
+             const isc::asiolink::IOAddress& last)
+    :Pool(first, last), type_(type), prefix_len_(0) {
+
+    // check if specified address boundaries are sane
+    if (first.getFamily() != AF_INET6 || last.getFamily() != AF_INET6) {
+        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
+    }
+
+    if (last < first) {
+        isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
+        // This check is a bit strict. If we decide that it is too strict,
+        // we need to comment it and uncomment lines below.
+        // On one hand, letting the user specify 2001::f - 2001::1 is nice, but
+        // on the other hand, 2001::1 may be a typo and the user really meant
+        // 2001::1:0 (or 1 followed by some hex digit), so a at least a warning
+        // would be useful.
+
+        // first_  = last;
+        // last_ = first;
+    }
+
+
+    // TYPE_PD is not supported by this constructor. first-last style
+    // parameters are for IA and TA only. There is another dedicated
+    // constructor for that (it uses prefix/length)
+    if ((type != TYPE_IA) && (type != TYPE_TA)) {
+        isc_throw(BadValue, "Invalid Pool6 type specified");
+    }
+}
+
+Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
+             uint8_t prefix_len)
+    :Pool(prefix, IOAddress("::")),
+     type_(type), prefix_len_(prefix_len) {
+
+    // check if the prefix is sane
+    if (prefix.getFamily() != AF_INET6) {
+        isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
+    }
+
+    // check if the prefix length is sane
+    if (prefix_len == 0 || prefix_len > 128) {
+        isc_throw(BadValue, "Invalid prefix length");
+    }
+
+    /// @todo: We should probably implement checks against weird addresses
+    /// here, like ::, starting with fe80, starting with ff etc. .
+
+    // Let's now calculate the last address in defined pool
+    last_ = lastAddrInPrefix(prefix, prefix_len);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 0 - 0
src/lib/dhcp/pool.h


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