Parcourir la source

Merge branch 'trac2238' into trac2269

Tomek Mrugalski il y a 12 ans
Parent
commit
e3cc1803c6
100 fichiers modifiés avec 6223 ajouts et 766 suppressions
  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
-	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)
 
+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
 	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

+ 1 - 1
Makefile.am

@@ -30,7 +30,7 @@ endif
 
 check-valgrind-suppress:
 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
 else
 	@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/memory/Makefile
                  src/lib/datasrc/memory/tests/Makefile
+                 src/lib/datasrc/memory/tests/testdata/Makefile
                  src/lib/datasrc/memory/benchmarks/Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/datasrc/tests/testdata/Makefile

+ 2 - 1
doc/devel/mainpage.dox

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

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

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

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

@@ -12,7 +12,7 @@ endif
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 
-CLEANFILES = spec_config.h
+CLEANFILES  = *.gcno *.gcda spec_config.h dhcp4_messages.h dhcp4_messages.cc
 
 man_MANS = b10-dhcp4.8
 DISTCLEANFILES = $(man_MANS)
@@ -35,11 +35,20 @@ endif
 spec_config.h: 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
 
-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 += 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
 # Disable unused parameter warning caused by some of the
@@ -47,7 +56,7 @@ if USE_CLANGPP
 b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
 endif
 
-b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp4_LDADD  = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la

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

@@ -13,28 +13,30 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+
 #include <cassert>
 #include <iostream>
 
-#include <cc/session.h>
+#include <asiolink/asiolink.h>
 #include <cc/data.h>
-#include <exceptions/exceptions.h>
+#include <cc/session.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
-#include <util/buffer.h>
-#include <dhcp4/spec_config.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/spec_config.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::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 dhcp {
@@ -43,7 +45,8 @@ ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
 
 ConstElementPtr
 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,
                              "Thank you for sending config.");
     return (answer);
@@ -51,13 +54,14 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
 
 ConstElementPtr
 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 (ControlledDhcpv4Srv::server_) {
             ControlledDhcpv4Srv::server_->shutdown();
         } else {
-            cout << "Server not initialized yet or already shut down." << endl;
+            LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
             ConstElementPtr answer = isc::config::createAnswer(1,
                                      "Shutdown failure.");
             return (answer);
@@ -93,10 +97,9 @@ void ControlledDhcpv4Srv::establishSession() {
 
     /// @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());
-
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
                                           dhcp4ConfigHandler,
                                           dhcp4CommandHandler, false);
@@ -106,8 +109,8 @@ void ControlledDhcpv4Srv::establishSession() {
     /// control with the "select" model of the DHCP server.  This is
     /// fully explained in \ref dhcpv4Session.
     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);
 }
 

+ 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/iface_mgr.h>
 #include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/dhcp4_log.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option4_addrlst.h>
 
-using namespace std;
 using namespace isc;
-using namespace isc::dhcp;
 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
 // 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";
 
 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 {
-        // 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
         IfaceMgr::instance();
 
         /// @todo: instantiate LeaseMgr here once it is imlpemented.
-
         IfaceMgr::instance().openSockets4(port);
 
         setServerID();
+
     } 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;
         return;
     }
@@ -57,12 +58,11 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
 }
 
 Dhcpv4Srv::~Dhcpv4Srv() {
-    cout << "b10-dhcp4: DHCPv4 server terminating." << endl;
     IfaceMgr::instance().closeSockets();
 }
 
 void Dhcpv4Srv::shutdown() {
-    cout << "b10-dhcp4: DHCPv4 server shutdown." << endl;
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
     shutdown_ = true;
 }
 
@@ -79,39 +79,48 @@ Dhcpv4Srv::run() {
         if (query) {
             try {
                 query->unpack();
+
             } 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;
             }
+            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()) {
             case DHCPDISCOVER:
                 rsp = processDiscover(query);
                 break;
+
             case DHCPREQUEST:
                 rsp = processRequest(query);
                 break;
+
             case DHCPRELEASE:
                 processRelease(query);
                 break;
+
             case DHCPDECLINE:
                 processDecline(query);
                 break;
+
             case DHCPINFORM:
                 processInform(query);
                 break;
+
             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->getRemoteAddr().toText() == "0.0.0.0") {
                     rsp->setRemoteAddr(query->getRemoteAddr());
@@ -127,14 +136,15 @@ Dhcpv4Srv::run() {
                 rsp->setIface(query->getIface());
                 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()) {
-                    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) {
     /// TODO: Implement this.
-    cout << "Received RELEASE on " << release->getIface() << " interface." << endl;
 }
 
 void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
     /// TODO: Implement this.
-    cout << "Received DECLINE on " << decline->getIface() << " interface." << endl;
 }
 
 Pkt4Ptr Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
     /// TODO: Currently implemented echo mode. Implement this for real
     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:
     /// @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
     /// network interaction. Will instantiate lease manager, and load
     /// 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
     Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
 
-    /// @brief Destructor. Used during DHCPv6 service shutdown.
+    /// @brief Destructor. Used during DHCPv4 service shutdown.
     ~Dhcpv4Srv();
 
     /// @brief Main server processing loop.
@@ -70,6 +70,23 @@ class Dhcpv4Srv : public boost::noncopyable {
     /// @brief Instructs the server to shut down.
     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:
     /// @brief Processes incoming DISCOVER and returns response.
     ///
@@ -89,11 +106,11 @@ protected:
     /// is valid, not expired, not reserved, not used by other client and
     /// 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
     ///
-    /// @return ACK or NACK message
+    /// @return ACK or NAK message
     Pkt4Ptr processRequest(Pkt4Ptr& request);
 
     /// @brief Stub function that will handle incoming RELEASE messages.

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

@@ -14,13 +14,15 @@
 
 #include <config.h>
 #include <iostream>
-#include <log/dummylog.h>
-#include <log/logger_support.h>
-#include <dhcp4/ctrl_dhcp4_srv.h>
+
 #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 std;
 
 /// This file contains entry point (main() function) for standard DHCPv4 server
 /// component for BIND10 framework. It parses command-line arguments and
@@ -37,11 +39,10 @@ const char* const DHCP4_NAME = "b10-dhcp4";
 
 void
 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;
     exit(EXIT_FAILURE);
 }
@@ -50,20 +51,21 @@ usage() {
 int
 main(int argc, char* argv[]) {
     int ch;
-    bool verbose_mode = false; // should server be verbose?
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
                                          // 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) {
         switch (ch) {
         case 'v':
             verbose_mode = true;
-            isc::log::denabled = true;
             break;
+
         case 's':
             stand_alone = true;
             break;
+
         case 'p':
             try {
                 port_number = boost::lexical_cast<int>(optarg);
@@ -78,50 +80,46 @@ main(int argc, char* argv[]) {
                 usage();
             }
             break;
-        case ':':
+
         default:
             usage();
         }
     }
 
+    // Check for extraneous parameters.
+    if (argc > optind) {
+        usage();
+    }
+
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(DHCP4_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
                          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;
-
     try {
-
-        cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
-
-        /// @todo: pass verbose to the actul server once logging is implemented
         ControlledDhcpv4Srv server(port_number);
-
         if (!stand_alone) {
             try {
                 server.establishSession();
             } 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
                 // DHCP server in stand-alone mode, e.g. for testing
             }
         } else {
-            cout << "Skipping connection to the BIND10 msgq." << endl;
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
         }
-
         server.run();
+        LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
+
     } 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;
     }
 

+ 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 += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
-CLEANFILES = $(builddir)/interfaces.txt
+CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -47,9 +47,11 @@ if HAVE_GTEST
 TESTS += dhcp4_unittests
 
 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_srv_unittest.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
+nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
 
 if USE_CLANGPP
 # 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
 // purpose with or without fee is hereby granted, provided that the above
@@ -271,4 +271,36 @@ TEST_F(Dhcpv4SrvTest, processInform) {
     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

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

@@ -27,16 +27,27 @@ import fcntl
 
 class TestDhcpv4Daemon(unittest.TestCase):
     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
-        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):
         pass
 
     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
 
@@ -79,9 +90,9 @@ class TestDhcpv4Daemon(unittest.TestCase):
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
 
         # There's potential problem if b10-dhcp4 prints out more
-        # than 4k of text
+        # than 16kB of text
         try:
-            output = os.read(self.stdout_pipes[0], 4096)
+            output = os.read(self.stdout_pipes[0], 16384)
         except OSError:
             print("No data available from stdout")
             output = ""
@@ -91,7 +102,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
             output = ""
 
         try:
-            error = os.read(self.stderr_pipes[0], 4096)
+            error = os.read(self.stderr_pipes[0], 16384)
         except OSError:
             print("No data available on stderr")
             error = ""
@@ -128,13 +139,13 @@ class TestDhcpv4Daemon(unittest.TestCase):
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
 
         (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):
         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
         self.assertTrue(returncode != 0)
@@ -178,28 +189,19 @@ class TestDhcpv4Daemon(unittest.TestCase):
     def test_portnumber_nonroot(self):
         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):
         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__':
     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
+/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@
 
-CLEANFILES = spec_config.h
+CLEANFILES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
 
 man_MANS = b10-dhcp6.8
 DISTCLEANFILES = $(man_MANS)
@@ -37,11 +37,20 @@ endif
 spec_config.h: 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
 
-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 += 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
 # Disable unused parameter warning caused by some of the
@@ -49,7 +58,7 @@ if USE_CLANGPP
 b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
 endif
 
-b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp6_LDADD  = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la

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

@@ -13,28 +13,30 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+
 #include <cassert>
 #include <iostream>
 
-#include <cc/session.h>
+#include <asiolink/asiolink.h>
 #include <cc/data.h>
-#include <exceptions/exceptions.h>
+#include <cc/session.h>
 #include <cc/session.h>
 #include <config/ccsession.h>
-#include <util/buffer.h>
-#include <dhcp6/spec_config.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/dhcp6_log.h>
+#include <dhcp6/spec_config.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::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 dhcp {
@@ -43,7 +45,8 @@ ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
 
 ConstElementPtr
 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,
                              "Thank you for sending config.");
     return (answer);
@@ -51,13 +54,14 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
 
 ConstElementPtr
 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 (ControlledDhcpv6Srv::server_) {
             ControlledDhcpv6Srv::server_->shutdown();
         } else {
-            cout << "Server not initialized yet or already shut down." << endl;
+            LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
             ConstElementPtr answer = isc::config::createAnswer(1,
                                      "Shutdown failure.");
             return (answer);
@@ -93,10 +97,9 @@ void ControlledDhcpv6Srv::establishSession() {
 
     /// @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());
-
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
                                           dhcp6ConfigHandler,
                                           dhcp6CommandHandler, false);
@@ -106,8 +109,8 @@ void ControlledDhcpv6Srv::establishSession() {
     /// control with the "select" model of the DHCP server.  This is
     /// fully explained in \ref dhcpv6Session.
     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);
 }
 

+ 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 <time.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp6/dhcp6_log.h>
+#include <dhcp6/dhcp6_srv.h>
 #include <dhcp/dhcp6.h>
-#include <dhcp/pkt6.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 <asiolink/io_address.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/pkt6.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
 
-using namespace std;
 using namespace isc;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
+using namespace isc::dhcp;
 using namespace isc::util;
+using namespace std;
 
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const uint32_t HARDCODED_T1 = 1500; // in seconds
@@ -40,14 +42,14 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 
 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
     try {
 
         if (IfaceMgr::instance().countIfaces() == 0) {
-            cout << "Failed to detect any network interfaces. Aborting." << endl;
+            LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
             shutdown_ = true;
             return;
         }
@@ -59,7 +61,7 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
         /// @todo: instantiate LeaseMgr here once it is imlpemented.
 
     } 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;
         return;
     }
@@ -68,13 +70,11 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
 }
 
 Dhcpv6Srv::~Dhcpv6Srv() {
-    cout << "DHCPv6 Srv shutdown." << endl;
-
     IfaceMgr::instance().closeSockets();
 }
 
 void Dhcpv6Srv::shutdown() {
-    cout << "b10-dhcp6: DHCPv6 server shutdown." << endl;
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
     shutdown_ = true;
 }
 
@@ -89,42 +89,58 @@ bool Dhcpv6Srv::run() {
 
         if (query) {
             if (!query->unpack()) {
-                cout << "Failed to parse incoming packet" << endl;
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                          DHCP6_PACKET_PARSE_FAIL);
                 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()) {
             case DHCPV6_SOLICIT:
                 rsp = processSolicit(query);
                 break;
+
             case DHCPV6_REQUEST:
                 rsp = processRequest(query);
                 break;
+
             case DHCPV6_RENEW:
                 rsp = processRenew(query);
                 break;
+
             case DHCPV6_REBIND:
                 rsp = processRebind(query);
                 break;
+
             case DHCPV6_CONFIRM:
                 rsp = processConfirm(query);
                 break;
+
             case DHCPV6_RELEASE:
                 rsp = processRelease(query);
                 break;
+
             case DHCPV6_DECLINE:
                 rsp = processDecline(query);
                 break;
+
             case DHCPV6_INFORMATION_REQUEST:
                 rsp = processInfRequest(query);
                 break;
+
             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) {
                 rsp->setRemoteAddr(query->getRemoteAddr());
                 rsp->setLocalAddr(query->getLocalAddr());
@@ -132,14 +148,16 @@ bool Dhcpv6Srv::run() {
                 rsp->setLocalPort(DHCP6_SERVER_PORT);
                 rsp->setIndex(query->getIndex());
                 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()));
     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.
     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:
     /// @brief Processes incoming SOLICIT and returns response.
     ///

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

@@ -14,13 +14,15 @@
 
 #include <config.h>
 #include <iostream>
-#include <log/dummylog.h>
-#include <log/logger_support.h>
-#include <dhcp6/ctrl_dhcp6_srv.h>
+
 #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 std;
 
 /// This file contains entry point (main() function) for standard DHCPv6 server
 /// component for BIND10 framework. It parses command-line arguments and
@@ -37,11 +39,10 @@ const char* const DHCP6_NAME = "b10-dhcp6";
 
 void
 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;
     exit(EXIT_FAILURE);
 }
@@ -52,18 +53,19 @@ main(int argc, char* argv[]) {
     int ch;
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
                                          // useful for testing only.
+    bool stand_alone = false;  // Should be connect to BIND10 msgq?
     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) {
         switch (ch) {
         case 'v':
             verbose_mode = true;
-            isc::log::denabled = true;
             break;
+
         case 's':
             stand_alone = true;
             break;
+
         case 'p':
             try {
                 port_number = boost::lexical_cast<int>(optarg);
@@ -78,51 +80,45 @@ main(int argc, char* argv[]) {
                 usage();
             }
             break;
-        case ':':
+
         default:
             usage();
         }
     }
 
+    // Check for extraneous parameters.
+    if (argc > optind) {
+        usage();
+    }
+
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(DHCP6_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
                          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;
-
     try {
-
-        cout << "b10-dhcp6: Initiating DHCPv6 server operation." << endl;
-
-        /// @todo: pass verbose to the actual server once logging is implemented
         ControlledDhcpv6Srv server(port_number);
-
         if (!stand_alone) {
             try {
                 server.establishSession();
             } 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 
                 // DHCP server in stand-alone mode, e.g. for testing
             }
         } else {
-            cout << "Skipping connection to the BIND10 msgq." << endl;
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
         }
-
         server.run();
+        LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
 
     } 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;
     }
 

+ 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 += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
-CLEANFILES = $(builddir)/interfaces.txt
+CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -42,10 +42,13 @@ if HAVE_GTEST
 
 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 += 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
 # 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
 // 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
 }
 
+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):
     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
-        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):
         pass
 
     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
 
@@ -79,9 +90,9 @@ class TestDhcpv6Daemon(unittest.TestCase):
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
 
         # There's potential problem if b10-dhcp4 prints out more
-        # than 4k of text
+        # than 16k of text
         try:
-            output = os.read(self.stdout_pipes[0], 4096)
+            output = os.read(self.stdout_pipes[0], 16384)
         except OSError:
             print("No data available from stdout")
             output = ""
@@ -91,7 +102,7 @@ class TestDhcpv6Daemon(unittest.TestCase):
             output = ""
 
         try:
-            error = os.read(self.stderr_pipes[0], 4096)
+            error = os.read(self.stderr_pipes[0], 16384)
         except OSError:
             print("No data available on stderr")
             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("      not that is can bind sockets correctly. Please ignore binding errors.")
         (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):
         print("Check that specifying port number 0 is not allowed.")
@@ -180,27 +191,19 @@ class TestDhcpv6Daemon(unittest.TestCase):
     def test_portnumber_nonroot(self):
         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):
         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__':
     unittest.main()

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

@@ -153,6 +153,54 @@
 
   </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>
     <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
                                        isc.acl.dns.REQUEST_LOADER.load(
                                            [{"action": "ACCEPT"}]),
-                                       {})
+                                       {},
+                                       counter_xfrrej=self._counter_xfrrej,
+                                       counter_xfrreqdone=self._counter_xfrreqdone)
         self.set_request_type(RRType.AXFR()) # test AXFR by default
         self.mdata = self.create_request_data()
         self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
         # some test replaces a module-wide function.  We should ensure the
         # original is used elsewhere.
         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):
         xfrout.get_rrset_len = self.orig_get_rrset_len
@@ -458,7 +468,28 @@ class TestXfroutSession(TestXfroutSessionBase):
         # ACL checks only with the default ACL
         def acl_setter(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.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):
         # 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._acl = isc.acl.dns.REQUEST_LOADER.load([
                     {"from": "127.0.0.1", "action": "DROP"}])
+        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_zoneacl_nomatch(self):
         # 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([
                     {"from": "127.0.0.1", "action": "DROP"}])
             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_get_transfer_acl(self):
         # set the default ACL.  If there's no specific zone ACL, this one
@@ -831,9 +866,39 @@ class TestXfroutSession(TestXfroutSessionBase):
         def myreply(msg, sock):
             self.sock.send(b"success")
 
+        self.assertIsNone(self._zone_name_xfrreqdone)
         self.xfrsess._reply_xfrout_query = myreply
         self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
         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):
         self.xfrsess._soa = self.soa_rrset
@@ -1153,6 +1218,7 @@ class MyUnixSockServer(UnixSockServer):
         self._common_init()
         self._cc = MyCCSession()
         self.update_config_data(self._cc.get_full_config())
+        self._counters = {}
 
 class TestUnixSockServer(unittest.TestCase):
     def setUp(self):
@@ -1504,6 +1570,80 @@ class MyXfroutServer(XfroutServer):
         self._unix_socket_server = None
         # Disable the wait for threads
         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):
     def setUp(self):
@@ -1514,6 +1654,11 @@ class TestXfroutServer(unittest.TestCase):
         self.assertTrue(self.xfrout_server._notifier.shutdown_called)
         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__":
     isc.log.resetUnitTestRootLogger()
     unittest.main()

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

@@ -153,7 +153,8 @@ def get_soa_serial(soa_rdata):
 
 class XfroutSession():
     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._request_data = request_data
         self._server = server
@@ -168,6 +169,10 @@ class XfroutSession():
         self.ClientClass = client_class # parameterize this for testing
         self._soa = None # will be set in _xfrout_setup or in tests
         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()
 
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -270,6 +275,9 @@ class XfroutSession():
                          format_zone_str(zone_name, zone_class))
             return None, None
         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,
                          self._request_type, format_addrinfo(self._remote),
                          format_zone_str(zone_name, zone_class))
@@ -525,6 +533,9 @@ class XfroutSession():
         except Exception as err:
             logger.error(XFROUT_XFR_TRANSFER_ERROR, self._request_typestr,
                     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,
                     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.'''
 
     def __init__(self, sock_file, handle_class, shutdown_event, config_data,
-                 cc):
+                 cc, **counters):
         self._remove_unused_sock_file(sock_file)
         self._sock_file = sock_file
         socketserver_mixin.NoPollMixIn.__init__(self)
@@ -644,6 +655,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._common_init()
         self._cc = cc
         self.update_config_data(config_data)
+        # handlers for statistics use
+        self._counters = counters
 
     def _common_init(self):
         '''Initialization shared with the mock server class used for tests'''
@@ -798,7 +811,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
         self._lock.release()
         self.RequestHandlerClass(sock_fd, request_data, self,
                                  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):
         '''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._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:
     def __init__(self):
         self._unix_socket_server = None
@@ -933,6 +1048,8 @@ class XfroutServer:
         self._shutdown_event = threading.Event()
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._config_data = self._cc.get_full_config()
+        self._counter = XfroutCounter(
+            self._cc.get_module_spec().get_statistics_spec())
         self._cc.start()
         self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
         isc.server_common.tsig_keyring.init_keyring(self._cc)
@@ -941,17 +1058,25 @@ class XfroutServer:
 
     def _start_xfr_query_listener(self):
         '''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.start()
 
     def _start_notifier(self):
         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:
             for slave in self._config_data['also_notify']:
                 self._notifier.add_slave(slave['address'], slave['port'])
@@ -1027,6 +1152,15 @@ class XfroutServer:
             else:
                 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:
             answer = create_answer(1, "Unknown command:" + str(cmd))
 

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

@@ -114,6 +114,65 @@
             "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
 NOTIFY packets should be sent for the given zone.
 
+% XFROUT_RECEIVED_GETSTATS_COMMAND received command to get statistics data
+The xfrout daemon received a command on the command channel that
+statistics data should be sent to the stats daemon.
+
 % XFROUT_PARSE_QUERY_ERROR error parsing query: %1
 There was a parse error while reading an incoming query. The parse
 error is shown in the log message. A remote client sent a packet we

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

@@ -150,18 +150,15 @@ public:
     /// Operations within one protocol family are obvious.
     /// Comparisons between v4 and v6 will allways return v4
     /// 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
@@ -173,7 +170,7 @@ public:
         if (equals(other)) {
             return (true);
         }
-        return (smallerThan(other));
+        return (lessThan(other));
     }
 
     /// \brief Checks if one address is smaller than the other
@@ -182,7 +179,7 @@ public:
     ///
     /// See \ref smaller_than method for details.
     bool operator<(const IOAddress& other) const {
-        return (smallerThan(other));
+        return (lessThan(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");
 }
 
-TEST(IOAddressTest, compare) {
+TEST(IOAddressTest, lessThanEqual) {
     IOAddress addr1("192.0.2.5");
     IOAddress addr2("192.0.2.6");
     IOAddress addr3("0.0.0.0");

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

@@ -456,7 +456,7 @@ ModuleCCSession::ModuleCCSession(
 
     ConstElementPtr answer, env;
     session_.group_recvmsg(env, answer, false, seq);
-    int rcode;
+    int rcode = -1;
     ConstElementPtr err = parseAnswer(rcode, answer);
     if (rcode != 0) {
         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());
         // handle config update
         answer = config_handler_(diff);
-        int rcode;
+        int rcode = -1;
         parseAnswer(rcode, answer);
         if (rcode == 0) {
             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");
         ConstElementPtr env, answer;
         session_.group_recvmsg(env, answer, false, seq);
-        int rcode;
+        int rcode = -1;
         ConstElementPtr spec_data = parseAnswer(rcode, answer);
         if (rcode == 0 && spec_data) {
             // received OK, construct the spec out of it
@@ -689,7 +689,7 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_name,
 
     ConstElementPtr env, answer;
     session_.group_recvmsg(env, answer, false, seq);
-    int rcode;
+    int rcode = -1;
     ConstElementPtr new_config = parseAnswer(rcode, answer);
     ElementPtr local_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/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/log/libb10-log.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)
 
 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)
 
-CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
+CLEANFILES = *.gcno *.gcda
 
 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 += zone_data.h zone_data.cc
 libdatasrc_memory_la_SOURCES += segment_object_holder.h
+libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
+libdatasrc_memory_la_SOURCES += logger.h logger.cc
 libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
+libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
+nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.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
+/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
     /// 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
     /// 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),
                     CBARG callback_arg) const
 {
-    if (!node_path.isEmpty()) {
+    if (node_path.isEmpty() ^ target_labels_orig.isAbsolute()) {
         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;
     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_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
 AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -24,18 +27,23 @@ run_unittests_SOURCES += domaintree_unittest.cc
 run_unittests_SOURCES += treenode_rrset_unittest.cc
 run_unittests_SOURCES += zone_table_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 += segment_object_holder_unittest.cc
+run_unittests_SOURCES += memory_client_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 
 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/util/unittests/libutil_unittests.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/exceptions/libb10-exceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 run_unittests_LDADD += $(GTEST_LDADD)
 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);
 }
 
+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) {
     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 <util/unittests/run_all.h>
+#include <log/logger_support.h>
 
 int
 main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
 
+    isc::log::initLogger();
+
     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
 checkBasicFields(const AbstractRRset& actual_rrset, const Name& expected_name,
                  const RRClass& expected_class, const RRType& expected_type,
+                 const uint32_t expected_ttl,
                  size_t expected_rdatacount, size_t expected_sigcount)
 {
     EXPECT_EQ(expected_name, actual_rrset.getName());
     EXPECT_EQ(expected_class, actual_rrset.getClass());
     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_sigcount, actual_rrset.getRRsigDataCount());
 }
@@ -176,30 +178,30 @@ createRRset(const Name& realname, const RRClass& rrclass, const ZoneNode* node,
 TEST_F(TreeNodeRRsetTest, create) {
     // Constructed with RRSIG, and it should be visible.
     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.
     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)
     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
     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)
     checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
                                   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
     // invisible
     checkBasicFields(*createRRset(rrclass_, www_node_, rrsig_only_rdataset_,
                                   false),
-                     www_name_, rrclass_, RRType::TXT(), 0, 0);
+                     www_name_, rrclass_, RRType::TXT(), 3600, 0, 0);
     // Wildcard substitution
     checkBasicFields(*createRRset(match_name_, rrclass_,
                                   wildcard_node_, wildcard_rdataset_,
                                   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
@@ -572,7 +574,6 @@ TEST_F(TreeNodeRRsetTest, unexpectedMethods) {
 
     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.setName(Name("example")), isc::Unexpected);
     EXPECT_THROW(rrset.addRdata(createRdata(RRType::A(), rrclass_, "0.0.0.0")),

Fichier diff supprimé car celui-ci est trop grand
+ 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&
 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

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

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

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

@@ -43,6 +43,7 @@ namespace memory {
 
 typedef DomainTree<RdataSet> ZoneTree;
 typedef DomainTreeNode<RdataSet> ZoneNode;
+typedef DomainTreeNodeChain<RdataSet> ZoneChain;
 
 /// \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()));
 }
 
+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 datasrc
 } // end of namespace isc

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

@@ -86,11 +86,11 @@ public:
     /// \brief Result data of findZone() method.
     struct FindResult {
         FindResult(result::Result param_code,
-                   const ZoneData* param_zone_data) :
+                   ZoneData* param_zone_data) :
             code(param_code), zone_data(param_zone_data)
         {}
         const result::Result code;
-        const ZoneData* const zone_data;
+        ZoneData* const zone_data;
     };
 
 private:
@@ -185,6 +185,16 @@ public:
     /// \return A \c FindResult object enclosing the search result (see above).
     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:
     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_dhcpsrv_la_SOURCES  = cfgmgr.cc cfgmgr.h
+libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
+libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
+libb10_dhcpsrv_la_SOURCES += triplet.h
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)

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

@@ -14,46 +14,80 @@
 
 #include <dhcp/addr_utilities.h>
 
+using namespace isc::asiolink;
+
 namespace isc {
 namespace dhcp {
 
 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 };
-    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);
 
+    // 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) {
+
+        // 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];
+
+        // Let's leave only whatever the mask says should not be cleared.
         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;
     }
 
+    // Finally, let's wrap this into nice and easy IOAddress object.
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
 }
 
 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 };
-    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);
 
+    // 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) {
+        // 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];
+
+        // 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;
-        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;
     }
 
+    // Finally, let's wrap this into nice and easy IOAddress object.
     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 {
 
 /// 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.
 
 /// @brief returns a first address in a given prefix
 ///
 /// 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 len prefix length
 ///
 /// @return first address from a 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
 ///
 /// Example: For 2001:db8:1::deaf:beef and length /112 the function will return
 /// 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 len prefix length
 ///
 /// @return first address from a 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
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <dhcp/addr_utilities.h>
 #include <asiolink/io_address.h>
 #include <dhcp/cfgmgr.h>
 
@@ -22,125 +21,7 @@ using namespace isc::util;
 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) {
-    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&
@@ -153,6 +34,13 @@ Subnet6Ptr
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 
     // 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) {
         return (subnets6_[0]);
     }

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

@@ -23,331 +23,12 @@
 #include <asiolink/io_address.h>
 #include <util/buffer.h>
 #include <dhcp/option.h>
+#include <dhcp/pool.h>
+#include <dhcp/subnet.h>
 
 namespace isc {
 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
 ///

+ 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
 // 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_controllen = control_buf_len_;
     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_type = IPV6_PKTINFO;
     cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
@@ -813,8 +820,12 @@ IfaceMgr::send(const Pkt4Ptr& pkt)
 
 
 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;
     IfaceCollection::const_iterator iface;
     fd_set sockets;
@@ -854,13 +865,13 @@ IfaceMgr::receive4(uint32_t timeout) {
         names << session_socket_ << "(session)";
     }
 
-    /// @todo: implement sub-second precision one day
     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()
-         << ". 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);
     cout << "select returned " << result << endl;
 
@@ -983,7 +994,12 @@ IfaceMgr::receive4(uint32_t timeout) {
     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;
     fd_set sockets;
@@ -1023,13 +1039,13 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
         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;
-    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);
 

+ 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
 // 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
     /// (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)
-    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.
     ///
@@ -375,10 +378,13 @@ public:
     /// If reception is successful and all information about its sender
     /// 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)
-    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.
     ///

+ 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


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff