Browse Source

[trac1687]Merge branch 'master' into trac1687

merge in master; fix one conflict
Jeremy C. Reed 13 years ago
parent
commit
8958f15ce8
100 changed files with 3538 additions and 279 deletions
  1. 21 0
      AUTHORS
  2. 24 0
      ChangeLog
  3. 4 0
      Makefile.am
  4. 11 6
      compatcheck/Makefile.am
  5. 3 0
      configure.ac
  6. 1 1
      doc/Doxyfile
  7. 6 4
      doc/guide/Makefile.am
  8. 3 1
      doc/guide/bind10-guide.xml
  9. 3 2
      src/bin/auth/auth_srv.cc
  10. 24 0
      src/bin/bind10/bind10_src.py.in
  11. 1 0
      src/bin/bind10/tests/Makefile.am
  12. 35 0
      src/bin/bind10/tests/bind10_test.py.in
  13. 1 0
      src/bin/cmdctl/tests/Makefile.am
  14. 1 0
      src/bin/dbutil/tests/Makefile.am
  15. 433 29
      src/bin/ddns/ddns.py.in
  16. 19 5
      src/bin/ddns/ddns.spec
  17. 139 0
      src/bin/ddns/ddns_messages.mes
  18. 1 0
      src/bin/ddns/tests/Makefile.am
  19. 928 12
      src/bin/ddns/tests/ddns_test.py
  20. 6 0
      src/bin/dhcp4/Makefile.am
  21. 6 0
      src/bin/dhcp4/tests/Makefile.am
  22. 6 0
      src/bin/dhcp6/Makefile.am
  23. 7 0
      src/bin/dhcp6/tests/Makefile.am
  24. 1 0
      src/bin/stats/tests/Makefile.am
  25. 8 44
      src/bin/xfrin/xfrin.py.in
  26. 0 6
      src/bin/xfrin/xfrin_messages.mes
  27. 2 2
      src/lib/asiolink/io_endpoint.h
  28. 17 2
      src/lib/datasrc/Makefile.am
  29. 162 0
      src/lib/datasrc/client_list.cc
  30. 289 0
      src/lib/datasrc/client_list.h
  31. 6 3
      src/lib/datasrc/database.cc
  32. 1 1
      src/lib/datasrc/memory_datasrc.cc
  33. 12 0
      src/lib/datasrc/static.zone.pre
  34. 62 0
      src/lib/datasrc/static_datasrc_link.cc
  35. 2 0
      src/lib/datasrc/tests/Makefile.am
  36. 475 0
      src/lib/datasrc/tests/client_list_unittest.cc
  37. 55 1
      src/lib/datasrc/tests/database_unittest.cc
  38. 56 0
      src/lib/datasrc/tests/factory_unittest.cc
  39. 2 0
      src/lib/datasrc/tests/testdata/static.zone
  40. 12 0
      src/lib/dhcp/Makefile.am
  41. 8 0
      src/lib/dhcp/iface_mgr.cc
  42. 8 0
      src/lib/dhcp/option.cc
  43. 9 0
      src/lib/dhcp/option.h
  44. 5 0
      src/lib/dhcp/pkt4.cc
  45. 47 0
      src/lib/dhcp/pkt4.h
  46. 6 0
      src/lib/dhcp/pkt6.cc
  47. 47 0
      src/lib/dhcp/pkt6.h
  48. 9 2
      src/lib/dhcp/tests/Makefile.am
  49. 28 1
      src/lib/dhcp/tests/option_unittest.cc
  50. 31 1
      src/lib/dhcp/tests/pkt4_unittest.cc
  51. 27 0
      src/lib/dhcp/tests/pkt6_unittest.cc
  52. 2 2
      src/lib/dns/labelsequence.h
  53. 5 1
      src/lib/dns/message.cc
  54. 155 90
      src/lib/dns/python/message_python.cc
  55. 2 2
      src/lib/dns/python/name_python.cc
  56. 5 0
      src/lib/dns/python/pydnspp_common.h
  57. 1 0
      src/lib/dns/python/rdata_python.cc
  58. 2 2
      src/lib/dns/python/rrclass_python.cc
  59. 15 0
      src/lib/dns/python/tests/message_python_test.py
  60. 1 1
      src/lib/dns/rdata.cc
  61. 18 15
      src/lib/dns/rdata/any_255/tsig_250.cc
  62. 1 1
      src/lib/dns/rdata/ch_3/a_1.cc
  63. 1 1
      src/lib/dns/rdata/generic/dlv_32769.cc
  64. 1 1
      src/lib/dns/rdata/generic/dnskey_48.cc
  65. 1 1
      src/lib/dns/rdata/generic/ds_43.cc
  66. 1 1
      src/lib/dns/rdata/generic/hinfo_13.cc
  67. 1 1
      src/lib/dns/rdata/generic/nsec3_50.cc
  68. 1 1
      src/lib/dns/rdata/generic/nsec3param_51.cc
  69. 1 1
      src/lib/dns/rdata/generic/nsec_47.cc
  70. 1 1
      src/lib/dns/rdata/generic/opt_41.cc
  71. 1 1
      src/lib/dns/rdata/generic/ptr_12.cc
  72. 1 1
      src/lib/dns/rdata/generic/rrsig_46.cc
  73. 1 1
      src/lib/dns/rdata/generic/soa_6.cc
  74. 1 1
      src/lib/dns/rdata/generic/sshfp_44.cc
  75. 1 1
      src/lib/dns/rdata/hs_4/a_1.cc
  76. 1 1
      src/lib/dns/rdata/in_1/a_1.cc
  77. 1 1
      src/lib/dns/rdata/in_1/aaaa_28.cc
  78. 1 1
      src/lib/dns/rdata/in_1/dhcid_49.cc
  79. 6 6
      src/lib/dns/rdata/in_1/srv_33.cc
  80. 1 1
      src/lib/dns/rrclass.cc
  81. 4 4
      src/lib/dns/rrparamregistry-placeholder.cc
  82. 1 1
      src/lib/dns/rrttl.cc
  83. 1 1
      src/lib/dns/rrtype.cc
  84. 2 0
      src/lib/dns/tests/message_unittest.cc
  85. 1 0
      src/lib/log/Makefile.am
  86. 5 5
      src/lib/log/compiler/message.cc
  87. 7 0
      src/lib/log/logger.cc
  88. 23 0
      src/lib/log/logger.h
  89. 33 2
      src/lib/log/logger_impl.cc
  90. 16 2
      src/lib/log/logger_impl.h
  91. 8 0
      src/lib/log/logger_manager.cc
  92. 3 0
      src/lib/log/logger_unittest_support.cc
  93. 3 3
      src/lib/log/message_dictionary.cc
  94. 9 0
      src/lib/log/message_exception.h
  95. 4 0
      src/lib/log/tests/.gitignore
  96. 19 0
      src/lib/log/tests/Makefile.am
  97. 26 0
      src/lib/log/tests/log_test_messages.mes
  98. 9 1
      src/lib/log/tests/logger_example.cc
  99. 64 0
      src/lib/log/tests/logger_lock_test.cc
  100. 0 0
      src/lib/log/tests/logger_lock_test.sh.in

+ 21 - 0
AUTHORS

@@ -0,0 +1,21 @@
+Chen Zhengzhang
+Dmitriy Volodin
+Evan Hunt
+Haidong Wang
+Haikuo Zhang
+Han Feng
+Jelte Jansen
+Jeremy C. Reed
+Xie Jiagui
+Jin Jian
+JINMEI Tatuya
+Kazunori Fujiwara
+Michael Graff
+Michal Vaner
+Mukund Sivaraman
+Naoki Kambe
+Shane Kerr
+Shen Tingting
+Stephen Morris
+Yoshitaka Aharen
+Zhang Likun

+ 24 - 0
ChangeLog

@@ -1,3 +1,27 @@
+445.	[bug]*		jinmei
+	The pre-install check for older SQLite3 DB now refers to the DB
+	file with the prefix of DESTDIR.  This ensures that 'make install'
+	with specific DESTDIR works regardless of the version of the DB
+	file installed in the default path.
+	(Trac #1982, git 380b3e8ec02ef45555c0113ee19329fe80539f71)
+
+444.	[bug]		jinmei
+	libdatasrc: fixed ZoneFinder for database-based data sources so
+	that it handles type DS query correctly, i.e., treating it as
+	authoritative data even on a delegation point.
+	(Trac #1912, git 7130da883f823ce837c10cbf6e216a15e1996e5d)
+
+443.    [func]*		muks
+	The logger now uses a lockfile named `logger_lockfile' that is
+	created in the local state directory to mutually separate
+	individual logging operations from various processes. This is
+	done so that log messages from different processes don't mix
+	together in the middle of lines. The `logger_lockfile` is created
+	with file permission mode 0660. BIND 10's local state directory
+	should be writable and perhaps have g+s mode bit so that the
+	`logger_lockfile` can be opened by a group of processes.
+	(Trac #1704, git ad8d445dd0ba208107eb239405166c5c2070bd8b)
+
 442.	[func]		tomek
 442.	[func]		tomek
 	b10-dhcp4, b10-dhcp6: Both DHCP servers now accept -p parameter
 	b10-dhcp4, b10-dhcp6: Both DHCP servers now accept -p parameter
 	that can be used to specify listening port number. This capability
 	that can be used to specify listening port number. This capability

+ 4 - 0
Makefile.am

@@ -16,6 +16,8 @@ DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations
 # Use same --with-gtest flag if set
 # Use same --with-gtest flag if set
 DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG)
 DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG)
 
 
+dist_doc_DATA = AUTHORS COPYING ChangeLog README
+
 clean-cpp-coverage:
 clean-cpp-coverage:
 	@if [ $(USE_LCOV) = yes ] ; then \
 	@if [ $(USE_LCOV) = yes ] ; then \
 		$(LCOV) --directory . --zerocounters; \
 		$(LCOV) --directory . --zerocounters; \
@@ -409,3 +411,5 @@ EXTRA_DIST += ext/coroutine/coroutine.h
 
 
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = dns++.pc
 pkgconfig_DATA = dns++.pc
+
+CLEANFILES = $(abs_top_builddir)/logger_lockfile

+ 11 - 6
compatcheck/Makefile.am

@@ -1,12 +1,17 @@
-# We're going to abuse install-data-local for a pre-install check.
-# This is to be considered a short term hack and is expected to be removed
-# in a near future version.
+# We're going to abuse install-data-local for a pre-install check.  This may
+# not be the cleanest way to do this type of job, but that's the least ugly
+# one we've found.
+#
+# Note also that if any test needs to examine some file that has possibly
+# been installed before (e.g., older DB or configuration file), it should be
+# referenced with the prefix of DESTDIR.  Otherwise
+# 'make DESTDIR=/somewhere install' may not work.
 install-data-local:
 install-data-local:
-	if test -e $(localstatedir)/$(PACKAGE)/zone.sqlite3; then \
+	if test -e $(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3; then \
 		$(SHELL) $(top_builddir)/src/bin/dbutil/run_dbutil.sh --check \
 		$(SHELL) $(top_builddir)/src/bin/dbutil/run_dbutil.sh --check \
-		$(localstatedir)/$(PACKAGE)/zone.sqlite3 || \
+		$(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3 || \
 		(echo "\nSQLite3 DB file schema version is old.  " \
 		(echo "\nSQLite3 DB file schema version is old.  " \
 		"Please run: " \
 		"Please run: " \
 		"$(abs_top_builddir)/src/bin/dbutil/run_dbutil.sh --upgrade " \
 		"$(abs_top_builddir)/src/bin/dbutil/run_dbutil.sh --upgrade " \
-		"$(localstatedir)/$(PACKAGE)/zone.sqlite3";  exit 1) \
+		"$(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3"; exit 1) \
 	fi
 	fi

+ 3 - 0
configure.ac

@@ -1209,6 +1209,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/log/tests/destination_test.sh
            src/lib/log/tests/destination_test.sh
            src/lib/log/tests/init_logger_test.sh
            src/lib/log/tests/init_logger_test.sh
            src/lib/log/tests/local_file_test.sh
            src/lib/log/tests/local_file_test.sh
+           src/lib/log/tests/logger_lock_test.sh
            src/lib/log/tests/severity_test.sh
            src/lib/log/tests/severity_test.sh
            src/lib/log/tests/tempdir.h
            src/lib/log/tests/tempdir.h
            src/lib/util/python/mkpywrapper.py
            src/lib/util/python/mkpywrapper.py
@@ -1257,6 +1258,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/lib/log/tests/destination_test.sh
            chmod +x src/lib/log/tests/destination_test.sh
            chmod +x src/lib/log/tests/init_logger_test.sh
            chmod +x src/lib/log/tests/init_logger_test.sh
            chmod +x src/lib/log/tests/local_file_test.sh
            chmod +x src/lib/log/tests/local_file_test.sh
+           chmod +x src/lib/log/tests/logger_lock_test.sh
            chmod +x src/lib/log/tests/severity_test.sh
            chmod +x src/lib/log/tests/severity_test.sh
            chmod +x src/lib/util/python/mkpywrapper.py
            chmod +x src/lib/util/python/mkpywrapper.py
            chmod +x src/lib/util/python/gen_wiredata.py
            chmod +x src/lib/util/python/gen_wiredata.py
@@ -1314,6 +1316,7 @@ Developer:
   Google Tests:  $gtest_path
   Google Tests:  $gtest_path
   C++ Code Coverage: $USE_LCOV
   C++ Code Coverage: $USE_LCOV
   Python Code Coverage: $USE_PYCOVERAGE
   Python Code Coverage: $USE_PYCOVERAGE
+  Logger checks: $enable_logger_checks
   Generate Manuals:  $enable_man
   Generate Manuals:  $enable_man
 
 
 END
 END

+ 1 - 1
doc/Doxyfile

@@ -579,7 +579,7 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
     ../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6 ../src/lib/dhcp \
     ../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6 ../src/lib/dhcp \
-    ../src/bin/dhcp4 devel
+    ../src/bin/dhcp4 ../tests/tools/perfdhcp devel
 
 
 # This tag can be used to specify the character encoding of the source files
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 6 - 4
doc/guide/Makefile.am

@@ -2,13 +2,15 @@
 if ENABLE_MAN
 if ENABLE_MAN
 
 
 # generated documentation
 # generated documentation
-DOCS = bind10-messages.html bind10-guide.html bind10-guide.txt
+HTMLDOCS = bind10-guide.html bind10-messages.html
+DOCS = bind10-guide.txt
 
 
-doc_DATA = $(DOCS) bind10-guide.css
+dist_doc_DATA = $(DOCS)
+dist_html_DATA = $(HTMLDOCS) bind10-guide.css
 
 
 # TODO: okay to include the generated bind10-messages.xml in dist tarfile too?
 # TODO: okay to include the generated bind10-messages.xml in dist tarfile too?
-EXTRA_DIST = bind10-guide.xml bind10-messages.xml $(doc_DATA)
-CLEANFILES = $(DOCS) bind10-messages.xml
+EXTRA_DIST = bind10-guide.xml bind10-messages.xml
+CLEANFILES = $(HTMLDOCS) $(DOCS) bind10-messages.xml
 
 
 bind10-guide.html: bind10-guide.xml
 bind10-guide.html: bind10-guide.xml
 	xsltproc --novalid --xinclude --nonet \
 	xsltproc --novalid --xinclude --nonet \

+ 3 - 1
doc/guide/bind10-guide.xml

@@ -131,7 +131,9 @@
         and <command>b10-zonemgr</command> components require the
         and <command>b10-zonemgr</command> components require the
         libpython3 library and the Python _sqlite3.so module
         libpython3 library and the Python _sqlite3.so module
         (which is included with Python).
         (which is included with Python).
-        The Python module needs to be built for the corresponding Python 3.
+        The <command>b10-stats-httpd</command> component uses the
+        Python pyexpat.so module.
+        The Python modules need to be built for the corresponding Python 3.
       </para>
       </para>
 <!-- TODO: this will change ... -->
 <!-- TODO: this will change ... -->
 
 

+ 3 - 2
src/bin/auth/auth_srv.cc

@@ -390,8 +390,9 @@ private:
     AuthSrv* server_;
     AuthSrv* server_;
 };
 };
 
 
-AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client,
-                 BaseSocketSessionForwarder& ddns_forwarder)
+AuthSrv::AuthSrv(const bool use_cache,
+                 isc::xfr::AbstractXfroutClient& xfrout_client,
+                 isc::util::io::BaseSocketSessionForwarder& ddns_forwarder)
 {
 {
     impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
     impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
     checkin_ = new ConfigChecker(this);
     checkin_ = new ConfigChecker(this);

+ 24 - 0
src/bin/bind10/bind10_src.py.in

@@ -64,6 +64,7 @@ import posix
 import copy
 import copy
 
 
 from bind10_config import LIBEXECPATH
 from bind10_config import LIBEXECPATH
+import bind10_config
 import isc.cc
 import isc.cc
 import isc.util.process
 import isc.util.process
 import isc.net.parse
 import isc.net.parse
@@ -1122,6 +1123,28 @@ def unlink_pid_file(pid_file):
         if error.errno is not errno.ENOENT:
         if error.errno is not errno.ENOENT:
             raise
             raise
 
 
+def remove_lock_files():
+    """
+    Remove various lock files which were created by code such as in the
+    logger. This function should be called after BIND 10 shutdown.
+    """
+
+    lockfiles = ["logger_lockfile"]
+
+    lpath = bind10_config.DATA_PATH
+    if "B10_FROM_BUILD" in os.environ:
+        lpath = os.environ["B10_FROM_BUILD"]
+    if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+        lpath = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+    if "B10_LOCKFILE_DIR_FROM_BUILD" in os.environ:
+        lpath = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"]
+
+    for f in lockfiles:
+        fname = lpath + '/' + f
+        if os.path.isfile(fname):
+            os.unlink(fname)
+
+    return
 
 
 def main():
 def main():
     global options
     global options
@@ -1201,6 +1224,7 @@ def main():
     finally:
     finally:
         # Clean up the filesystem
         # Clean up the filesystem
         unlink_pid_file(options.pid_file)
         unlink_pid_file(options.pid_file)
+        remove_lock_files()
         if boss_of_bind is not None:
         if boss_of_bind is not None:
             boss_of_bind.remove_socket_srv()
             boss_of_bind.remove_socket_srv()
     sys.exit(boss_of_bind.exitcode)
     sys.exit(boss_of_bind.exitcode)

+ 1 - 0
src/bin/bind10/tests/Makefile.am

@@ -23,6 +23,7 @@ endif
 	chmod +x $(abs_builddir)/$$pytest ; \
 	chmod +x $(abs_builddir)/$$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 		$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 		$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done
 	done

+ 35 - 0
src/bin/bind10/tests/bind10_test.py.in

@@ -1463,6 +1463,41 @@ class SocketSrvTest(unittest.TestCase):
         self.assertEqual({}, self.__boss._unix_sockets)
         self.assertEqual({}, self.__boss._unix_sockets)
         self.assertTrue(sock.closed)
         self.assertTrue(sock.closed)
 
 
+class TestFunctions(unittest.TestCase):
+    def setUp(self):
+        self.lockfile_testpath = \
+            "@abs_top_builddir@/src/bin/bind10/tests/lockfile_test"
+        self.assertFalse(os.path.exists(self.lockfile_testpath))
+        os.mkdir(self.lockfile_testpath)
+        self.assertTrue(os.path.isdir(self.lockfile_testpath))
+
+    def tearDown(self):
+        os.rmdir(self.lockfile_testpath)
+        self.assertFalse(os.path.isdir(self.lockfile_testpath))
+        os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+
+    def test_remove_lock_files(self):
+        os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
+
+        # create lockfiles for the testcase
+        lockfiles = ["logger_lockfile"]
+        for f in lockfiles:
+            fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
+            self.assertFalse(os.path.exists(fname))
+            open(fname, "w").close()
+            self.assertTrue(os.path.isfile(fname))
+
+        # first call should clear up all the lockfiles
+        bind10_src.remove_lock_files()
+
+        # check if the lockfiles exist
+        for f in lockfiles:
+            fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
+            self.assertFalse(os.path.isfile(fname))
+
+        # second call should not assert anyway
+        bind10_src.remove_lock_files()
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     # store os.environ for test_unchanged_environment
     # store os.environ for test_unchanged_environment
     original_os_environ = copy.deepcopy(os.environ)
     original_os_environ = copy.deepcopy(os.environ)

+ 1 - 0
src/bin/cmdctl/tests/Makefile.am

@@ -22,5 +22,6 @@ endif
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cmdctl \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
 	CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

+ 1 - 0
src/bin/dbutil/tests/Makefile.am

@@ -3,4 +3,5 @@ SUBDIRS = . testdata
 # Tests of the update script.
 # Tests of the update script.
 
 
 check-local:
 check-local:
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(SHELL) $(abs_builddir)/dbutil_test.sh
 	$(SHELL) $(abs_builddir)/dbutil_test.sh

+ 433 - 29
src/bin/ddns/ddns.py.in

@@ -18,13 +18,24 @@
 
 
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import isc
 import isc
+from isc.acl.dns import REQUEST_LOADER
 import bind10_config
 import bind10_config
 from isc.dns import *
 from isc.dns import *
+import isc.ddns.session
+from isc.ddns.zone_config import ZoneConfig
+from isc.ddns.logger import ClientFormatter, ZoneFormatter
 from isc.config.ccsession import *
 from isc.config.ccsession import *
-from isc.cc import SessionError, SessionTimeout
+from isc.config.module_spec import ModuleSpecError
+from isc.cc import SessionError, SessionTimeout, ProtocolError
 import isc.util.process
 import isc.util.process
 import isc.util.cio.socketsession
 import isc.util.cio.socketsession
+from isc.notify.notify_out import ZONE_NEW_DATA_READY_CMD
+import isc.server_common.tsig_keyring
+from isc.server_common.dns_tcp import DNSTCPContext
+from isc.datasrc import DataSourceClient
+from isc.server_common.auth_command import auth_loadzone_command
 import select
 import select
+import time
 import errno
 import errno
 
 
 from isc.log_messages.ddns_messages import *
 from isc.log_messages.ddns_messages import *
@@ -39,26 +50,39 @@ isc.log.init("b10-ddns")
 logger = isc.log.Logger("ddns")
 logger = isc.log.Logger("ddns")
 TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
 TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
 
 
+# Well known path settings.  We need to define
+# SPECFILE_LOCATION: ddns configuration spec file
+# SOCKET_FILE: Unix domain socket file to communicate with b10-auth
+# AUTH_SPECFILE_LOCATION: b10-auth configuration spec file (tentatively
+#  necessarily for sqlite3-only-and-older-datasrc-API stuff).  This should be
+#  gone once we migrate to the new API and start using generalized config.
+#
 # If B10_FROM_SOURCE is set in the environment, we use data files
 # If B10_FROM_SOURCE is set in the environment, we use data files
 # from a directory relative to that, otherwise we use the ones
 # from a directory relative to that, otherwise we use the ones
 # installed on the system
 # installed on the system
 if "B10_FROM_SOURCE" in os.environ:
 if "B10_FROM_SOURCE" in os.environ:
-    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
-        "src" + os.sep + "bin" + os.sep + "ddns" + os.sep + "ddns.spec"
+    SPECFILE_PATH = os.environ["B10_FROM_SOURCE"] + "/src/bin/ddns"
 else:
 else:
     PREFIX = "@prefix@"
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"
     DATAROOTDIR = "@datarootdir@"
-    SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "ddns.spec"
-    SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR)\
-        .replace("${prefix}", PREFIX)
+    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR)
+    SPECFILE_PATH = SPECFILE_PATH.replace("${prefix}", PREFIX)
 
 
-SOCKET_FILE = bind10_config.DATA_PATH + '/ddns_socket'
 if "B10_FROM_BUILD" in os.environ:
 if "B10_FROM_BUILD" in os.environ:
     if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
     if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
-        SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \
-            "/ddns_socket"
+        SOCKET_FILE_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
     else:
     else:
-        SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/ddns_socket"
+        SOCKET_FILE_PATH = os.environ["B10_FROM_BUILD"]
+else:
+    SOCKET_FILE_PATH = bind10_config.DATA_PATH
+
+SPECFILE_LOCATION = SPECFILE_PATH + "/ddns.spec"
+SOCKET_FILE = SOCKET_FILE_PATH + '/ddns_socket'
+
+# Cooperating or dependency modules
+AUTH_MODULE_NAME = 'Auth'
+XFROUT_MODULE_NAME = 'Xfrout'
+ZONEMGR_MODULE_NAME = 'Zonemgr'
 
 
 isc.util.process.rename()
 isc.util.process.rename()
 
 
@@ -93,7 +117,55 @@ def clear_socket():
     if os.path.exists(SOCKET_FILE):
     if os.path.exists(SOCKET_FILE):
         os.remove(SOCKET_FILE)
         os.remove(SOCKET_FILE)
 
 
+def get_datasrc_client(cc_session):
+    '''Return data source client for update requests.
+
+    This is supposed to have a very short lifetime and should soon be replaced
+    with generic data source configuration framework.  Based on that
+    observation we simply hardcode everything except the SQLite3 database file,
+    which will be retrieved from the auth server configuration (this behavior
+    will also be deprecated).  When something goes wrong with it this function
+    still returns a dummy client so that the caller doesn't have to bother
+    to handle the error (which would also have to be replaced anyway).
+    The caller will subsequently call its find_zone method via an update
+    session object, which will result in an exception, and then result in
+    a SERVFAIL response.
+
+    Once we are ready for introducing the general framework, the whole
+    function will simply be removed.
+
+    '''
+    HARDCODED_DATASRC_CLASS = RRClass.IN()
+    file, is_default = cc_session.get_remote_config_value("Auth",
+                                                          "database_file")
+    # See xfrout.py:get_db_file() for this trick:
+    if is_default and "B10_FROM_BUILD" in os.environ:
+        file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
+    datasrc_config = '{ "database_file": "' + file + '"}'
+    try:
+        return (HARDCODED_DATASRC_CLASS,
+                DataSourceClient('sqlite3', datasrc_config), file)
+    except isc.datasrc.Error as ex:
+        class DummyDataSourceClient:
+            def __init__(self, ex):
+                self.__ex = ex
+            def find_zone(self, zone_name):
+                raise isc.datasrc.Error(self.__ex)
+        return (HARDCODED_DATASRC_CLASS, DummyDataSourceClient(ex), file)
+
+def add_pause(sec):
+    '''Pause a specified period for inter module synchronization.
+
+    This is a trivial wrapper of time.sleep, but defined as a separate function
+    so tests can customize it.
+    '''
+    time.sleep(sec)
+
 class DDNSServer:
 class DDNSServer:
+    # The number of TCP clients that can be handled by the server at the same
+    # time (this should be configurable parameter).
+    TCP_CLIENTS = 10
+
     def __init__(self, cc_session=None):
     def __init__(self, cc_session=None):
         '''
         '''
         Initialize the DDNS Server.
         Initialize the DDNS Server.
@@ -110,8 +182,32 @@ class DDNSServer:
                                                   self.config_handler,
                                                   self.config_handler,
                                                   self.command_handler)
                                                   self.command_handler)
 
 
+        # Initialize configuration with defaults.  Right now 'zones' is the
+        # only configuration, so we simply directly set it here.
         self._config_data = self._cc.get_full_config()
         self._config_data = self._cc.get_full_config()
+        self._zone_config = self.__update_zone_config(
+            self._cc.get_default_value('zones'))
         self._cc.start()
         self._cc.start()
+
+        # Internal attributes derived from other modules.  They will be
+        # initialized via dd_remote_xxx below and will be kept updated
+        # through their callbacks.  They are defined as 'protected' so tests
+        # can examine them; but they are essentially private to the class.
+        #
+        # Datasource client used for handling update requests: when set,
+        # should a tuple of RRClass and DataSourceClient.  Constructed and
+        # maintained based on auth configuration.
+        self._datasrc_info = None
+        # A set of secondary zones, retrieved from zonemgr configuration.
+        self._secondary_zones = None
+
+        # Get necessary configurations from remote modules.
+        for mod in [(AUTH_MODULE_NAME, self.__auth_config_handler),
+                    (ZONEMGR_MODULE_NAME, self.__zonemgr_config_handler)]:
+            self.__add_remote_module(mod[0], mod[1])
+        # This should succeed as long as cfgmgr is up.
+        isc.server_common.tsig_keyring.init_keyring(self._cc)
+
         self._shutdown = False
         self._shutdown = False
         # List of the session receivers where we get the requests
         # List of the session receivers where we get the requests
         self._socksession_receivers = {}
         self._socksession_receivers = {}
@@ -120,12 +216,54 @@ class DDNSServer:
         self._listen_socket.bind(SOCKET_FILE)
         self._listen_socket.bind(SOCKET_FILE)
         self._listen_socket.listen(16)
         self._listen_socket.listen(16)
 
 
+        # Create reusable resources
+        self.__request_msg = Message(Message.PARSE)
+        self.__response_renderer = MessageRenderer()
+
+        # The following attribute(s) are essentially private, but defined as
+        # "protected" so that test code can customize/inspect them.
+        # They should not be overridden/referenced for any other purposes.
+        #
+        # DDNS Protocol handling class.
+        self._UpdateSessionClass = isc.ddns.session.UpdateSession
+        # Outstanding TCP context: fileno=>(context_obj, dst)
+        self._tcp_ctxs = {}
+
+    class InternalError(Exception):
+        '''Exception for internal errors in an update session.
+
+        This exception is expected to be caught within the server class,
+        only used for controling the code flow.
+
+        '''
+        pass
+
     def config_handler(self, new_config):
     def config_handler(self, new_config):
         '''Update config data.'''
         '''Update config data.'''
-        # TODO: Handle exceptions and turn them to an error response
-        # (once we have any configuration)
-        answer = create_answer(0)
-        return answer
+        try:
+            if 'zones' in new_config:
+                self._zone_config = \
+                    self.__update_zone_config(new_config['zones'])
+            return create_answer(0)
+        except Exception as ex:
+            # We catch any exception here.  That includes any syntax error
+            # against the configuration spec.  The config interface is too
+            # complicated and it's not clear how much validation is performed
+            # there, so, while assuming it's unlikely to happen, we act
+            # proactively.
+            logger.error(DDNS_CONFIG_HANDLER_ERROR, ex)
+            return create_answer(1, "Failed to handle new configuration: " +
+                                 str(ex))
+
+    def __update_zone_config(self, new_zones_config):
+        '''Handle zones configuration update.'''
+        new_zones = {}
+        for zone_config in new_zones_config:
+            origin = Name(zone_config['origin'])
+            rrclass = RRClass(zone_config['class'])
+            update_acl = zone_config['update_acl']
+            new_zones[(origin, rrclass)] = REQUEST_LOADER.load(update_acl)
+        return new_zones
 
 
     def command_handler(self, cmd, args):
     def command_handler(self, cmd, args):
         '''
         '''
@@ -141,6 +279,88 @@ class DDNSServer:
             answer = create_answer(1, "Unknown command: " + str(cmd))
             answer = create_answer(1, "Unknown command: " + str(cmd))
         return answer
         return answer
 
 
+    def __add_remote_module(self, mod_name, callback):
+        '''Register interest in other module's config with a callback.'''
+
+        # Due to startup timing, add_remote_config can fail.  We could make it
+        # more sophisticated, but for now we simply retry a few times, each
+        # separated by a short period (3 times and 1 sec, arbitrary chosen,
+        # and hardcoded for now).  In practice this should be more than
+        # sufficient, but if it turns out to be a bigger problem we can
+        # consider more elegant solutions.
+        for n_try in range(0, 3):
+            try:
+                # by_name() version can fail with ModuleSpecError in getting
+                # the module spec because cfgmgr returns a "successful" answer
+                # with empty data if it cannot find the specified module.
+                # This seems to be a deviant behavior (see Trac #2039), but
+                # we need to deal with it.
+                self._cc.add_remote_config_by_name(mod_name, callback)
+                return
+            except (ModuleSpecError, ModuleCCSessionError) as ex:
+                logger.warn(DDNS_GET_REMOTE_CONFIG_FAIL, mod_name, n_try + 1,
+                            ex)
+                last_ex = ex
+                add_pause(1)
+        raise last_ex
+
+    def __auth_config_handler(self, new_config, module_config):
+        logger.info(DDNS_RECEIVED_AUTH_UPDATE)
+
+        # If we've got the config before and the new config doesn't update
+        # the DB file, there's nothing we should do with it.
+        # Note: there seems to be a bug either in bindctl or cfgmgr, and
+        # new_config can contain 'database_file' even if it's not really
+        # updated.  We still perform the check so we can avoid redundant
+        # resetting when the bug is fixed.  The redundant reset itself is not
+        # good, but such configuration update should not happen so often and
+        # it should be acceptable in practice.
+        if self._datasrc_info is not None and \
+                not 'database_file' in new_config:
+            return
+        rrclass, client, db_file = get_datasrc_client(self._cc)
+        self._datasrc_info = (rrclass, client)
+        logger.info(DDNS_AUTH_DBFILE_UPDATE, db_file)
+
+    def __zonemgr_config_handler(self, new_config, module_config):
+        logger.info(DDNS_RECEIVED_ZONEMGR_UPDATE)
+
+        # If we've got the config before and the new config doesn't update
+        # the secondary zone list, there's nothing we should do with it.
+        # (Same note as that for auth's config applies)
+        if self._secondary_zones is not None and \
+                not 'secondary_zones' in new_config:
+            return
+
+        # Get the latest secondary zones.  Use get_remote_config_value() so
+        # it can work for both the initial default case and updates.
+        sec_zones, _ = self._cc.get_remote_config_value(ZONEMGR_MODULE_NAME,
+                                                        'secondary_zones')
+        new_secondary_zones = set()
+        try:
+            # Parse the new config and build a new list of secondary zones.
+            # Unfortunately, in the current implementation, even an observer
+            # module needs to perform full validation.  This should be changed
+            # so that only post-validation (done by the main module) config is
+            # delivered to observer modules, but until it's supported we need
+            # to protect ourselves.
+            for zone_spec in sec_zones:
+                zname = Name(zone_spec['name'])
+                # class has the default value in case it's unspecified.
+                # ideally this should be merged within the config module, but
+                # the current implementation doesn't esnure that, so we need to
+                # subsitute it ourselves.
+                if 'class' in zone_spec:
+                    zclass = RRClass(zone_spec['class'])
+                else:
+                    zclass = RRClass(module_config.get_default_value(
+                            'secondary_zones/class'))
+                new_secondary_zones.add((zname, zclass))
+            self._secondary_zones = new_secondary_zones
+            logger.info(DDNS_SECONDARY_ZONES_UPDATE, len(self._secondary_zones))
+        except Exception as ex:
+            logger.error(DDNS_SECONDARY_ZONES_UPDATE_FAIL, ex)
+
     def trigger_shutdown(self):
     def trigger_shutdown(self):
         '''Initiate a shutdown sequence.
         '''Initiate a shutdown sequence.
 
 
@@ -168,10 +388,10 @@ class DDNSServer:
         Accept another connection and create the session receiver.
         Accept another connection and create the session receiver.
         """
         """
         try:
         try:
-            sock = self._listen_socket.accept()
+            (sock, remote_addr) = self._listen_socket.accept()
             fileno = sock.fileno()
             fileno = sock.fileno()
             logger.debug(TRACE_BASIC, DDNS_NEW_CONN, fileno,
             logger.debug(TRACE_BASIC, DDNS_NEW_CONN, fileno,
-                         sock.getpeername())
+                         remote_addr if remote_addr else '<anonymous address>')
             receiver = isc.util.cio.socketsession.SocketSessionReceiver(sock)
             receiver = isc.util.cio.socketsession.SocketSessionReceiver(sock)
             self._socksession_receivers[fileno] = (sock, receiver)
             self._socksession_receivers[fileno] = (sock, receiver)
         except (socket.error, isc.util.cio.socketsession.SocketSessionError) \
         except (socket.error, isc.util.cio.socketsession.SocketSessionError) \
@@ -180,7 +400,30 @@ class DDNSServer:
             # continue with the rest
             # continue with the rest
             logger.error(DDNS_ACCEPT_FAILURE, e)
             logger.error(DDNS_ACCEPT_FAILURE, e)
 
 
-    def handle_request(self, request):
+    def __check_request_tsig(self, msg, req_data):
+        '''TSIG checker for update requests.
+
+        This is a helper method for handle_request() below.  It examines
+        the given update request message to see if it contains a TSIG RR,
+        and verifies the signature if it does.  It returs the TSIG context
+        used for the verification, or None if the request doesn't contain
+        a TSIG.  If the verification fails it simply raises an exception
+        as handle_request() assumes it should succeed.
+
+        '''
+        tsig_record = msg.get_tsig_record()
+        if tsig_record is None:
+            return None
+        tsig_ctx = TSIGContext(tsig_record.get_name(),
+                               tsig_record.get_rdata().get_algorithm(),
+                               isc.server_common.tsig_keyring.get_keyring())
+        tsig_error = tsig_ctx.verify(tsig_record, req_data)
+        if tsig_error != TSIGError.NOERROR:
+            raise self.InternalError("Failed to verify request's TSIG: " +
+                                     str(tsig_error))
+        return tsig_ctx
+
+    def handle_request(self, req_session):
         """
         """
         This is the place where the actual DDNS processing is done. Other
         This is the place where the actual DDNS processing is done. Other
         methods are either subroutines of this method or methods doing the
         methods are either subroutines of this method or methods doing the
@@ -190,27 +433,179 @@ class DDNSServer:
         It is called with the request being session as received from
         It is called with the request being session as received from
         SocketSessionReceiver, i.e. tuple
         SocketSessionReceiver, i.e. tuple
         (socket, local_address, remote_address, data).
         (socket, local_address, remote_address, data).
+
+        In general, this method doesn't propagate exceptions outside the
+        method.  Most of protocol or system errors will result in an error
+        response to the update client or dropping the update request.
+        The update session class should also ensure this.  Critical exceptions
+        such as memory allocation failure will be propagated, however, and
+        will subsequently terminate the server process.
+
+        Return: True if a response to the request is successfully sent;
+        False otherwise.  The return value wouldn't be useful for the server
+        itself; it's provided mainly for testing purposes.
+
         """
         """
-        # TODO: Implement the magic
+        # give tuple elements intuitive names
+        (sock, local_addr, remote_addr, req_data) = req_session
+
+        # The session sender (b10-auth) should have made sure that this is
+        # a validly formed DNS message of OPCODE being UPDATE, and if it's
+        # TSIG signed, its key is known to the system and the signature is
+        # valid.  Messages that don't meet these should have been resopnded
+        # or dropped by the sender, so if such error is detected we treat it
+        # as an internal error and don't bother to respond.
+        try:
+            self.__request_msg.clear(Message.PARSE)
+            # specify PRESERVE_ORDER as we need to handle each RR separately.
+            self.__request_msg.from_wire(req_data, Message.PRESERVE_ORDER)
+            if self.__request_msg.get_opcode() != Opcode.UPDATE():
+                raise self.InternalError('Update request has unexpected '
+                                         'opcode: ' +
+                                         str(self.__request_msg.get_opcode()))
+            tsig_ctx = self.__check_request_tsig(self.__request_msg, req_data)
+        except Exception as ex:
+            logger.error(DDNS_REQUEST_PARSE_FAIL, ex)
+            return False
+
+        # Let an update session object handle the request.  Note: things around
+        # ZoneConfig will soon be substantially revised.  For now we don't
+        # bother to generalize it.
+        zone_cfg = ZoneConfig(self._secondary_zones, self._datasrc_info[0],
+                              self._datasrc_info[1], self._zone_config)
+        update_session = self._UpdateSessionClass(self.__request_msg,
+                                                  remote_addr, zone_cfg)
+        result, zname, zclass = update_session.handle()
+
+        # If the request should be dropped, we're done; otherwise, send the
+        # response generated by the session object.
+        if result == isc.ddns.session.UPDATE_DROP:
+            return False
+        msg = update_session.get_message()
+        self.__response_renderer.clear()
+        if tsig_ctx is not None:
+            msg.to_wire(self.__response_renderer, tsig_ctx)
+        else:
+            msg.to_wire(self.__response_renderer)
 
 
-        # TODO: Don't propagate most of the exceptions (like datasrc errors),
-        # just drop the packet.
-        pass
+        ret = self.__send_response(sock, self.__response_renderer.get_data(),
+                                   remote_addr)
+        if result == isc.ddns.session.UPDATE_SUCCESS:
+            self.__notify_auth(zname, zclass)
+            self.__notify_xfrout(zname, zclass)
+        return ret
+
+    def __send_response(self, sock, data, dest):
+        '''Send DDNS response to the client.
+
+        Right now, this is a straightforward subroutine of handle_request(),
+        but is intended to be extended evetually so that it can handle more
+        comlicated operations for TCP (which requires asynchronous write).
+        Further, when we support multiple requests over a single TCP
+        connection, this method may even be shared by multiple methods.
+
+        Parameters:
+        sock: (python socket) the socket to which the response should be sent.
+        data: (binary) the response data
+        dest: (python socket address) the destion address to which the response
+          should be sent.
+
+        Return: True if the send operation succeds; otherwise False.
+
+        '''
+        try:
+            if sock.proto == socket.IPPROTO_UDP:
+                sock.sendto(data, dest)
+            else:
+                tcp_ctx = DNSTCPContext(sock)
+                send_result = tcp_ctx.send(data)
+                if send_result == DNSTCPContext.SENDING:
+                    self._tcp_ctxs[sock.fileno()] = (tcp_ctx, dest)
+                elif send_result == DNSTCPContext.CLOSED:
+                    raise socket.error("socket error in TCP send")
+                else:
+                    tcp_ctx.close()
+        except socket.error as ex:
+            logger.warn(DDNS_RESPONSE_SOCKET_ERROR, ClientFormatter(dest), ex)
+            return False
+
+        return True
+
+    def __notify_auth(self, zname, zclass):
+        '''Notify auth of the update, if necessary.'''
+        msg = auth_loadzone_command(self._cc, zname, zclass)
+        if msg is not None:
+            self.__notify_update(AUTH_MODULE_NAME, msg, zname, zclass)
+
+    def __notify_xfrout(self, zname, zclass):
+        '''Notify xfrout of the update.'''
+        param = {'zone_name': zname.to_text(), 'zone_class': zclass.to_text()}
+        msg = create_command(ZONE_NEW_DATA_READY_CMD, param)
+        self.__notify_update(XFROUT_MODULE_NAME, msg, zname, zclass)
+
+    def __notify_update(self, modname, msg, zname, zclass):
+        '''Notify other module of the update.
+
+        Note that we use blocking communication here.  While the internal
+        communication bus is generally expected to be pretty responsive and
+        error free, notable delay can still occur, and in worse cases timeouts
+        or connection reset can happen.  In these cases, even if the trouble
+        is temporary, the update service will be suspended for a while.
+        For a longer term we'll need to switch to asynchronous communication,
+        but for now we rely on the blocking operation.
+
+        Note also that we directly refer to the "protected" member of
+        ccsession (_cc._session) rather than creating a separate channel.
+        It's probably not the best practice, but hopefully we can introduce
+        a cleaner way when we support asynchronous communication.
+        At the moment we prefer the brevity with the use of internal channel
+        of the cc session.
+
+        '''
+        try:
+            seq = self._cc._session.group_sendmsg(msg, modname)
+            answer, _ = self._cc._session.group_recvmsg(False, seq)
+            rcode, error_msg = parse_answer(answer)
+        except (SessionTimeout, SessionError, ProtocolError) as ex:
+            rcode = 1
+            error_msg = str(ex)
+        if rcode == 0:
+            logger.debug(TRACE_BASIC, DDNS_UPDATE_NOTIFY, modname,
+                         ZoneFormatter(zname, zclass))
+        else:
+            logger.error(DDNS_UPDATE_NOTIFY_FAIL, modname,
+                         ZoneFormatter(zname, zclass), error_msg)
 
 
     def handle_session(self, fileno):
     def handle_session(self, fileno):
-        """
-        Handle incoming session on the socket with given fileno.
+        """Handle incoming session on the socket with given fileno.
+
+        Return True if a response (whether positive or negative) has been
+        sent; otherwise False.  The return value isn't expected to be used
+        for other purposes than testing.
+
         """
         """
         logger.debug(TRACE_BASIC, DDNS_SESSION, fileno)
         logger.debug(TRACE_BASIC, DDNS_SESSION, fileno)
-        (socket, receiver) = self._socksession_receivers[fileno]
+        (session_socket, receiver) = self._socksession_receivers[fileno]
         try:
         try:
-            self.handle_request(receiver.pop())
+            req_session = receiver.pop()
+            (sock, remote_addr) = (req_session[0], req_session[2])
+
+            # If this is a TCP client, check the quota, and immediately reject
+            # it if we cannot accept more.
+            if sock.proto == socket.IPPROTO_TCP and \
+                    len(self._tcp_ctxs) >= self.TCP_CLIENTS:
+                logger.warn(DDNS_REQUEST_TCP_QUOTA,
+                            ClientFormatter(remote_addr), len(self._tcp_ctxs))
+                sock.close()
+                return False
+            return self.handle_request(req_session)
         except isc.util.cio.socketsession.SocketSessionError as se:
         except isc.util.cio.socketsession.SocketSessionError as se:
             # No matter why this failed, the connection is in unknown, possibly
             # No matter why this failed, the connection is in unknown, possibly
             # broken state. So, we close the socket and remove the receiver.
             # broken state. So, we close the socket and remove the receiver.
             del self._socksession_receivers[fileno]
             del self._socksession_receivers[fileno]
-            socket.close()
+            session_socket.close()
             logger.warn(DDNS_DROP_CONN, fileno, se)
             logger.warn(DDNS_DROP_CONN, fileno, se)
+            return False
 
 
     def run(self):
     def run(self):
         '''
         '''
@@ -231,8 +626,8 @@ class DDNSServer:
             try:
             try:
                 (reads, writes, exceptions) = \
                 (reads, writes, exceptions) = \
                     select.select([cc_fileno, listen_fileno] +
                     select.select([cc_fileno, listen_fileno] +
-                                  list(self._socksession_receivers.keys()), [],
-                                  [])
+                                  list(self._socksession_receivers.keys()),
+                                  list(self._tcp_ctxs.keys()), [])
             except select.error as se:
             except select.error as se:
                 # In case it is just interrupted, we continue like nothing
                 # In case it is just interrupted, we continue like nothing
                 # happened
                 # happened
@@ -247,6 +642,15 @@ class DDNSServer:
                     self.accept()
                     self.accept()
                 else:
                 else:
                     self.handle_session(fileno)
                     self.handle_session(fileno)
+            for fileno in writes:
+                ctx = self._tcp_ctxs[fileno]
+                result = ctx[0].send_ready()
+                if result != DNSTCPContext.SENDING:
+                    if result == DNSTCPContext.CLOSED:
+                        logger.warn(DDNS_RESPONSE_TCP_SOCKET_ERROR,
+                                    ClientFormatter(ctx[1]))
+                    ctx[0].close()
+                    del self._tcp_ctxs[fileno]
         self.shutdown_cleanup()
         self.shutdown_cleanup()
         logger.info(DDNS_STOPPED)
         logger.info(DDNS_STOPPED)
 
 
@@ -305,7 +709,7 @@ def main(ddns_server=None):
         logger.info(DDNS_STOPPED_BY_KEYBOARD)
         logger.info(DDNS_STOPPED_BY_KEYBOARD)
     except SessionError as e:
     except SessionError as e:
         logger.error(DDNS_CC_SESSION_ERROR, str(e))
         logger.error(DDNS_CC_SESSION_ERROR, str(e))
-    except ModuleCCSessionError as e:
+    except (ModuleSpecError, ModuleCCSessionError) as e:
         logger.error(DDNS_MODULECC_SESSION_ERROR, str(e))
         logger.error(DDNS_MODULECC_SESSION_ERROR, str(e))
     except DDNSConfigError as e:
     except DDNSConfigError as e:
         logger.error(DDNS_CONFIG_ERROR, str(e))
         logger.error(DDNS_CONFIG_ERROR, str(e))

+ 19 - 5
src/bin/ddns/ddns.spec

@@ -4,22 +4,36 @@
     "config_data": [
     "config_data": [
       {
       {
         "item_name": "zones",
         "item_name": "zones",
-        "item_type": "named_set",
+        "item_type": "list",
         "item_optional": false,
         "item_optional": false,
-        "item_default": {},
-        "named_set_item_spec": {
+        "item_default": [],
+        "list_item_spec": {
           "item_name": "entry",
           "item_name": "entry",
           "item_type": "map",
           "item_type": "map",
           "item_optional": true,
           "item_optional": true,
           "item_default": {
           "item_default": {
-            "update_acl": [{"action": "ACCEPT", "from": "127.0.0.1"},
-                           {"action": "ACCEPT", "from": "::1"}]
+	    "origin": "",
+	    "class": "IN",
+            "update_acl": []
           },
           },
           "map_item_spec": [
           "map_item_spec": [
             {
             {
+              "item_name": "origin",
+              "item_type": "string",
+              "item_optional": false,
+              "item_default": ""
+            },
+            {
+              "item_name": "class",
+              "item_type": "string",
+              "item_optional": false,
+              "item_default": "IN"
+            },
+            {
               "item_name": "update_acl",
               "item_name": "update_acl",
               "item_type": "list",
               "item_type": "list",
               "item_optional": false,
               "item_optional": false,
+	      "item_default": [],
               "list_item_spec": {
               "list_item_spec": {
                 "item_name": "acl_element",
                 "item_name": "acl_element",
                 "item_type": "any",
                 "item_type": "any",

+ 139 - 0
src/bin/ddns/ddns_messages.mes

@@ -25,6 +25,12 @@ There was a low-level error when we tried to accept an incoming connection
 connections we already have, but this connection is dropped. The reason
 connections we already have, but this connection is dropped. The reason
 is logged.
 is logged.
 
 
+% DDNS_AUTH_DBFILE_UPDATE updated auth DB file to %1
+b10-ddns was notified of updates to the SQLite3 DB file that b10-auth
+uses for the underlying data source and on which b10-ddns needs to
+make updates.  b10-ddns then updated its internal setup so further
+updates would be made on the new DB.
+
 % DDNS_CC_SESSION_ERROR error reading from cc channel: %1
 % DDNS_CC_SESSION_ERROR error reading from cc channel: %1
 There was a problem reading from the command and control channel. The
 There was a problem reading from the command and control channel. The
 most likely cause is that the msgq process is not running.
 most likely cause is that the msgq process is not running.
@@ -38,6 +44,14 @@ configuration manager b10-cfgmgr is not running.
 The ddns process encountered an error when installing the configuration at
 The ddns process encountered an error when installing the configuration at
 startup time.  Details of the error are included in the log message.
 startup time.  Details of the error are included in the log message.
 
 
+% DDNS_CONFIG_HANDLER_ERROR failed to update ddns configuration: %1
+An update to b10-ddns configuration was delivered but an error was
+found while applying them.  None of the delivered updates were applied
+to the running b10-ddns system, and the server will keep running with
+the existing configuration.  If this happened in the initial
+configuration setup, the server will be running with the default
+configurations.
+
 % DDNS_DROP_CONN dropping connection on file descriptor %1 because of error %2
 % DDNS_DROP_CONN dropping connection on file descriptor %1 because of error %2
 There was an error on a connection with the b10-auth server (or whatever
 There was an error on a connection with the b10-auth server (or whatever
 connects to the ddns daemon). This might be OK, for example when the
 connects to the ddns daemon). This might be OK, for example when the
@@ -45,6 +59,29 @@ authoritative server shuts down, the connection would get closed. It also
 can mean the system is busy and can't keep up or that the other side got
 can mean the system is busy and can't keep up or that the other side got
 confused and sent bad data.
 confused and sent bad data.
 
 
+% DDNS_GET_REMOTE_CONFIG_FAIL failed to get %1 module configuration %2 times: %3
+b10-ddns tried to get configuration of some remote modules for its
+operation, but it failed.  The most likely cause of this is that the
+remote module has not fully started up and b10-ddns couldn't get the
+configuration in a timely fashion.  b10-ddns attempts to retry it a
+few times, imposing a short delay, hoping it eventually succeeds if
+it's just a timing issue.  The number of total failed attempts is also
+logged.  If it reaches an internal threshold b10-ddns considers it a
+fatal error and terminates.  Even in that case, if b10-ddns is
+configured as a "dispensable" component (which is the default), the
+parent bind10 process will restart it, and there will be another
+chance of getting the remote configuration successfully.  These are
+not the optimal behavior, but it's believed to be sufficient in
+practice (there would normally be no failure in the first place).  If
+it really causes an operational trouble other than having a few of
+these log messages, please submit a bug report; there can be several
+ways to make it more sophisticated.  Another, less likely reason for
+having this error is because the remote modules are not actually
+configured to run.  If that's the case fixing the configuration should
+solve the problem - either by making sure the remote module will run
+or by not running b10-ddns (without these remote modules b10-ddns is
+not functional, so there's no point in running it in this case).
+
 % DDNS_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
 % DDNS_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
 There was a problem in the lower level module handling configuration and
 There was a problem in the lower level module handling configuration and
 control commands.  This could happen for various reasons, but the most likely
 control commands.  This could happen for various reasons, but the most likely
@@ -58,14 +95,93 @@ requests from it. The file descriptor number and the address where the request
 comes from is logged. The connection is over a unix domain socket and is likely
 comes from is logged. The connection is over a unix domain socket and is likely
 coming from a b10-auth process.
 coming from a b10-auth process.
 
 
+% DDNS_RECEIVED_AUTH_UPDATE received configuration updates from auth server
+b10-ddns is notified of updates to b10-auth configuration
+(including a report of the initial configuration) that b10-ddns might
+be interested in.
+
 % DDNS_RECEIVED_SHUTDOWN_COMMAND shutdown command received
 % DDNS_RECEIVED_SHUTDOWN_COMMAND shutdown command received
 The ddns process received a shutdown command from the command channel
 The ddns process received a shutdown command from the command channel
 and will now shut down.
 and will now shut down.
 
 
+% DDNS_RECEIVED_ZONEMGR_UPDATE received configuration updates from zonemgr
+b10-ddns is notified of updates to b10-zonemgr's configuration
+(including a report of the initial configuration).  It may possibly
+contain changes to the secondary zones, in which case b10-ddns will
+update its internal copy of that configuration.
+
+% DDNS_REQUEST_PARSE_FAIL failed to parse update request: %1
+b10-ddns received an update request via b10-auth, but the received
+data failed to pass minimum validation: it was either broken wire
+format data for a valid DNS message (e.g. it's shorter than the
+fixed-length header), or the opcode is not update, or TSIG is included
+in the request but it fails to validate.  Since b10-auth should have
+performed this level of checks, such an error shouldn't be detected at
+this stage and should rather be considered an internal bug.  This
+event is therefore logged at the error level, and the request is
+simply dropped.  Additional information of the error is also logged.
+
+% DDNS_REQUEST_TCP_QUOTA reject TCP update client %1 (%2 running)
+b10-ddns received a new update request from a client over TCP, but
+the number of TCP clients being handled by the server already reached
+the configured quota, so the latest client was rejected by closing
+the connection.  The administrator may want to check the status of
+b10-ddns, and if this happens even if the server is not very busy,
+the quota may have to be increased.  Or, if it's more likely to be
+malicious or simply bogus clients that somehow keep the TCP connection
+open for a long period, maybe they should be rejected with an
+appropriate ACL configuration or some lower layer filtering.  The
+number of existing TCP clients are shown in the log, which should be
+identical to the current quota.
+
+% DDNS_RESPONSE_SOCKET_ERROR failed to send update response to %1: %2
+Network I/O error happens in sending an update response.  The
+client's address that caused the error and error details are also
+logged.
+
+% DDNS_RESPONSE_TCP_SOCKET_ERROR failed to complete sending update response to %1 over TCP
+b10-ddns had tried to send an update response over TCP, and it hadn't
+been completed at that time, and a followup attempt to complete the
+send operation failed due to some network I/O error.  While a network
+error can happen any time, this event is quite unexpected for two
+reasons.  First, since the size of a response to an update request
+should be generally small, it's unlikely that the initial attempt
+didn't fail but wasn't completed.  Second, since the first attempt
+succeeded and the TCP connection had been established in the first
+place, it's more likely for the subsequent attempt to succeed.  In any
+case, there may not be able to do anything to fix it at the server
+side, but the administrator may want to check the general reachability
+with the client address.
+
 % DDNS_RUNNING ddns server is running and listening for updates
 % DDNS_RUNNING ddns server is running and listening for updates
 The ddns process has successfully started and is now ready to receive commands
 The ddns process has successfully started and is now ready to receive commands
 and updates.
 and updates.
 
 
+% DDNS_SECONDARY_ZONES_UPDATE updated secondary zone list (%1 zones are listed)
+b10-ddns has successfully updated the internal copy of secondary zones
+obtained from b10-zonemgr, based on a latest update to zonemgr's
+configuration.  The number of newly configured (unique) secondary
+zones is logged.
+
+% DDNS_SECONDARY_ZONES_UPDATE_FAIL failed to update secondary zone list: %1
+An error message.  b10-ddns was notified of updates to a list of
+secondary zones from b10-zonemgr and tried to update its own internal
+copy of the list, but it failed.  This can happen if the configuration
+contains an error, and b10-zonemgr should also reject that update.
+Unfortunately, in the current implementation there is no way to ensure
+that both zonemgr and ddns have consistent information when an update
+contains an error; further, as of this writing zonemgr has a bug that
+it could partially update the list of secondary zones if part of the
+list has an error (see Trac ticket #2038).  b10-ddns still keeps
+running with the previous configuration, but it's strongly advisable
+to check log messages from zonemgr, and if it indicates there can be
+inconsistent state, it's better to restart the entire BIND 10 system
+(just restarting b10-ddns wouldn't be enough, because zonemgr can have
+partially updated configuration due to bug #2038).  The log message
+contains an error description, but it's intentionally kept simple as
+it's primarily a matter of zonemgr.  To know the details of the error,
+log messages of zonemgr should be consulted.
+
 % DDNS_SESSION session arrived on file descriptor %1
 % DDNS_SESSION session arrived on file descriptor %1
 A debug message, informing there's some activity on the given file descriptor.
 A debug message, informing there's some activity on the given file descriptor.
 It will be either a request or the file descriptor will be closed. See
 It will be either a request or the file descriptor will be closed. See
@@ -88,3 +204,26 @@ process will now shut down.
 The b10-ddns process encountered an uncaught exception and will now shut
 The b10-ddns process encountered an uncaught exception and will now shut
 down. This is indicative of a programming error and should not happen under
 down. This is indicative of a programming error and should not happen under
 normal circumstances. The exception type and message are printed.
 normal circumstances. The exception type and message are printed.
+
+% DDNS_UPDATE_NOTIFY notified %1 of updates to %2
+Debug message.  b10-ddns has made updates to a zone based on an update
+request and has successfully notified an external module of the updates.
+The notified module will use that information for updating its own
+state or any necessary protocol action such as zone reloading or sending
+notify messages to secondary servers.
+
+% DDNS_UPDATE_NOTIFY_FAIL failed to notify %1 of updates to %2: %3
+b10-ddns has made updates to a zone based on an update request and
+tried to notify an external module of the updates, but the
+notification fails.  Severity of this effect depends on the type of
+the module.  If it's b10-xfrout, this means DNS notify messages won't
+be sent to secondary servers of the zone.  It's suboptimal, but not
+necessarily critical as the secondary servers will try to check the
+zone's status periodically.  If it's b10-auth and the notification was
+needed to have it reload the corresponding zone, it's more serious
+because b10-auth won't be able to serve the new version of the zone
+unless some explicit recovery action is taken.  So the administrator
+needs to examine this message and takes an appropriate action.  In
+either case, this notification is generally expected to succeed; so
+the fact it fails itself means there's something wrong in the BIND 10
+system, and it would be advisable to check other log messages.

+ 1 - 0
src/bin/ddns/tests/Makefile.am

@@ -25,5 +25,6 @@ endif
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/ddns:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/ddns:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
 	TESTDATASRCDIR=$(abs_srcdir)/testdata/ \
 	TESTDATASRCDIR=$(abs_srcdir)/testdata/ \
+	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done

File diff suppressed because it is too large
+ 928 - 12
src/bin/ddns/tests/ddns_test.py


+ 6 - 0
src/bin/dhcp4/Makefile.am

@@ -32,6 +32,12 @@ pkglibexec_PROGRAMS = b10-dhcp4
 
 
 b10_dhcp4_SOURCES = main.cc dhcp4_srv.cc dhcp4_srv.h
 b10_dhcp4_SOURCES = main.cc dhcp4_srv.cc dhcp4_srv.h
 
 
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
+endif
+
 b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libdhcp++.la
 b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libdhcp++.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la

+ 6 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -47,6 +47,12 @@ dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 
 
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+dhcp4_unittests_CXXFLAGS = -Wno-unused-parameter
+endif
+
 dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp4_unittests_LDADD = $(GTEST_LDADD)
 dhcp4_unittests_LDADD = $(GTEST_LDADD)

+ 6 - 0
src/bin/dhcp6/Makefile.am

@@ -34,6 +34,12 @@ pkglibexec_PROGRAMS = b10-dhcp6
 
 
 b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
 b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
 
 
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
+endif
+
 b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/liblog.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/liblog.la

+ 7 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -43,6 +43,12 @@ dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 
 
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+dhcp6_unittests_CXXFLAGS = -Wno-unused-parameter
+endif
+
 dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
@@ -50,6 +56,7 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
 endif
 endif
 
 
 noinst_PROGRAMS = $(TESTS)
 noinst_PROGRAMS = $(TESTS)

+ 1 - 0
src/bin/stats/tests/Makefile.am

@@ -24,6 +24,7 @@ endif
 	B10_FROM_SOURCE=$(abs_top_srcdir) \
 	B10_FROM_SOURCE=$(abs_top_srcdir) \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 	CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
 	CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done
 
 

+ 8 - 44
src/bin/xfrin/xfrin.py.in

@@ -33,6 +33,7 @@ import isc.util.process
 from isc.datasrc import DataSourceClient, ZoneFinder
 from isc.datasrc import DataSourceClient, ZoneFinder
 import isc.net.parse
 import isc.net.parse
 from isc.xfrin.diff import Diff
 from isc.xfrin.diff import Diff
+from isc.server_common.auth_command import auth_loadzone_command
 from isc.log_messages.xfrin_messages import *
 from isc.log_messages.xfrin_messages import *
 
 
 isc.log.init("b10-xfrin")
 isc.log.init("b10-xfrin")
@@ -1248,50 +1249,13 @@ class ZoneInfo:
                 (str(self.master_addr), self.master_port))
                 (str(self.master_addr), self.master_port))
 
 
 def _do_auth_loadzone(server, zone_name, zone_class):
 def _do_auth_loadzone(server, zone_name, zone_class):
-    # On a successful zone transfer, if the zone is served by
-    # b10-auth in the in-memory data source using sqlite3 as a
-    # backend, send the "loadzone" command for the zone to auth.
-    datasources, is_default =\
-        server._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "datasources")
-    if is_default:
-        return
-    for d in datasources:
-        if "type" not in d:
-            continue
-        try:
-            if "class" in d:
-                dclass = RRClass(d["class"])
-            else:
-                dclass = RRClass("IN")
-        except InvalidRRClass as err:
-            logger.info(XFRIN_AUTH_CONFIG_RRCLASS_ERROR, str(err))
-            continue
-
-        if d["type"].lower() == "memory" and dclass == zone_class:
-            for zone in d["zones"]:
-                if "filetype" not in zone:
-                    continue
-                if "origin" not in zone:
-                    continue
-                if "filetype" not in zone:
-                    continue
-                try:
-                    name = Name(zone["origin"])
-                except (EmptyLabel, TooLongLabel, BadLabelType, BadEscape, TooLongName, IncompleteName):
-                    logger.info(XFRIN_AUTH_CONFIG_NAME_PARSER_ERROR, str(err))
-                    continue
-
-                if zone["filetype"].lower() == "sqlite3" and name == zone_name:
-                    param = {"origin": zone_name.to_text(),
-                             "class": zone_class.to_text(),
-                             "datasrc": d["type"]}
-
-                    logger.debug(DBG_XFRIN_TRACE, XFRIN_AUTH_LOADZONE,
-                                 param["origin"], param["class"], param["datasrc"])
-
-                    msg = create_command("loadzone", param)
-                    seq = server._send_cc_session.group_sendmsg(msg, AUTH_MODULE_NAME)
-                    answer, env = server._send_cc_session.group_recvmsg(False, seq)
+    msg = auth_loadzone_command(server._module_cc, zone_name, zone_class)
+    if msg is not None:
+        param = msg['command'][1]
+        logger.debug(DBG_XFRIN_TRACE, XFRIN_AUTH_LOADZONE, param["origin"],
+                     param["class"], param["datasrc"])
+        seq = server._send_cc_session.group_sendmsg(msg, AUTH_MODULE_NAME)
+        answer, env = server._send_cc_session.group_recvmsg(False, seq)
 
 
 class Xfrin:
 class Xfrin:
     def __init__(self):
     def __init__(self):

+ 0 - 6
src/bin/xfrin/xfrin_messages.mes

@@ -15,12 +15,6 @@
 # No namespace declaration - these constants go in the global namespace
 # No namespace declaration - these constants go in the global namespace
 # of the xfrin messages python module.
 # of the xfrin messages python module.
 
 
-% XFRIN_AUTH_CONFIG_NAME_PARSER_ERROR Invalid name when parsing Auth configuration: %1
-There was an invalid name when parsing Auth configuration.
-
-% XFRIN_AUTH_CONFIG_RRCLASS_ERROR Invalid RRClass when parsing Auth configuration: %1
-There was an invalid RR class when parsing Auth configuration.
-
 % XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3
 % XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3
 There was a successful zone transfer, and the zone is served by b10-auth
 There was a successful zone transfer, and the zone is served by b10-auth
 in the in-memory data source using sqlite3 as a backend. We send the
 in the in-memory data source using sqlite3 as a backend. We send the

+ 2 - 2
src/lib/asiolink/io_endpoint.h

@@ -168,8 +168,8 @@ public:
 ///
 ///
 /// This method converts the address and port of the endpoint in the textual
 /// This method converts the address and port of the endpoint in the textual
 /// format that other BIND 10 modules would use in logging, i.e.,
 /// format that other BIND 10 modules would use in logging, i.e.,
-/// - For IPv6 address: [<address>]:port (e.g., [2001:db8::5300]:53)
-/// - For IPv4 address: <address>:port (e.g., 192.0.2.53:5300)
+/// - For IPv6 address: [&lt;address&gt;]:port (e.g., [2001:db8::5300]:53)
+/// - For IPv4 address: &lt;address&gt;:port (e.g., 192.0.2.53:5300)
 ///
 ///
 /// If it's neither IPv6 nor IPv4, it converts the endpoint into text in the
 /// If it's neither IPv6 nor IPv4, it converts the endpoint into text in the
 /// same format as that for IPv4, although in practice such a case is not
 /// same format as that for IPv4, although in practice such a case is not

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

@@ -12,8 +12,13 @@ pkglibdir = $(libexecdir)/@PACKAGE@/backends
 datasrc_config.h: datasrc_config.h.pre
 datasrc_config.h: datasrc_config.h.pre
 	$(SED) -e "s|@@PKGLIBDIR@@|$(pkglibdir)|" datasrc_config.h.pre >$@
 	$(SED) -e "s|@@PKGLIBDIR@@|$(pkglibdir)|" datasrc_config.h.pre >$@
 
 
+static.zone: static.zone.pre
+	$(SED) -e "s|@@VERSION_STRING@@|$(PACKAGE_STRING)|" $(srcdir)/static.zone.pre >$@
+	$(SED) -e 's/\(.*\)/AUTHORS.BIND.	0	CH	TXT	"\1"/' $(top_srcdir)/AUTHORS >>$@
+
 CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
 CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
 CLEANFILES += datasrc_config.h
 CLEANFILES += datasrc_config.h
+CLEANFILES += static.zone
 
 
 lib_LTLIBRARIES = libdatasrc.la
 lib_LTLIBRARIES = libdatasrc.la
 libdatasrc_la_SOURCES = data_source.h data_source.cc
 libdatasrc_la_SOURCES = data_source.h data_source.cc
@@ -30,10 +35,11 @@ libdatasrc_la_SOURCES += logger.h logger.cc
 libdatasrc_la_SOURCES += client.h iterator.h
 libdatasrc_la_SOURCES += client.h iterator.h
 libdatasrc_la_SOURCES += database.h database.cc
 libdatasrc_la_SOURCES += database.h database.cc
 libdatasrc_la_SOURCES += factory.h factory.cc
 libdatasrc_la_SOURCES += factory.h factory.cc
+libdatasrc_la_SOURCES += client_list.h client_list.cc
 nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 libdatasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
 libdatasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
 
 
-pkglib_LTLIBRARIES =  sqlite3_ds.la memory_ds.la
+pkglib_LTLIBRARIES =  sqlite3_ds.la memory_ds.la static_ds.la
 
 
 sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
 sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
 sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
 sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
@@ -49,6 +55,12 @@ memory_ds_la_LDFLAGS = -module -avoid-version
 memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 memory_ds_la_LIBADD += libdatasrc.la
 memory_ds_la_LIBADD += libdatasrc.la
 
 
+static_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
+static_ds_la_SOURCES += static_datasrc_link.cc
+static_ds_la_LDFLAGS = -module -avoid-version
+static_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+static_ds_la_LIBADD += libdatasrc.la
+
 libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
@@ -59,4 +71,7 @@ BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc
 datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes
 datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/datasrc_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/datasrc_messages.mes
 
 
-EXTRA_DIST = datasrc_messages.mes
+EXTRA_DIST = datasrc_messages.mes static.zone.pre
+
+zonedir = $(pkgdatadir)
+zone_DATA = static.zone

+ 162 - 0
src/lib/datasrc/client_list.cc

@@ -0,0 +1,162 @@
+// 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 "client_list.h"
+#include "client.h"
+#include "factory.h"
+
+#include <memory>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace datasrc {
+
+void
+ConfigurableClientList::configure(const Element& config, bool) {
+    // TODO: Implement the cache
+    // TODO: Implement recycling from the old configuration.
+    size_t i(0); // Outside of the try to be able to access it in the catch
+    try {
+        vector<DataSourceInfo> new_data_sources;
+        for (; i < config.size(); ++i) {
+            // Extract the parameters
+            const ConstElementPtr dconf(config.get(i));
+            const ConstElementPtr typeElem(dconf->get("type"));
+            if (typeElem == ConstElementPtr()) {
+                isc_throw(ConfigurationError, "Missing the type option in "
+                          "data source no " << i);
+            }
+            const string type(typeElem->stringValue());
+            ConstElementPtr paramConf(dconf->get("params"));
+            if (paramConf == ConstElementPtr()) {
+                paramConf.reset(new NullElement());
+            }
+            // TODO: Special-case the master files type.
+            // Ask the factory to create the data source for us
+            const DataSourcePair ds(this->getDataSourceClient(type,
+                                                              paramConf));
+            // And put it into the vector
+            new_data_sources.push_back(DataSourceInfo(ds.first, ds.second));
+        }
+        // If everything is OK up until now, we have the new configuration
+        // ready. So just put it there and let the old one die when we exit
+        // the scope.
+        data_sources_.swap(new_data_sources);
+    } catch (const TypeError& te) {
+        isc_throw(ConfigurationError, "Malformed configuration at data source "
+                  "no. " << i << ": " << te.what());
+    }
+}
+
+ClientList::FindResult
+ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
+                            bool) const
+{
+    // Nothing found yet.
+    //
+    // We have this class as a temporary storage, as the FindResult can't be
+    // assigned.
+    struct MutableResult {
+        MutableResult() :
+            datasrc_client(NULL),
+            matched_labels(0),
+            matched(false)
+        {}
+        DataSourceClient* datasrc_client;
+        ZoneFinderPtr finder;
+        uint8_t matched_labels;
+        bool matched;
+        operator FindResult() const {
+            // Conversion to the right result. If we return this, there was
+            // a partial match at best.
+            return (FindResult(datasrc_client, finder, false));
+        }
+    } candidate;
+
+    BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
+        // TODO: Once we have support for the caches, consider them too here
+        // somehow. This would probably get replaced by a function, that
+        // checks if there's a cache available, if it is, checks the loaded
+        // zones and zones expected to be in the real data source. If it is
+        // the cached one, provide the cached one. If it is in the external
+        // data source, use the datasource and don't provide the finder yet.
+        const DataSourceClient::FindResult result(
+            info.data_src_client_->findZone(name));
+        switch (result.code) {
+            case result::SUCCESS:
+                // If we found an exact match, we have no hope to getting
+                // a better one. Stop right here.
+
+                // TODO: In case we have only the datasource and not the finder
+                // and the need_updater parameter is true, get the zone there.
+                return (FindResult(info.data_src_client_, result.zone_finder,
+                                   true));
+            case result::PARTIALMATCH:
+                if (!want_exact_match) {
+                    // In case we have a partial match, check if it is better
+                    // than what we have. If so, replace it.
+                    //
+                    // We don't need the labels at the first partial match,
+                    // we have nothing to compare with. So we don't get it
+                    // (as a performance) and hope we will not need it at all.
+                    const uint8_t labels(candidate.matched ?
+                        result.zone_finder->getOrigin().getLabelCount() : 0);
+                    if (candidate.matched && candidate.matched_labels == 0) {
+                        // But if the hope turns out to be false, we need to
+                        // compute it for the first match anyway.
+                        candidate.matched_labels = candidate.finder->
+                            getOrigin().getLabelCount();
+                    }
+                    if (labels > candidate.matched_labels ||
+                        !candidate.matched) {
+                        // This one is strictly better. Replace it.
+                        candidate.datasrc_client = info.data_src_client_;
+                        candidate.finder = result.zone_finder;
+                        candidate.matched_labels = labels;
+                        candidate.matched = true;
+                    }
+                }
+                break;
+            default:
+                // Nothing found, nothing to do.
+                break;
+        }
+    }
+
+    // TODO: In case we have only the datasource and not the finder
+    // and the need_updater parameter is true, get the zone there.
+
+    // Return the partial match we have. In case we didn't want a partial
+    // match, this surely contains the original empty result.
+    return (candidate);
+}
+
+// NOTE: This function is not tested, it would be complicated. However, the
+// purpose of the function is to provide a very thin wrapper to be able to
+// replace the call to DataSourceClientContainer constructor in tests.
+ConfigurableClientList::DataSourcePair
+ConfigurableClientList::getDataSourceClient(const string& type,
+                                            const ConstElementPtr&
+                                            configuration)
+{
+    DataSourceClientContainerPtr
+        container(new DataSourceClientContainer(type, configuration));
+    return (DataSourcePair(&container->getInstance(), container));
+}
+
+}
+}

+ 289 - 0
src/lib/datasrc/client_list.h

@@ -0,0 +1,289 @@
+// 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_CONTAINER_H
+#define DATASRC_CONTAINER_H
+
+#include <dns/name.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace datasrc {
+
+class ZoneFinder;
+typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
+class DataSourceClient;
+typedef boost::shared_ptr<DataSourceClient> DataSourceClientPtr;
+class DataSourceClientContainer;
+typedef boost::shared_ptr<DataSourceClientContainer>
+    DataSourceClientContainerPtr;
+
+/// \brief The list of data source clients.
+///
+/// The purpose of this class is to hold several data source clients and search
+/// through them to find one containing a zone best matching a request.
+///
+/// All the data source clients should be for the same class. If you need
+/// to handle multiple classes, you need to create multiple separate lists.
+///
+/// This is an abstract base class. It is not expected we would use multiple
+/// implementation inside the servers (but it is not forbidden either), we
+/// have it to allow easy testing. It is possible to create a mock-up class
+/// instead of creating a full-blown configuration. The real implementation
+/// is the ConfigurableClientList.
+class ClientList : public boost::noncopyable {
+protected:
+    /// \brief Constructor.
+    ///
+    /// It is protected to prevent accidental creation of the abstract base
+    /// class.
+    ClientList() {}
+public:
+    /// \brief Virtual destructor
+    virtual ~ClientList() {}
+    /// \brief Structure holding the (compound) result of find.
+    ///
+    /// As this is read-only structure, we don't bother to create accessors.
+    /// Instead, all the member variables are defined as const and can be
+    /// accessed directly.
+    struct FindResult {
+        /// \brief Constructor.
+        ///
+        /// It simply fills in the member variables according to the
+        /// parameters. See the member descriptions for their meaning.
+        FindResult(DataSourceClient* dsrc_client, const ZoneFinderPtr& finder,
+                   bool exact_match) :
+            dsrc_client_(dsrc_client),
+            finder_(finder),
+            exact_match_(exact_match)
+        {}
+
+        /// \brief Negative answer constructor.
+        ///
+        /// This conscructs a result for negative answer. Both pointers are
+        /// NULL, and exact_match_ is false.
+        FindResult() :
+            dsrc_client_(NULL),
+            exact_match_(false)
+        {}
+
+        /// \brief Comparison operator.
+        ///
+        /// It is needed for tests and it might be of some use elsewhere
+        /// too.
+        bool operator ==(const FindResult& other) const {
+        return (dsrc_client_ == other.dsrc_client_ &&
+                finder_ == other.finder_ &&
+                exact_match_ == other.exact_match_);
+        }
+
+        /// \brief The found data source client.
+        ///
+        /// The client of the data source containing the best matching zone.
+        /// If no such data source exists, this is NULL pointer.
+        ///
+        /// Note that the pointer is valid only as long the ClientList which
+        /// returned the pointer is alive and was not reconfigured. The
+        /// ownership is preserved within the ClientList.
+        DataSourceClient* const dsrc_client_;
+
+        /// \brief The finder for the requested zone.
+        ///
+        /// This is the finder corresponding to the best matching zone.
+        /// This may be NULL even in case the datasrc_ is something
+        /// else, depending on the find options.
+        ///
+        /// \see find
+        const ZoneFinderPtr finder_;
+
+        /// \brief If the result is an exact match.
+        const bool exact_match_;
+    };
+
+    /// \brief Search for a zone through the data sources.
+    ///
+    /// This searches the contained data source clients for a one that best
+    /// matches the zone name.
+    ///
+    /// There are two expected usage scenarios. One is answering queries. In
+    /// this case, the zone finder is needed and the best matching superzone
+    /// of the searched name is needed. Therefore, the call would look like:
+    ///
+    /// \code FindResult result(list->find(queried_name));
+    ///   FindResult result(list->find(queried_name));
+    ///   if (result.datasrc_) {
+    ///       createTheAnswer(result.finder_);
+    ///   } else {
+    ///       createNotAuthAnswer();
+    /// } \endcode
+    ///
+    /// The other scenario is manipulating zone data (XfrOut, XfrIn, DDNS,
+    /// ...). In this case, the finder itself is not so important. However,
+    /// we need an exact match (if we want to manipulate zone data, we must
+    /// know exactly, which zone we are about to manipulate). Then the call
+    ///
+    /// \code FindResult result(list->find(zone_name, true, false));
+    ///   FindResult result(list->find(zone_name, true, false));
+    ///   if (result.datasrc_) {
+    ///       ZoneUpdaterPtr updater(result.datasrc_->getUpdater(zone_name);
+    ///       ...
+    /// } \endcode
+    ///
+    /// \param zone The name of the zone to look for.
+    /// \param want_exact_match If it is true, it returns only exact matches.
+    ///     If the best possible match is partial, a negative result is
+    ///     returned instead. It is possible the caller could check it and
+    ///     act accordingly if the result would be partial match, but with this
+    ///     set to true, the find might be actually faster under some
+    ///     circumstances.
+    /// \param want_finder If this is false, the finder_ member of FindResult
+    ///     might be NULL even if the corresponding data source is found. This
+    ///     is because of performance, in some cases the finder is a side
+    ///     result of the searching algorithm (therefore asking for it again
+    ///     would be a waste), but under other circumstances it is not, so
+    ///     providing it when it is not needed would also be wasteful.
+    ///
+    ///     Other things are never the side effect of searching, therefore the
+    ///     caller can get them explicitly (the updater, journal reader and
+    ///     iterator).
+    /// \return A FindResult describing the data source and zone with the
+    ///     longest match against the zone parameter.
+    virtual FindResult find(const dns::Name& zone,
+                            bool want_exact_match = false,
+                            bool want_finder = true) const = 0;
+};
+
+/// \brief Shared pointer to the list.
+typedef boost::shared_ptr<ClientList> ClientListPtr;
+/// \brief Shared const pointer to the list.
+typedef boost::shared_ptr<const ClientList> ConstClientListPtr;
+
+/// \Concrete implementation of the ClientList, which is constructed based on
+///     configuration.
+///
+/// This is the implementation which is expected to be used in the servers.
+/// However, it is expected most of the code will use it as the ClientList,
+/// only the creation is expected to be direct.
+///
+/// While it is possible to inherit this class, it is not expected to be
+/// inherited except for tests.
+class ConfigurableClientList : public ClientList {
+public:
+    /// \brief Exception thrown when there's an error in configuration.
+    class ConfigurationError : public Exception {
+    public:
+        ConfigurationError(const char* file, size_t line, const char* what) :
+            Exception(file, line, what)
+        {}
+    };
+
+    /// \brief Sets the configuration.
+    ///
+    /// This fills the ClientList with data source clients corresponding to the
+    /// configuration. The data source clients are newly created or recycled
+    /// from previous configuration.
+    ///
+    /// If any error is detected, an exception is thrown and the current
+    /// configuration is preserved.
+    ///
+    /// \param configuration The JSON element describing the configuration to
+    ///     use.
+    /// \param allow_cache If it is true, the 'cache' option of the
+    ///     configuration is used and some zones are cached into an In-Memory
+    ///     data source according to it. If it is false, it is ignored and
+    ///     no In-Memory data sources are created.
+    /// \throw DataSourceError if there's a problem creating a data source
+    ///     client.
+    /// \throw ConfigurationError if the configuration is invalid in some
+    ///     sense.
+    void configure(const data::Element& configuration, bool allow_cache);
+
+    /// \brief Implementation of the ClientList::find.
+    virtual FindResult find(const dns::Name& zone,
+                            bool want_exact_match = false,
+                            bool want_finder = true) const;
+
+    /// \brief This holds one data source client and corresponding information.
+    ///
+    /// \todo The content yet to be defined.
+    struct DataSourceInfo {
+        /// \brief Default constructor.
+        ///
+        /// Don't use directly. It is here so the structure can live in
+        /// a vector.
+        DataSourceInfo() :
+            data_src_client_(NULL)
+        {}
+        DataSourceInfo(DataSourceClient* data_src_client,
+                       const DataSourceClientContainerPtr& container) :
+            data_src_client_(data_src_client),
+            container_(container)
+        {}
+        DataSourceClient* data_src_client_;
+        DataSourceClientContainerPtr container_;
+    };
+
+    /// \brief The collection of data sources.
+    typedef std::vector<DataSourceInfo> DataSources;
+protected:
+    /// \brief The data sources held here.
+    ///
+    /// All our data sources are stored here. It is protected to let the
+    /// tests in. You should consider it private if you ever want to
+    /// derive this class (which is not really recommended anyway).
+    DataSources data_sources_;
+
+    /// \brief Convenience type alias.
+    ///
+    /// \see getDataSource
+    typedef std::pair<DataSourceClient*, DataSourceClientContainerPtr>
+        DataSourcePair;
+
+    /// \brief Create a data source client of given type and configuration.
+    ///
+    /// This is a thin wrapper around the DataSourceClientContainer
+    /// constructor. The function is here to make it possible for tests
+    /// to replace the DataSourceClientContainer with something else.
+    /// Also, derived classes could want to create the data source clients
+    /// in a different way, though inheriting this class is not recommended.
+    ///
+    /// The parameters are the same as of the constructor.
+    /// \return Pair containing both the data source client and the container.
+    ///     The container might be NULL in the derived class, it is
+    ///     only stored so the data source client is properly destroyed when
+    ///     not needed. However, in such case, it is the caller's
+    ///     responsibility to ensure the data source client is deleted when
+    ///     needed.
+    virtual DataSourcePair getDataSourceClient(const std::string& type,
+                                               const data::ConstElementPtr&
+                                               configuration);
+public:
+    /// \brief Access to the data source clients.
+    ///
+    /// It can be used to examine the loaded list of data sources clients
+    /// directly. It is not known if it is of any use other than testing, but
+    /// it might be, so it is just made public (there's no real reason to
+    /// hide it).
+    const DataSources& getDataSources() const { return (data_sources_); }
+};
+
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_CONTAINER_H

+ 6 - 3
src/lib/datasrc/database.cc

@@ -450,7 +450,8 @@ DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
     const size_t remove_labels = name.getLabelCount() - origin_label_count;
     const size_t remove_labels = name.getLabelCount() - origin_label_count;
 
 
     // Go through all superdomains from the origin down searching for nodes
     // Go through all superdomains from the origin down searching for nodes
-    // that indicate a delegation (.e. NS or DNAME).
+    // that indicate a delegation (.e. NS or DNAME).  Note that we only check
+    // pure superdomains; delegation on an exact match will be detected later.
     for (int i = remove_labels; i > 0; --i) {
     for (int i = remove_labels; i > 0; --i) {
         const Name superdomain(name.split(i));
         const Name superdomain(name.split(i));
 
 
@@ -810,12 +811,14 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
     const FoundIterator cni(found.second.find(RRType::CNAME()));
     const FoundIterator cni(found.second.find(RRType::CNAME()));
     const FoundIterator wti(found.second.find(type));
     const FoundIterator wti(found.second.find(type));
 
 
-    if (!is_origin && (options & FIND_GLUE_OK) == 0 &&
+    if (!is_origin && (options & FIND_GLUE_OK) == 0 && type != RRType::DS() &&
         nsi != found.second.end()) {
         nsi != found.second.end()) {
         // A NS RRset was found at the domain we were searching for.  As it is
         // A NS RRset was found at the domain we were searching for.  As it is
         // not at the origin of the zone, it is a delegation and indicates that
         // not at the origin of the zone, it is a delegation and indicates that
         // this zone is not authoritative for the data. Just return the
         // this zone is not authoritative for the data. Just return the
-        // delegation information.
+        // delegation information, except:
+        // - when we are looking for glue records (FIND_GLUE_OK), or
+        // - when the query type is DS (which cancels the delegation)
         return (logAndCreateResult(name, wildname, type, DELEGATION,
         return (logAndCreateResult(name, wildname, type, DELEGATION,
                                    nsi->second,
                                    nsi->second,
                                    wild ? DATASRC_DATABASE_WILDCARD_NS :
                                    wild ? DATASRC_DATABASE_WILDCARD_NS :

+ 1 - 1
src/lib/datasrc/memory_datasrc.cc

@@ -1747,7 +1747,7 @@ generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) {
 }
 }
 
 
 void
 void
-InMemoryZoneFinder::load(const string& filename) {
+InMemoryZoneFinder::load(const std::string& filename) {
     LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
     LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
         arg(filename);
         arg(filename);
 
 

+ 12 - 0
src/lib/datasrc/static.zone.pre

@@ -0,0 +1,12 @@
+;; This is the content of the BIND./CH zone. It contains the version and
+;; authors (called VERSION.BIND. and AUTHORS.BIND.). You can add more or
+;; modify the zone. Then you can reload the zone by issuing the command
+;;
+;;   loadzone CH BIND
+;;
+;; in the bindctl.
+
+BIND.           0   CH  SOA bind. authors.bind. 0 28800 7200 604800 86400
+
+VERSION.BIND.   0   CH  TXT "@@VERSION_STRING@@"
+;; HOSTNAME.BIND    0   CH  TXT "localhost"

+ 62 - 0
src/lib/datasrc/static_datasrc_link.cc

@@ -0,0 +1,62 @@
+// 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 "client.h"
+#include "memory_datasrc.h"
+
+#include <cc/data.h>
+#include <dns/rrclass.h>
+
+#include <memory>
+#include <exception>
+
+using namespace isc::data;
+using namespace isc::dns;
+using namespace boost;
+using namespace std;
+
+namespace isc {
+namespace datasrc {
+
+DataSourceClient*
+createInstance(ConstElementPtr config, string& error) {
+    try {
+        // Create the data source
+        auto_ptr<InMemoryClient> client(new InMemoryClient());
+        // Hardcode the origin and class
+        shared_ptr<InMemoryZoneFinder>
+            finder(new InMemoryZoneFinder(RRClass::CH(), Name("BIND")));
+        // Fill it with data
+        const string path(config->stringValue());
+        finder->load(path);
+        // And put the zone inside
+        client->addZone(finder);
+        return (client.release());
+    }
+    catch (const std::exception& e) {
+        error = e.what();
+    }
+    catch (...) {
+        error = "Unknown exception";
+    }
+    return (NULL);
+}
+
+void
+destroyInstance(DataSourceClient* instance) {
+    delete instance;
+}
+
+}
+}

+ 2 - 0
src/lib/datasrc/tests/Makefile.am

@@ -59,6 +59,7 @@ run_unittests_SOURCES += memory_datasrc_unittest.cc
 run_unittests_SOURCES += rbnode_rrset_unittest.cc
 run_unittests_SOURCES += rbnode_rrset_unittest.cc
 run_unittests_SOURCES += zone_finder_context_unittest.cc
 run_unittests_SOURCES += zone_finder_context_unittest.cc
 run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
 run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
+run_unittests_SOURCES += client_list_unittest.cc
 
 
 # We need the actual module implementation in the tests (they are not part
 # We need the actual module implementation in the tests (they are not part
 # of libdatasrc)
 # of libdatasrc)
@@ -113,3 +114,4 @@ EXTRA_DIST += testdata/test.sqlite3
 EXTRA_DIST += testdata/new_minor_schema.sqlite3
 EXTRA_DIST += testdata/new_minor_schema.sqlite3
 EXTRA_DIST += testdata/newschema.sqlite3
 EXTRA_DIST += testdata/newschema.sqlite3
 EXTRA_DIST += testdata/oldschema.sqlite3
 EXTRA_DIST += testdata/oldschema.sqlite3
+EXTRA_DIST += testdata/static.zone

+ 475 - 0
src/lib/datasrc/tests/client_list_unittest.cc

@@ -0,0 +1,475 @@
+// 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/client_list.h>
+#include <datasrc/client.h>
+#include <datasrc/data_source.h>
+
+#include <dns/rrclass.h>
+
+#include <gtest/gtest.h>
+
+#include <set>
+
+using namespace isc::datasrc;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace boost;
+using namespace std;
+
+namespace {
+
+// A test data source. It pretends it has some zones.
+class MockDataSourceClient : public DataSourceClient {
+public:
+    class Finder : public ZoneFinder {
+    public:
+        Finder(const Name& origin) :
+            origin_(origin)
+        {}
+        Name getOrigin() const { return (origin_); }
+        // The rest is not to be called, so just have them
+        RRClass getClass() const {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        shared_ptr<Context> find(const Name&, const RRType&,
+                                 const FindOptions)
+        {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        shared_ptr<Context> findAll(const Name&,
+                                    vector<ConstRRsetPtr>&,
+                                    const FindOptions)
+        {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        FindNSEC3Result findNSEC3(const Name&, bool) {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        Name findPreviousName(const Name&) const {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+    private:
+        Name origin_;
+    };
+    // Constructor from a list of zones.
+    MockDataSourceClient(const char* zone_names[]) {
+        for (const char** zone(zone_names); *zone; ++zone) {
+            zones.insert(Name(*zone));
+        }
+    }
+    // Constructor from configuration. The list of zones will be empty, but
+    // it will keep the configuration inside for further inspection.
+    MockDataSourceClient(const string& type,
+                         const ConstElementPtr& configuration) :
+        type_(type),
+        configuration_(configuration)
+    {}
+    virtual FindResult findZone(const Name& name) const {
+        if (zones.empty()) {
+            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
+        set<Name>::const_iterator it(zones.upper_bound(name));
+        if (it == zones.begin()) {
+            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
+        --it;
+        NameComparisonResult compar(it->compare(name));
+        const ZoneFinderPtr finder(new Finder(*it));
+        switch (compar.getRelation()) {
+            case NameComparisonResult::EQUAL:
+                return (FindResult(result::SUCCESS, finder));
+            case NameComparisonResult::SUPERDOMAIN:
+                return (FindResult(result::PARTIALMATCH, finder));
+            default:
+                return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
+    }
+    // These methods are not used. They just need to be there to have
+    // complete vtable.
+    virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+        getJournalReader(const Name&, uint32_t, uint32_t) const
+    {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    const string type_;
+    const ConstElementPtr configuration_;
+private:
+    set<Name> zones;
+};
+
+
+// The test version is the same as the normal version. We, however, add
+// some methods to dig directly in the internals, for the tests.
+class TestedList : public ConfigurableClientList {
+public:
+    DataSources& getDataSources() { return (data_sources_); }
+    // Overwrite the list's method to get a data source with given type
+    // and configuration. We mock the data source and don't create the
+    // container. This is just to avoid some complexity in the tests.
+    virtual DataSourcePair getDataSourceClient(const string& type,
+                                               const ConstElementPtr&
+                                               configuration)
+    {
+        if (type == "error") {
+            isc_throw(DataSourceError, "The error data source type");
+        }
+        shared_ptr<MockDataSourceClient>
+            ds(new MockDataSourceClient(type, configuration));
+        // Make sure it is deleted when the test list is deleted.
+        to_delete_.push_back(ds);
+        return (DataSourcePair(ds.get(), DataSourceClientContainerPtr()));
+    }
+private:
+    // Hold list of data sources created internally, so they are preserved
+    // until the end of the test and then deleted.
+    vector<shared_ptr<MockDataSourceClient> > to_delete_;
+};
+
+const char* ds_zones[][3] = {
+    {
+        "example.org.",
+        "example.com.",
+        NULL
+    },
+    {
+        "sub.example.org.",
+        NULL, NULL
+    },
+    {
+        NULL, NULL, NULL
+    },
+    {
+        "sub.example.org.",
+        NULL, NULL
+    }
+};
+
+const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones));
+
+class ListTest : public ::testing::Test {
+public:
+    ListTest() :
+        // The empty list corresponds to a list with no elements inside
+        list_(new TestedList()),
+        config_elem_(Element::fromJSON("["
+            "{"
+            "   \"type\": \"test_type\","
+            "   \"cache\": \"off\","
+            "   \"params\": {}"
+            "}]"))
+    {
+        for (size_t i(0); i < ds_count; ++ i) {
+            shared_ptr<MockDataSourceClient>
+                ds(new MockDataSourceClient(ds_zones[i]));
+            ds_.push_back(ds);
+            ds_info_.push_back(ConfigurableClientList::DataSourceInfo(ds.get(),
+                DataSourceClientContainerPtr()));
+        }
+    }
+    // Check the positive result is as we expect it.
+    void positiveResult(const ClientList::FindResult& result,
+                        const shared_ptr<MockDataSourceClient>& dsrc,
+                        const Name& name, bool exact,
+                        const char* test)
+    {
+        SCOPED_TRACE(test);
+        EXPECT_EQ(dsrc.get(), result.dsrc_client_);
+        ASSERT_NE(ZoneFinderPtr(), result.finder_);
+        EXPECT_EQ(name, result.finder_->getOrigin());
+        EXPECT_EQ(exact, result.exact_match_);
+    }
+    // Configure the list with multiple data sources, according to
+    // some configuration. It uses the index as parameter, to be able to
+    // loop through the configurations.
+    void multiConfiguration(size_t index) {
+        list_->getDataSources().clear();
+        switch (index) {
+            case 2:
+                list_->getDataSources().push_back(ds_info_[2]);
+                // The ds_[2] is empty. We just check that it doesn't confuse
+                // us. Fall through to the case 0.
+            case 0:
+                list_->getDataSources().push_back(ds_info_[0]);
+                list_->getDataSources().push_back(ds_info_[1]);
+                break;
+            case 1:
+                // The other order
+                list_->getDataSources().push_back(ds_info_[1]);
+                list_->getDataSources().push_back(ds_info_[0]);
+                break;
+            case 3:
+                list_->getDataSources().push_back(ds_info_[1]);
+                list_->getDataSources().push_back(ds_info_[0]);
+                // It is the same as ds_[1], but we take from the first one.
+                // The first one to match is the correct one.
+                list_->getDataSources().push_back(ds_info_[3]);
+                break;
+            default:
+                FAIL() << "Unknown configuration index " << index;
+        }
+    }
+    void checkDS(size_t index, const string& type, const string& params) const
+    {
+        ASSERT_GT(list_->getDataSources().size(), index);
+        MockDataSourceClient* ds(dynamic_cast<MockDataSourceClient*>(
+            list_->getDataSources()[index].data_src_client_));
+
+        // Comparing with NULL does not work
+        ASSERT_NE(ds, static_cast<const MockDataSourceClient*>(NULL));
+        EXPECT_EQ(type, ds->type_);
+        EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_));
+    }
+    shared_ptr<TestedList> list_;
+    const ClientList::FindResult negativeResult_;
+    vector<shared_ptr<MockDataSourceClient> > ds_;
+    vector<ConfigurableClientList::DataSourceInfo> ds_info_;
+    const ConstElementPtr config_elem_;
+};
+
+// Test the test itself
+TEST_F(ListTest, selfTest) {
+    EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
+    EXPECT_EQ(result::PARTIALMATCH,
+              ds_[0]->findZone(Name("sub.example.org")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("org")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
+}
+
+// Test the list we create with empty configuration is, in fact, empty
+TEST_F(ListTest, emptyList) {
+    EXPECT_TRUE(list_->getDataSources().empty());
+}
+
+// Check the values returned by a find on an empty list. It should be
+// a negative answer (nothing found) no matter if we want an exact or inexact
+// match.
+TEST_F(ListTest, emptySearch) {
+    // No matter what we try, we don't get an answer.
+
+    // Note: we don't have operator<< for the result class, so we cannot use
+    // EXPECT_EQ.  Same for other similar cases.
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), false,
+                                               false));
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), false,
+                                               true));
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), true,
+                                               false));
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), true,
+                                               true));
+}
+
+// Put a single data source inside the list and check it can find an
+// exact match if there's one.
+TEST_F(ListTest, singleDSExactMatch) {
+    list_->getDataSources().push_back(ds_info_[0]);
+    // This zone is not there
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("org."), true));
+    // But this one is, so check it.
+    positiveResult(list_->find(Name("example.org"), true), ds_[0],
+                   Name("example.org"), true, "Exact match");
+    // When asking for a sub zone of a zone there, we get nothing
+    // (we want exact match, this would be partial one)
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("sub.example.org."),
+                                               true));
+}
+
+// When asking for a partial match, we get all that the exact one, but more.
+TEST_F(ListTest, singleDSBestMatch) {
+    list_->getDataSources().push_back(ds_info_[0]);
+    // This zone is not there
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("org.")));
+    // But this one is, so check it.
+    positiveResult(list_->find(Name("example.org")), ds_[0],
+                   Name("example.org"), true, "Exact match");
+    // When asking for a sub zone of a zone there, we get the parent
+    // one.
+    positiveResult(list_->find(Name("sub.example.org.")), ds_[0],
+                   Name("example.org"), false, "Subdomain match");
+}
+
+const char* const test_names[] = {
+    "Sub second",
+    "Sub first",
+    "With empty",
+    "With a duplicity"
+};
+
+TEST_F(ListTest, multiExactMatch) {
+    // Run through all the multi-configurations
+    for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) {
+        SCOPED_TRACE(test_names[i]);
+        multiConfiguration(i);
+        // Something that is nowhere there
+        EXPECT_TRUE(negativeResult_ == list_->find(Name("org."), true));
+        // This one is there exactly.
+        positiveResult(list_->find(Name("example.org"), true), ds_[0],
+                       Name("example.org"), true, "Exact match");
+        // This one too, but in a different data source.
+        positiveResult(list_->find(Name("sub.example.org."), true), ds_[1],
+                       Name("sub.example.org"), true, "Subdomain match");
+        // But this one is in neither data source.
+        EXPECT_TRUE(negativeResult_ ==
+                    list_->find(Name("sub.example.com."), true));
+    }
+}
+
+TEST_F(ListTest, multiBestMatch) {
+    // Run through all the multi-configurations
+    for (size_t i(0); i < 4; ++ i) {
+        SCOPED_TRACE(test_names[i]);
+        multiConfiguration(i);
+        // Something that is nowhere there
+        EXPECT_TRUE(negativeResult_ == list_->find(Name("org.")));
+        // This one is there exactly.
+        positiveResult(list_->find(Name("example.org")), ds_[0],
+                       Name("example.org"), true, "Exact match");
+        // This one too, but in a different data source.
+        positiveResult(list_->find(Name("sub.example.org.")), ds_[1],
+                       Name("sub.example.org"), true, "Subdomain match");
+        // But this one is in neither data source. But it is a subdomain
+        // of one of the zones in the first data source.
+        positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
+                       Name("example.com."), false, "Subdomain in com");
+    }
+}
+
+// Check the configuration is empty when the list is empty
+TEST_F(ListTest, configureEmpty) {
+    ConstElementPtr elem(new ListElement);
+    list_->configure(*elem, true);
+    EXPECT_TRUE(list_->getDataSources().empty());
+}
+
+// Check we can get multiple data sources and they are in the right order.
+TEST_F(ListTest, configureMulti) {
+    ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"type1\","
+        "   \"cache\": \"off\","
+        "   \"params\": {}"
+        "},"
+        "{"
+        "   \"type\": \"type2\","
+        "   \"cache\": \"off\","
+        "   \"params\": {}"
+        "}]"
+    ));
+    list_->configure(*elem, true);
+    EXPECT_EQ(2, list_->getDataSources().size());
+    checkDS(0, "type1", "{}");
+    checkDS(1, "type2", "{}");
+}
+
+// Check we can pass whatever we want to the params
+TEST_F(ListTest, configureParams) {
+    const char* params[] = {
+        "true",
+        "false",
+        "null",
+        "\"hello\"",
+        "42",
+        "[]",
+        "{}",
+        NULL
+    };
+    for (const char** param(params); *param; ++param) {
+        SCOPED_TRACE(*param);
+        ConstElementPtr elem(Element::fromJSON(string("["
+            "{"
+            "   \"type\": \"t\","
+            "   \"cache\": \"off\","
+            "   \"params\": ") + *param +
+            "}]"));
+        list_->configure(*elem, true);
+        EXPECT_EQ(1, list_->getDataSources().size());
+        checkDS(0, "t", *param);
+    }
+}
+
+TEST_F(ListTest, wrongConfig) {
+    const char* configs[] = {
+        // A lot of stuff missing from there
+        "[{\"type\": \"test_type\", \"params\": 13}, {}]",
+        // Some bad types completely
+        "{}",
+        "true",
+        "42",
+        "null",
+        "[{\"type\": \"test_type\", \"params\": 13}, true]",
+        "[{\"type\": \"test_type\", \"params\": 13}, []]",
+        "[{\"type\": \"test_type\", \"params\": 13}, 42]",
+        // Bad type of type
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": 42}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": true}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]",
+        // TODO: Once cache is supported, add some invalid cache values
+        NULL
+    };
+    // Put something inside to see it survives the exception
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+    for (const char** config(configs); *config; ++config) {
+        SCOPED_TRACE(*config);
+        ConstElementPtr elem(Element::fromJSON(*config));
+        EXPECT_THROW(list_->configure(*elem, true),
+                     ConfigurableClientList::ConfigurationError);
+        // Still untouched
+        checkDS(0, "test_type", "{}");
+        EXPECT_EQ(1, list_->getDataSources().size());
+    }
+}
+
+// The param thing defaults to null. Cache is not used yet.
+TEST_F(ListTest, defaults) {
+    ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"type1\""
+        "}]"));
+    list_->configure(*elem, true);
+    EXPECT_EQ(1, list_->getDataSources().size());
+    checkDS(0, "type1", "null");
+}
+
+// Check we can call the configure multiple times, to change the configuration
+TEST_F(ListTest, reconfigure) {
+    ConstElementPtr empty(new ListElement);
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+    list_->configure(*empty, true);
+    EXPECT_TRUE(list_->getDataSources().empty());
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+}
+
+// Make sure the data source error exception from the factory is propagated
+TEST_F(ListTest, dataSrcError) {
+    ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"error\""
+        "}]"));
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+    EXPECT_THROW(list_->configure(*elem, true), DataSourceError);
+    checkDS(0, "test_type", "{}");
+}
+
+}

+ 55 - 1
src/lib/datasrc/tests/database_unittest.cc

@@ -142,9 +142,11 @@ const char* const TEST_RECORDS[][5] = {
     {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
     {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
     {"delegation.example.org.", "NS", "3600", "",
     {"delegation.example.org.", "NS", "3600", "",
      "ns.delegation.example.org."},
      "ns.delegation.example.org."},
-    {"delegation.example.org.", "DS", "3600", "", "1 RSAMD5 2 abcd"},
+    {"delegation.example.org.", "DS", "3600", "", "1 1 2 abcd"},
     {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
     {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
      "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
      "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+    {"delegation.example.org.", "RRSIG", "3600", "", "DS 5 3 3600 "
+     "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
     {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
     {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
     {"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
     {"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
 
 
@@ -156,6 +158,16 @@ const char* const TEST_RECORDS[][5] = {
 
 
     {"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
     {"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
 
 
+    // Insecure delegation (i.e., no DS at the delegation point)
+    {"insecdelegation.example.org.", "NS", "3600", "", "ns.example.com."},
+    {"insecdelegation.example.org.", "NSEC", "3600", "",
+     "dummy.example.org. NS NSEC"},
+    // and a DS under the zone cut. Such an RR shouldn't exist in a sane zone,
+    // but it could by error or some malicious attempt.  It shouldn't confuse
+    // the implementation)
+    {"child.insecdelegation.example.org.", "DS", "3600", "", "DS 5 3 3600 "
+     "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
     // Broken NS
     // Broken NS
     {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
     {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
     {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
     {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
@@ -2201,6 +2213,48 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
                  DataSourceError);
                  DataSourceError);
 }
 }
 
 
+TYPED_TEST(DatabaseClientTest, findDS) {
+    // Type DS query is an exception to the general delegation case; the NS
+    // should be ignored and it should be treated just like normal
+    // authoritative data.
+
+    boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+    // DS exists at the delegation point.  It should be returned with result
+    // code of SUCCESS.
+    this->expected_rdatas_.push_back("1 1 2 abcd"),
+    this->expected_sig_rdatas_.push_back("DS 5 3 3600 20000101000000 "
+                                         "20000201000000 12345 example.org. "
+                                         "FAKEFAKEFAKE");
+    doFindTest(*finder, Name("delegation.example.org."),
+               RRType::DS(), RRType::DS(), this->rrttl_, ZoneFinder::SUCCESS,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_DEFAULT);
+
+    // DS doesn't exist at the delegation point.  The result should be
+    // NXRRSET, and if DNSSEC is requested and the zone is NSEC-signed,
+    // the corresponding NSEC should be returned (normally with its RRSIG,
+    // but in this simplified test setup it's omitted in the test data).
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("dummy.example.org. NS NSEC");
+    this->expected_sig_rdatas_.clear();
+    doFindTest(*finder, Name("insecdelegation.example.org."),
+               RRType::DS(), RRType::NSEC(), this->rrttl_, ZoneFinder::NXRRSET,
+               this->expected_rdatas_, this->expected_sig_rdatas_,
+               ZoneFinder::RESULT_NSEC_SIGNED,
+               Name("insecdelegation.example.org."), ZoneFinder::FIND_DNSSEC);
+
+    // Some insane case: DS under a zone cut.  It's included in the DB, but
+    // shouldn't be visible via finder.
+    this->expected_rdatas_.clear();
+    this->expected_rdatas_.push_back("ns.example.com");
+    doFindTest(*finder, Name("child.insecdelegation.example.org"),
+               RRType::DS(), RRType::NS(), this->rrttl_,
+               ZoneFinder::DELEGATION, this->expected_rdatas_,
+               this->empty_rdatas_, ZoneFinder::RESULT_DEFAULT,
+               Name("insecdelegation.example.org."), ZoneFinder::FIND_DNSSEC);
+}
+
 TYPED_TEST(DatabaseClientTest, emptyDomain) {
 TYPED_TEST(DatabaseClientTest, emptyDomain) {
     boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
     boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
 
 

+ 56 - 0
src/lib/datasrc/tests/factory_unittest.cc

@@ -28,6 +28,8 @@ using namespace isc::datasrc;
 using namespace isc::data;
 using namespace isc::data;
 
 
 std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
 std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
+const std::string STATIC_DS_FILE = TEST_DATA_DIR "/static.zone";
+const std::string ROOT_ZONE_FILE = TEST_DATA_DIR "/root.zone";
 
 
 namespace {
 namespace {
 
 
@@ -235,5 +237,59 @@ TEST(FactoryTest, badType) {
                                            DataSourceError);
                                            DataSourceError);
 }
 }
 
 
+// Check the static data source can be loaded.
+TEST(FactoryTest, staticDS) {
+    // The only configuration is the file to load.
+    const ConstElementPtr config(new StringElement(STATIC_DS_FILE));
+    // Get the data source
+    DataSourceClientContainer dsc("static", config);
+    // And try getting something out to see if it really works.
+    DataSourceClient::FindResult
+        result(dsc.getInstance().findZone(isc::dns::Name("BIND")));
+    ASSERT_EQ(result::SUCCESS, result.code);
+    EXPECT_EQ(isc::dns::Name("BIND"), result.zone_finder->getOrigin());
+    EXPECT_EQ(isc::dns::RRClass::CH(), result.zone_finder->getClass());
+    const isc::dns::ConstRRsetPtr
+        version(result.zone_finder->find(isc::dns::Name("VERSION.BIND"),
+                                         isc::dns::RRType::TXT())->rrset);
+    ASSERT_NE(isc::dns::ConstRRsetPtr(), version);
+    EXPECT_EQ(isc::dns::Name("VERSION.BIND"), version->getName());
+    EXPECT_EQ(isc::dns::RRClass::CH(), version->getClass());
+    EXPECT_EQ(isc::dns::RRType::TXT(), version->getType());
+}
+
+// Check that file not containing BIND./CH is rejected
+//
+// FIXME: This test is disabled because the InMemoryZoneFinder::load does
+// not check if the data loaded correspond with the origin. The static
+// factory is not the place to fix that.
+TEST(FactoryTest, DISABLED_staticDSBadFile) {
+    // The only configuration is the file to load.
+    const ConstElementPtr config(new StringElement(STATIC_DS_FILE));
+    // See it does not want the file
+    EXPECT_THROW(DataSourceClientContainer("static", config), DataSourceError);
+}
+
+// Check that some bad configs are rejected
+TEST(FactoryTest, staticDSBadConfig) {
+    const char* configs[] = {
+        // The file does not exist
+        "\"/does/not/exist\"",
+        // Bad types
+        "null",
+        "42",
+        "{}",
+        "[]",
+        "true",
+        NULL
+    };
+    for (const char** config(configs); *config; ++config) {
+        SCOPED_TRACE(*config);
+        EXPECT_THROW(DataSourceClientContainer("static",
+                                               Element::fromJSON(*config)),
+                     DataSourceError);
+    }
+}
+
 } // end anonymous namespace
 } // end anonymous namespace
 
 

+ 2 - 0
src/lib/datasrc/tests/testdata/static.zone

@@ -0,0 +1,2 @@
+BIND.           3600    CH  SOA BIND. BIND. 1 3600 300 36000 3600
+VERSION.BIND.   3600    CH  TXT "10"

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

@@ -5,6 +5,12 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
 CLEANFILES = *.gcno *.gcda
 CLEANFILES = *.gcno *.gcda
 
 
 lib_LTLIBRARIES = libdhcp++.la
 lib_LTLIBRARIES = libdhcp++.la
@@ -31,3 +37,9 @@ libdhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libdhcp___la_LIBADD   = $(top_builddir)/src/lib/asiolink/libasiolink.la
 libdhcp___la_LIBADD   = $(top_builddir)/src/lib/asiolink/libasiolink.la
 libdhcp___la_LIBADD  += $(top_builddir)/src/lib/util/libutil.la
 libdhcp___la_LIBADD  += $(top_builddir)/src/lib/util/libutil.la
 libdhcp___la_LDFLAGS  = -no-undefined -version-info 1:0:0
 libdhcp___la_LDFLAGS  = -no-undefined -version-info 1:0:0
+
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libdhcp___la_CXXFLAGS += -Wno-unused-parameter
+endif

+ 8 - 0
src/lib/dhcp/iface_mgr.cc

@@ -606,6 +606,8 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
     pktinfo->ipi6_ifindex = pkt->getIndex();
     pktinfo->ipi6_ifindex = pkt->getIndex();
     m.msg_controllen = cmsg->cmsg_len;
     m.msg_controllen = cmsg->cmsg_len;
 
 
+    pkt->updateTimestamp();
+
     result = sendmsg(getSocket(*pkt), &m, 0);
     result = sendmsg(getSocket(*pkt), &m, 0);
     if (result < 0) {
     if (result < 0) {
         isc_throw(Unexpected, "Pkt6 send failed: sendmsg() returned " << result);
         isc_throw(Unexpected, "Pkt6 send failed: sendmsg() returned " << result);
@@ -665,6 +667,8 @@ IfaceMgr::send(const Pkt4Ptr& pkt)
          << " over socket " << getSocket(*pkt) << " on interface "
          << " over socket " << getSocket(*pkt) << " on interface "
          << getIface(pkt->getIface())->getFullName() << endl;
          << getIface(pkt->getIface())->getFullName() << endl;
 
 
+    pkt->updateTimestamp();
+
     int result = sendmsg(getSocket(*pkt), &m, 0);
     int result = sendmsg(getSocket(*pkt), &m, 0);
     if (result < 0) {
     if (result < 0) {
         isc_throw(Unexpected, "Pkt4 send failed.");
         isc_throw(Unexpected, "Pkt4 send failed.");
@@ -755,6 +759,8 @@ IfaceMgr::receive4() {
     // We have all data let's create Pkt4 object.
     // We have all data let's create Pkt4 object.
     Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
     Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
 
 
+    pkt->updateTimestamp();
+
     unsigned int ifindex = iface->getIndex();
     unsigned int ifindex = iface->getIndex();
 
 
     IOAddress from(htonl(from_addr.sin_addr.s_addr));
     IOAddress from(htonl(from_addr.sin_addr.s_addr));
@@ -899,6 +905,8 @@ Pkt6Ptr IfaceMgr::receive6() {
         return (Pkt6Ptr()); // NULL
         return (Pkt6Ptr()); // NULL
     }
     }
 
 
+    pkt->updateTimestamp();
+
     pkt->setLocalAddr(IOAddress::from_bytes(AF_INET6,
     pkt->setLocalAddr(IOAddress::from_bytes(AF_INET6,
                       reinterpret_cast<const uint8_t*>(&to_addr)));
                       reinterpret_cast<const uint8_t*>(&to_addr)));
     pkt->setRemoteAddr(IOAddress::from_bytes(AF_INET6,
     pkt->setRemoteAddr(IOAddress::from_bytes(AF_INET6,

+ 8 - 0
src/lib/dhcp/option.cc

@@ -270,6 +270,14 @@ void Option::setUint32(uint32_t value) {
   writeUint32(value, &data_[0]);
   writeUint32(value, &data_[0]);
 }
 }
 
 
+void Option::setData(const OptionBufferConstIter first,
+                     const OptionBufferConstIter last) {
+    // We will copy entire option buffer, so we have to resize data_.
+    data_.resize(std::distance(first, last));
+    std::copy(first, last, data_.begin());
+}
+
+
 Option::~Option() {
 Option::~Option() {
 
 
 }
 }

+ 9 - 0
src/lib/dhcp/option.h

@@ -244,6 +244,15 @@ public:
     /// @param value value to be set
     /// @param value value to be set
     void setUint32(uint32_t value);
     void setUint32(uint32_t value);
 
 
+    /// @brief Sets content of this option from buffer.
+    ///
+    /// Option will be resized to length of buffer.
+    ///
+    /// @param first iterator pointing begining of buffer to copy.
+    /// @param last iterator pointing to end of buffer to copy.
+    void setData(const OptionBufferConstIter first,
+                 const OptionBufferConstIter last);
+
     /// just to force that every option has virtual dtor
     /// just to force that every option has virtual dtor
     virtual ~Option();
     virtual ~Option();
 
 

+ 5 - 0
src/lib/dhcp/pkt4.cc

@@ -305,6 +305,11 @@ Pkt4::getOption(uint8_t type) {
     return boost::shared_ptr<isc::dhcp::Option>(); // NULL
     return boost::shared_ptr<isc::dhcp::Option>(); // NULL
 }
 }
 
 
+void
+Pkt4::updateTimestamp() {
+    timestamp_ = boost::posix_time::microsec_clock::universal_time();
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc::dhcp
 
 
 } // end of namespace isc
 } // end of namespace isc

+ 47 - 0
src/lib/dhcp/pkt4.h

@@ -16,8 +16,10 @@
 #define PKT4_H
 #define PKT4_H
 
 
 #include <iostream>
 #include <iostream>
+#include <time.h>
 #include <vector>
 #include <vector>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include "asiolink/io_address.h"
 #include "asiolink/io_address.h"
 #include "util/buffer.h"
 #include "util/buffer.h"
 #include "dhcp/option.h"
 #include "dhcp/option.h"
@@ -202,6 +204,11 @@ public:
     void
     void
     setGiaddr(const isc::asiolink::IOAddress& giaddr) { giaddr_ = giaddr; };
     setGiaddr(const isc::asiolink::IOAddress& giaddr) { giaddr_ = giaddr; };
 
 
+    /// @brief Sets transaction-id value
+    ///
+    /// @param transid transaction-id to be set.
+    void setTransid(uint32_t transid) { transid_ = transid; }
+
     /// @brief Returns value of transaction-id field.
     /// @brief Returns value of transaction-id field.
     ///
     ///
     /// @return transaction-id
     /// @return transaction-id
@@ -321,6 +328,14 @@ public:
     /// @return interface name
     /// @return interface name
     std::string getIface() const { return iface_; };
     std::string getIface() const { return iface_; };
 
 
+    /// @brief Returns packet timestamp.
+    ///
+    /// Returns packet timestamp value updated when
+    /// packet is received or send.
+    ///
+    /// @return packet timestamp.
+    const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
+
     /// @brief Sets interface name.
     /// @brief Sets interface name.
     ///
     ///
     /// Sets interface name over which packet was received or is
     /// Sets interface name over which packet was received or is
@@ -387,6 +402,14 @@ public:
     /// @return remote port
     /// @return remote port
     uint16_t getRemotePort() { return (remote_port_); }
     uint16_t getRemotePort() { return (remote_port_); }
 
 
+    /// @brief Update packet timestamp.
+    ///
+    /// Updates packet timestamp. This method is invoked
+    /// by interface manager just before sending or
+    /// just after receiving it.
+    /// @throw isc::Unexpected if timestamp update failed
+    void updateTimestamp();
+
 protected:
 protected:
 
 
     /// converts DHCP message type to BOOTP op type
     /// converts DHCP message type to BOOTP op type
@@ -470,12 +493,26 @@ protected:
     // end of real DHCPv4 fields
     // end of real DHCPv4 fields
 
 
     /// output buffer (used during message transmission)
     /// output buffer (used during message transmission)
+    ///
+    /// @warning This protected member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
     isc::util::OutputBuffer bufferOut_;
     isc::util::OutputBuffer bufferOut_;
 
 
     /// that's the data of input buffer used in RX packet. Note that
     /// that's the data of input buffer used in RX packet. Note that
     /// InputBuffer does not store the data itself, but just expects that
     /// InputBuffer does not store the data itself, but just expects that
     /// data will be valid for the whole life of InputBuffer. Therefore we
     /// data will be valid for the whole life of InputBuffer. Therefore we
     /// need to keep the data around.
     /// need to keep the data around.
+    ///
+    /// @warning This protected member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
     std::vector<uint8_t> data_;
     std::vector<uint8_t> data_;
 
 
     /// message type (e.g. 1=DHCPDISCOVER)
     /// message type (e.g. 1=DHCPDISCOVER)
@@ -484,7 +521,17 @@ protected:
     uint8_t msg_type_;
     uint8_t msg_type_;
 
 
     /// collection of options present in this message
     /// collection of options present in this message
+    ///
+    /// @warnig This protected member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
     isc::dhcp::Option::OptionCollection options_;
     isc::dhcp::Option::OptionCollection options_;
+
+    /// packet timestamp
+    boost::posix_time::ptime timestamp_;
 }; // Pkt4 class
 }; // Pkt4 class
 
 
 typedef boost::shared_ptr<Pkt4> Pkt4Ptr;
 typedef boost::shared_ptr<Pkt4> Pkt4Ptr;

+ 6 - 0
src/lib/dhcp/pkt6.cc

@@ -202,5 +202,11 @@ void Pkt6::repack() {
     bufferOut_.writeData(&data_[0], data_.size());
     bufferOut_.writeData(&data_[0], data_.size());
 }
 }
 
 
+void
+Pkt6::updateTimestamp() {
+    timestamp_ = boost::posix_time::microsec_clock::universal_time();
+}
+
+
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace

+ 47 - 0
src/lib/dhcp/pkt6.h

@@ -16,8 +16,10 @@
 #define PKT6_H
 #define PKT6_H
 
 
 #include <iostream>
 #include <iostream>
+#include <time.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_array.hpp>
 #include <boost/shared_array.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include "asiolink/io_address.h"
 #include "asiolink/io_address.h"
 #include "dhcp/option.h"
 #include "dhcp/option.h"
 
 
@@ -129,6 +131,11 @@ public:
     /// @param type message type to be set
     /// @param type message type to be set
     void setType(uint8_t type) { msg_type_=type; };
     void setType(uint8_t type) { msg_type_=type; };
 
 
+    /// @brief Sets transaction-id value
+    ///
+    /// @param transid transaction-id to be set.
+    void setTransid(uint32_t transid) { transid_ = transid; }
+
     /// Returns value of transaction-id field
     /// Returns value of transaction-id field
     ///
     ///
     /// @return transaction-id
     /// @return transaction-id
@@ -220,6 +227,14 @@ public:
     /// @return interface name
     /// @return interface name
     std::string getIface() const { return iface_; };
     std::string getIface() const { return iface_; };
 
 
+    /// @brief Returns packet timestamp.
+    ///
+    /// Returns packet timestamp value updated when
+    /// packet is received or send.
+    ///
+    /// @return packet timestamp.
+    const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
+
     /// @brief Sets interface name.
     /// @brief Sets interface name.
     ///
     ///
     /// Sets interface name over which packet was received or is
     /// Sets interface name over which packet was received or is
@@ -231,8 +246,23 @@ public:
     /// TODO Need to implement getOptions() as well
     /// TODO Need to implement getOptions() as well
 
 
     /// collection of options present in this message
     /// collection of options present in this message
+    ///
+    /// @warning This protected member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
     isc::dhcp::Option::OptionCollection options_;
     isc::dhcp::Option::OptionCollection options_;
 
 
+    /// @brief Update packet timestamp.
+    ///
+    /// Updates packet timestamp. This method is invoked
+    /// by interface manager just before sending or
+    /// just after receiving it.
+    /// @throw isc::Unexpected if timestamp update failed
+    void updateTimestamp();
+
 protected:
 protected:
     /// Builds on wire packet for TCP transmission.
     /// Builds on wire packet for TCP transmission.
     ///
     ///
@@ -278,6 +308,13 @@ protected:
     uint32_t transid_;
     uint32_t transid_;
 
 
     /// unparsed data (in received packets)
     /// unparsed data (in received packets)
+    ///
+    /// @warning This protected member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
     OptionBuffer data_;
     OptionBuffer data_;
 
 
     /// name of the network interface the packet was received/to be sent over
     /// name of the network interface the packet was received/to be sent over
@@ -304,7 +341,17 @@ protected:
     uint16_t remote_port_;
     uint16_t remote_port_;
 
 
     /// output buffer (used during message transmission)
     /// output buffer (used during message transmission)
+    ///
+    /// @warning This protected member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
     isc::util::OutputBuffer bufferOut_;
     isc::util::OutputBuffer bufferOut_;
+
+    /// packet timestamp
+    boost::posix_time::ptime timestamp_;
 }; // Pkt6 class
 }; // Pkt6 class
 
 
 typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
 typedef boost::shared_ptr<Pkt6> Pkt6Ptr;

+ 9 - 2
src/lib/dhcp/tests/Makefile.am

@@ -7,6 +7,12 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
 if USE_STATIC_LINK
 if USE_STATIC_LINK
 AM_LDFLAGS = -static
 AM_LDFLAGS = -static
 endif
 endif
@@ -38,8 +44,9 @@ libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS)
 
 
 if USE_CLANGPP
 if USE_CLANGPP
 # This is to workaround unused variables tcout and tcerr in
 # This is to workaround unused variables tcout and tcerr in
-# log4cplus's streams.h.
-libdhcp___unittests_CXXFLAGS += -Wno-unused-variable
+# log4cplus's streams.h and unused parameters from some of the
+# Boost headers.
+libdhcp___unittests_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter
 endif
 endif
 libdhcp___unittests_LDADD  = $(GTEST_LDADD)
 libdhcp___unittests_LDADD  = $(GTEST_LDADD)
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la

+ 28 - 1
src/lib/dhcp/tests/option_unittest.cc

@@ -485,7 +485,7 @@ TEST_F(OptionTest, setUintX) {
     uint8_t exp2[] = {125, 2, 12345/256, 12345%256};
     uint8_t exp2[] = {125, 2, 12345/256, 12345%256};
     EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4));
     EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4));
 
 
-    // verity getUint32
+    // verify getUint32
     outBuf_.clear();
     outBuf_.clear();
     opt4->setUint32(0x12345678);
     opt4->setUint32(0x12345678);
     opt4->pack4(outBuf_);
     opt4->pack4(outBuf_);
@@ -495,4 +495,31 @@ TEST_F(OptionTest, setUintX) {
     uint8_t exp4[] = {125, 4, 0x12, 0x34, 0x56, 0x78};
     uint8_t exp4[] = {125, 4, 0x12, 0x34, 0x56, 0x78};
     EXPECT_TRUE(0 == memcmp(exp4, outBuf_.getData(), 6));
     EXPECT_TRUE(0 == memcmp(exp4, outBuf_.getData(), 6));
 }
 }
+
+TEST_F(OptionTest, setData) {
+    // verify data override with new buffer larger than
+    // initial option buffer size
+    OptionPtr opt1(new Option(Option::V4, 125,
+                              buf_.begin(), buf_.begin() + 10));
+    buf_.resize(20, 1);
+    opt1->setData(buf_.begin(), buf_.end());
+    opt1->pack4(outBuf_);
+    ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size());
+    const uint8_t* test_data = static_cast<const uint8_t*>(outBuf_.getData());
+    EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
+                            buf_.size()));
+
+    // verify data override with new buffer shorter than
+    // initial option buffer size
+    OptionPtr opt2(new Option(Option::V4, 125,
+                              buf_.begin(), buf_.begin() + 10));
+    outBuf_.clear();
+    buf_.resize(5, 1);
+    opt2->setData(buf_.begin(), buf_.end());
+    opt2->pack4(outBuf_);
+    ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size());
+    test_data = static_cast<const uint8_t*>(outBuf_.getData());
+    EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
+                            buf_.size()));
+}
 }
 }

+ 31 - 1
src/lib/dhcp/tests/pkt4_unittest.cc

@@ -31,7 +31,9 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 using namespace isc::util;
 using namespace isc::util;
-using namespace boost;
+// don't import the entire boost namespace.  It will unexpectedly hide uint8_t
+// for some systems.
+using boost::scoped_ptr;
 
 
 namespace {
 namespace {
 
 
@@ -598,4 +600,32 @@ TEST(Pkt4Test, metaFields) {
     delete pkt;
     delete pkt;
 }
 }
 
 
+TEST(Pkt4Test, Timestamp) {
+    scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+
+    // Just after construction timestamp is invalid
+    ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time());
+
+    // Update packet time.
+    pkt->updateTimestamp();
+
+    // Get updated packet time.
+    boost::posix_time::ptime ts_packet = pkt->getTimestamp();
+
+    // After timestamp is updated it should be date-time.
+    ASSERT_FALSE(ts_packet.is_not_a_date_time());
+
+    // Check current time.
+    boost::posix_time::ptime ts_now =
+        boost::posix_time::microsec_clock::universal_time();
+
+    // Calculate period between packet time and now.
+    boost::posix_time::time_period ts_period(ts_packet, ts_now);
+
+    // Duration should be positive or zero.
+    EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
+}
+
+
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 27 - 0
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -16,6 +16,7 @@
 #include <iostream>
 #include <iostream>
 #include <sstream>
 #include <sstream>
 #include <arpa/inet.h>
 #include <arpa/inet.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
@@ -204,4 +205,30 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     delete parent;
     delete parent;
 }
 }
 
 
+TEST_F(Pkt6Test, Timestamp) {
+    boost::scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
+
+    // Just after construction timestamp is invalid
+    ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time());
+
+    // Update packet time.
+    pkt->updateTimestamp();
+
+    // Get updated packet time.
+    boost::posix_time::ptime ts_packet = pkt->getTimestamp();
+
+    // After timestamp is updated it should be date-time.
+    ASSERT_FALSE(ts_packet.is_not_a_date_time());
+
+    // Check current time.
+    boost::posix_time::ptime ts_now =
+        boost::posix_time::microsec_clock::universal_time();
+
+    // Calculate period between packet time and now.
+    boost::posix_time::time_period ts_period(ts_packet, ts_now);
+
+    // Duration should be positive or zero.
+    EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
+}
+
 }
 }

+ 2 - 2
src/lib/dns/labelsequence.h

@@ -101,7 +101,7 @@ public:
     /// \note No actual memory is changed, this operation merely updates the
     /// \note No actual memory is changed, this operation merely updates the
     /// internal pointers based on the offsets in the Name object.
     /// internal pointers based on the offsets in the Name object.
     ///
     ///
-    /// \exeption OutOfRange if i is greater than or equal to the number
+    /// \exception OutOfRange if i is greater than or equal to the number
     ///           of labels currently pointed to by this LabelSequence
     ///           of labels currently pointed to by this LabelSequence
     ///
     ///
     /// \param i The number of labels to remove.
     /// \param i The number of labels to remove.
@@ -112,7 +112,7 @@ public:
     /// \note No actual memory is changed, this operation merely updates the
     /// \note No actual memory is changed, this operation merely updates the
     /// internal pointers based on the offsets in the Name object.
     /// internal pointers based on the offsets in the Name object.
     ///
     ///
-    /// \exeption OutOfRange if i is greater than or equal to the number
+    /// \exception OutOfRange if i is greater than or equal to the number
     ///           of labels currently pointed to by this LabelSequence
     ///           of labels currently pointed to by this LabelSequence
     ///
     ///
     /// \param i The number of labels to remove.
     /// \param i The number of labels to remove.

+ 5 - 1
src/lib/dns/message.cc

@@ -573,7 +573,11 @@ Message::clearSection(const Section section) {
     if (section >= MessageImpl::NUM_SECTIONS) {
     if (section >= MessageImpl::NUM_SECTIONS) {
         isc_throw(OutOfRange, "Invalid message section: " << section);
         isc_throw(OutOfRange, "Invalid message section: " << section);
     }
     }
-    impl_->rrsets_[section].clear();
+    if (section == Message::SECTION_QUESTION) {
+        impl_->questions_.clear();
+    } else {
+        impl_->rrsets_[section].clear();
+    }
     impl_->counts_[section] = 0;
     impl_->counts_[section] = 0;
 }
 }
 
 

+ 155 - 90
src/lib/dns/python/message_python.cc

@@ -209,12 +209,24 @@ Message_getHeaderFlag(s_Message* self, PyObject* args) {
         return (NULL);
         return (NULL);
     }
     }
 
 
-    if (self->cppobj->getHeaderFlag(
+    try {
+        if (self->cppobj->getHeaderFlag(
             static_cast<Message::HeaderFlag>(messageflag))) {
             static_cast<Message::HeaderFlag>(messageflag))) {
-        Py_RETURN_TRUE;
-    } else {
-        Py_RETURN_FALSE;
+            Py_RETURN_TRUE;
+        } else {
+            Py_RETURN_FALSE;
+        }
+    } catch (const isc::InvalidParameter& ip) {
+        PyErr_Clear();
+        PyErr_SetString(po_InvalidParameter, ip.what());
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.get_header_flag(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.get_header_flag()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -240,12 +252,17 @@ Message_setHeaderFlag(s_Message* self, PyObject* args) {
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_Clear();
         PyErr_Clear();
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
     } catch (const isc::InvalidParameter& ip) {
     } catch (const isc::InvalidParameter& ip) {
         PyErr_Clear();
         PyErr_Clear();
         PyErr_SetString(po_InvalidParameter, ip.what());
         PyErr_SetString(po_InvalidParameter, ip.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.set_header_flag(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.set_header_flag()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -273,8 +290,14 @@ Message_setQid(s_Message* self, PyObject* args) {
         Py_RETURN_NONE;
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.get_qid(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.set_qid()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -283,11 +306,14 @@ Message_getRcode(s_Message* self) {
         return (createRcodeObject(self->cppobj->getRcode()));
         return (createRcodeObject(self->cppobj->getRcode()));
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.get_rcode(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
-        PyErr_SetString(po_IscException, "Unexpected exception");
-        return (NULL);
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.get_rcode()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -301,8 +327,14 @@ Message_setRcode(s_Message* self, PyObject* args) {
         Py_RETURN_NONE;
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.set_rcode(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.set_rcode()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -311,17 +343,14 @@ Message_getOpcode(s_Message* self) {
         return (createOpcodeObject(self->cppobj->getOpcode()));
         return (createOpcodeObject(self->cppobj->getOpcode()));
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
     } catch (const exception& ex) {
     } catch (const exception& ex) {
-        const string ex_what =
-            "Failed to get message opcode: " + string(ex.what());
+        const string ex_what = "Error in Message.get_opcode(): " + string(ex.what());
         PyErr_SetString(po_IscException, ex_what.c_str());
         PyErr_SetString(po_IscException, ex_what.c_str());
-        return (NULL);
     } catch (...) {
     } catch (...) {
         PyErr_SetString(po_IscException,
         PyErr_SetString(po_IscException,
-                        "Unexpected exception getting opcode from message");
-        return (NULL);
+                        "Unexpected exception in Message.get_opcode()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -335,8 +364,14 @@ Message_setOpcode(s_Message* self, PyObject* args) {
         Py_RETURN_NONE;
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.set_opcode(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.set_opcode()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -348,12 +383,11 @@ Message_getEDNS(s_Message* self) {
     try {
     try {
         return (createEDNSObject(*src));
         return (createEDNSObject(*src));
     } catch (const exception& ex) {
     } catch (const exception& ex) {
-        const string ex_what =
-            "Failed to get EDNS from message: " + string(ex.what());
+        const string ex_what = "Error in Message.get_edns(): " + string(ex.what());
         PyErr_SetString(po_IscException, ex_what.c_str());
         PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
         PyErr_SetString(PyExc_SystemError,
         PyErr_SetString(PyExc_SystemError,
-                        "Unexpected failure getting EDNS from message");
+                        "Unexpected exception in Message.get_edns()");
     }
     }
     return (NULL);
     return (NULL);
 }
 }
@@ -369,8 +403,14 @@ Message_setEDNS(s_Message* self, PyObject* args) {
         Py_RETURN_NONE;
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.set_edns(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.set_edns()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -386,13 +426,11 @@ Message_getTSIGRecord(s_Message* self) {
     } catch (const InvalidMessageOperation& ex) {
     } catch (const InvalidMessageOperation& ex) {
         PyErr_SetString(po_InvalidMessageOperation, ex.what());
         PyErr_SetString(po_InvalidMessageOperation, ex.what());
     } catch (const exception& ex) {
     } catch (const exception& ex) {
-        const string ex_what =
-            "Unexpected failure in getting TSIGRecord from message: " +
-            string(ex.what());
+        const string ex_what = "Error in Message.get_tsig_record(): " + string(ex.what());
         PyErr_SetString(po_IscException, ex_what.c_str());
         PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
-        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
-                        "getting TSIGRecord from message");
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.get_tsig_record()");
     }
     }
     return (NULL);
     return (NULL);
 }
 }
@@ -411,8 +449,14 @@ Message_getRRCount(s_Message* self, PyObject* args) {
                                   static_cast<Message::Section>(section))));
                                   static_cast<Message::Section>(section))));
     } catch (const isc::OutOfRange& ex) {
     } catch (const isc::OutOfRange& ex) {
         PyErr_SetString(PyExc_OverflowError, ex.what());
         PyErr_SetString(PyExc_OverflowError, ex.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.get_rr_count(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.get_rr_count()");
     }
     }
+    return (NULL);
 }
 }
 
 
 // This is a helper templated class commonly used for getQuestion and
 // This is a helper templated class commonly used for getQuestion and
@@ -453,13 +497,11 @@ Message_getQuestion(PyObject* po_self, PyObject*) {
     } catch (const InvalidMessageSection& ex) {
     } catch (const InvalidMessageSection& ex) {
         PyErr_SetString(po_InvalidMessageSection, ex.what());
         PyErr_SetString(po_InvalidMessageSection, ex.what());
     } catch (const exception& ex) {
     } catch (const exception& ex) {
-        const string ex_what =
-            "Unexpected failure in Message.get_question: " +
-            string(ex.what());
+        const string ex_what = "Error in Message.get_question(): " + string(ex.what());
         PyErr_SetString(po_IscException, ex_what.c_str());
         PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
-        PyErr_SetString(PyExc_SystemError,
-                        "Unexpected failure in Message.get_question");
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.get_question()");
     }
     }
     return (NULL);
     return (NULL);
 }
 }
@@ -489,13 +531,11 @@ Message_getSection(PyObject* po_self, PyObject* args) {
     } catch (const InvalidMessageSection& ex) {
     } catch (const InvalidMessageSection& ex) {
         PyErr_SetString(po_InvalidMessageSection, ex.what());
         PyErr_SetString(po_InvalidMessageSection, ex.what());
     } catch (const exception& ex) {
     } catch (const exception& ex) {
-        const string ex_what =
-            "Unexpected failure in Message.get_section: " +
-            string(ex.what());
+        const string ex_what = "Error in Message.get_section(): " + string(ex.what());
         PyErr_SetString(po_IscException, ex_what.c_str());
         PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
-        PyErr_SetString(PyExc_SystemError,
-                        "Unexpected failure in Message.get_section");
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.get_section()");
     }
     }
     return (NULL);
     return (NULL);
 }
 }
@@ -513,9 +553,20 @@ Message_addQuestion(s_Message* self, PyObject* args) {
         return (NULL);
         return (NULL);
     }
     }
 
 
-    self->cppobj->addQuestion(PyQuestion_ToQuestion(question));
-
-    Py_RETURN_NONE;
+    try {
+        self->cppobj->addQuestion(PyQuestion_ToQuestion(question));
+        Py_RETURN_NONE;
+    } catch (const InvalidMessageOperation& imo) {
+        PyErr_Clear();
+        PyErr_SetString(po_InvalidMessageOperation, imo.what());
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.add_question(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.add_question()");
+    }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -534,36 +585,45 @@ Message_addRRset(s_Message* self, PyObject* args) {
         Py_RETURN_NONE;
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
     } catch (const isc::OutOfRange& ex) {
     } catch (const isc::OutOfRange& ex) {
         PyErr_SetString(PyExc_OverflowError, ex.what());
         PyErr_SetString(PyExc_OverflowError, ex.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.add_rrset(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
         PyErr_SetString(po_IscException,
         PyErr_SetString(po_IscException,
-                        "Unexpected exception in adding RRset");
-        return (NULL);
+                        "Unexpected exception in Message.add_rrset()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
 Message_clear(s_Message* self, PyObject* args) {
 Message_clear(s_Message* self, PyObject* args) {
     int i;
     int i;
-    if (PyArg_ParseTuple(args, "i", &i)) {
-        PyErr_Clear();
-        if (i == Message::PARSE) {
-            self->cppobj->clear(Message::PARSE);
-            Py_RETURN_NONE;
-        } else if (i == Message::RENDER) {
-            self->cppobj->clear(Message::RENDER);
-            Py_RETURN_NONE;
-        } else {
-            PyErr_SetString(PyExc_TypeError,
-                            "Message mode must be Message.PARSE or Message.RENDER");
-            return (NULL);
+
+    try {
+        if (PyArg_ParseTuple(args, "i", &i)) {
+            PyErr_Clear();
+            if (i == Message::PARSE) {
+                self->cppobj->clear(Message::PARSE);
+                Py_RETURN_NONE;
+            } else if (i == Message::RENDER) {
+                self->cppobj->clear(Message::RENDER);
+                Py_RETURN_NONE;
+            } else {
+                PyErr_SetString(PyExc_TypeError,
+                                "Message mode must be Message.PARSE or Message.RENDER");
+                return (NULL);
+            }
         }
         }
-    } else {
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.clear(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.clear()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -579,21 +639,34 @@ Message_clearSection(PyObject* pyself, PyObject* args) {
         Py_RETURN_NONE;
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
     } catch (const isc::OutOfRange& ex) {
     } catch (const isc::OutOfRange& ex) {
         PyErr_SetString(PyExc_OverflowError, ex.what());
         PyErr_SetString(PyExc_OverflowError, ex.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.clear_section(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
         PyErr_SetString(po_IscException,
         PyErr_SetString(po_IscException,
-                        "Unexpected exception in adding RRset");
-        return (NULL);
+                        "Unexpected exception in Message.clear_section()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
 Message_makeResponse(s_Message* self) {
 Message_makeResponse(s_Message* self) {
-    self->cppobj->makeResponse();
-    Py_RETURN_NONE;
+    try {
+        self->cppobj->makeResponse();
+        Py_RETURN_NONE;
+    } catch (const InvalidMessageOperation& imo) {
+        PyErr_Clear();
+        PyErr_SetString(po_InvalidMessageOperation, imo.what());
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.make_response(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.make_response()");
+    }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -604,11 +677,14 @@ Message_toText(s_Message* self) {
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_Clear();
         PyErr_Clear();
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
-        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what = "Error in Message.to_text(): " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
     } catch (...) {
     } catch (...) {
-        PyErr_SetString(po_IscException, "Unexpected exception");
-        return (NULL);
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in Message.to_text()");
     }
     }
+    return (NULL);
 }
 }
 
 
 PyObject*
 PyObject*
@@ -639,22 +715,18 @@ Message_toWire(s_Message* self, PyObject* args) {
         } catch (const InvalidMessageOperation& imo) {
         } catch (const InvalidMessageOperation& imo) {
             PyErr_Clear();
             PyErr_Clear();
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
-            return (NULL);
         } catch (const TSIGContextError& ex) {
         } catch (const TSIGContextError& ex) {
             // toWire() with a TSIG context can fail due to this if the
             // toWire() with a TSIG context can fail due to this if the
             // python program has a bug.
             // python program has a bug.
             PyErr_SetString(po_TSIGContextError, ex.what());
             PyErr_SetString(po_TSIGContextError, ex.what());
-            return (NULL);
-        } catch (const std::exception& ex) {
-            // Other exceptions should be rare (most likely an implementation
-            // bug)
-            PyErr_SetString(po_TSIGContextError, ex.what());
-            return (NULL);
+        } catch (const exception& ex) {
+            const string ex_what = "Error in Message.to_wire(): " + string(ex.what());
+            PyErr_SetString(po_TSIGContextError, ex_what.c_str());
         } catch (...) {
         } catch (...) {
-            PyErr_SetString(PyExc_RuntimeError,
-                            "Unexpected C++ exception in Message.to_wire");
-            return (NULL);
+            PyErr_SetString(po_IscException,
+                            "Unexpected exception in Message.to_wire()");
         }
         }
+        return (NULL);
     }
     }
     PyErr_Clear();
     PyErr_Clear();
     PyErr_SetString(PyExc_TypeError,
     PyErr_SetString(PyExc_TypeError,
@@ -682,29 +754,22 @@ Message_fromWire(PyObject* pyself, PyObject* args) {
             Py_RETURN_NONE;
             Py_RETURN_NONE;
         } catch (const InvalidMessageOperation& imo) {
         } catch (const InvalidMessageOperation& imo) {
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
-            return (NULL);
         } catch (const DNSMessageFORMERR& dmfe) {
         } catch (const DNSMessageFORMERR& dmfe) {
             PyErr_SetString(po_DNSMessageFORMERR, dmfe.what());
             PyErr_SetString(po_DNSMessageFORMERR, dmfe.what());
-            return (NULL);
         } catch (const DNSMessageBADVERS& dmfe) {
         } catch (const DNSMessageBADVERS& dmfe) {
             PyErr_SetString(po_DNSMessageBADVERS, dmfe.what());
             PyErr_SetString(po_DNSMessageBADVERS, dmfe.what());
-            return (NULL);
         } catch (const MessageTooShort& mts) {
         } catch (const MessageTooShort& mts) {
             PyErr_SetString(po_MessageTooShort, mts.what());
             PyErr_SetString(po_MessageTooShort, mts.what());
-            return (NULL);
         } catch (const InvalidBufferPosition& ex) {
         } catch (const InvalidBufferPosition& ex) {
             PyErr_SetString(po_DNSMessageFORMERR, ex.what());
             PyErr_SetString(po_DNSMessageFORMERR, ex.what());
-            return (NULL);
         } catch (const exception& ex) {
         } catch (const exception& ex) {
-            const string ex_what =
-                "Error in Message.from_wire: " + string(ex.what());
-            PyErr_SetString(PyExc_RuntimeError, ex_what.c_str());
-            return (NULL);
+            const string ex_what = "Error in Message.from_wire(): " + string(ex.what());
+            PyErr_SetString(po_IscException, ex_what.c_str());
         } catch (...) {
         } catch (...) {
-            PyErr_SetString(PyExc_RuntimeError,
-                            "Unexpected exception in Message.from_wire");
-            return (NULL);
+            PyErr_SetString(po_IscException,
+                            "Unexpected exception in Message.from_wire()");
         }
         }
+        return (NULL);
     }
     }
 
 
     PyErr_SetString(PyExc_TypeError,
     PyErr_SetString(PyExc_TypeError,

+ 2 - 2
src/lib/dns/python/name_python.cc

@@ -115,7 +115,7 @@ PyObject* Name_reverse(s_Name* self);
 PyObject* Name_concatenate(s_Name* self, PyObject* args);
 PyObject* Name_concatenate(s_Name* self, PyObject* args);
 PyObject* Name_downcase(s_Name* self);
 PyObject* Name_downcase(s_Name* self);
 PyObject* Name_isWildCard(s_Name* self);
 PyObject* Name_isWildCard(s_Name* self);
-long Name_hash(PyObject* py_self);
+Py_hash_t Name_hash(PyObject* py_self);
 
 
 PyMethodDef Name_methods[] = {
 PyMethodDef Name_methods[] = {
     { "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
     { "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
@@ -520,7 +520,7 @@ Name_isWildCard(s_Name* self) {
     }
     }
 }
 }
 
 
-long
+Py_hash_t
 Name_hash(PyObject* pyself) {
 Name_hash(PyObject* pyself) {
     s_Name* const self = static_cast<s_Name*>(pyself);
     s_Name* const self = static_cast<s_Name*>(pyself);
     return (LabelSequence(*self->cppobj).getHash(false));
     return (LabelSequence(*self->cppobj).getHash(false));

+ 5 - 0
src/lib/dns/python/pydnspp_common.h

@@ -43,6 +43,11 @@ extern PyObject* po_DNSMessageBADVERS;
 int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
 int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
 
 
 int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
 int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
+
+// Short term workaround for unifying the return type of tp_hash
+#if PY_MINOR_VERSION < 2
+typedef long Py_hash_t;
+#endif
 } // namespace python
 } // namespace python
 } // namespace dns
 } // namespace dns
 } // namespace isc
 } // namespace isc

+ 1 - 0
src/lib/dns/python/rdata_python.cc

@@ -116,6 +116,7 @@ Rdata_init(PyObject* self_p, PyObject* args, PyObject*) {
             return (0);
             return (0);
         } else if (PyArg_ParseTuple(args, "O!O!y#", &rrtype_type, &rrtype,
         } else if (PyArg_ParseTuple(args, "O!O!y#", &rrtype_type, &rrtype,
                                     &rrclass_type, &rrclass, &data, &len)) {
                                     &rrclass_type, &rrclass, &data, &len)) {
+            PyErr_Clear();
             InputBuffer input_buffer(data, len);
             InputBuffer input_buffer(data, len);
             self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
             self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
                                        PyRRClass_ToRRClass(rrclass),
                                        PyRRClass_ToRRClass(rrclass),

+ 2 - 2
src/lib/dns/python/rrclass_python.cc

@@ -52,7 +52,7 @@ PyObject* RRClass_str(PyObject* self);
 PyObject* RRClass_toWire(s_RRClass* self, PyObject* args);
 PyObject* RRClass_toWire(s_RRClass* self, PyObject* args);
 PyObject* RRClass_getCode(s_RRClass* self);
 PyObject* RRClass_getCode(s_RRClass* self);
 PyObject* RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op);
 PyObject* RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op);
-long RRClass_hash(PyObject* pyself);
+Py_hash_t RRClass_hash(PyObject* pyself);
 
 
 // Static function for direct class creation
 // Static function for direct class creation
 PyObject* RRClass_IN(s_RRClass *self);
 PyObject* RRClass_IN(s_RRClass *self);
@@ -265,7 +265,7 @@ PyObject* RRClass_ANY(s_RRClass*) {
     return (RRClass_createStatic(RRClass::ANY()));
     return (RRClass_createStatic(RRClass::ANY()));
 }
 }
 
 
-long
+Py_hash_t
 RRClass_hash(PyObject* pyself) {
 RRClass_hash(PyObject* pyself) {
     s_RRClass* const self = static_cast<s_RRClass*>(pyself);
     s_RRClass* const self = static_cast<s_RRClass*>(pyself);
     return (self->cppobj->getCode());
     return (self->cppobj->getCode());

+ 15 - 0
src/lib/dns/python/tests/message_python_test.py

@@ -118,6 +118,11 @@ class MessageTest(unittest.TestCase):
         self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_AD))
         self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_AD))
         self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_CD))
         self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_CD))
 
 
+        # 0 passed as flag should raise
+        self.assertRaises(InvalidParameter, self.r.get_header_flag, 0)
+        # unused bit
+        self.assertRaises(InvalidParameter, self.r.get_header_flag, 0x80000000)
+
         self.r.set_header_flag(Message.HEADERFLAG_QR)
         self.r.set_header_flag(Message.HEADERFLAG_QR)
         self.assertTrue(self.r.get_header_flag(Message.HEADERFLAG_QR))
         self.assertTrue(self.r.get_header_flag(Message.HEADERFLAG_QR))
 
 
@@ -267,6 +272,15 @@ class MessageTest(unittest.TestCase):
         self.assertEqual(1, sys.getrefcount(self.r.get_question()))
         self.assertEqual(1, sys.getrefcount(self.r.get_question()))
         self.assertEqual(1, sys.getrefcount(self.r.get_question()[0]))
         self.assertEqual(1, sys.getrefcount(self.r.get_question()[0]))
 
 
+        # Message.add_question() called in non-RENDER mode should assert
+        self.r.clear(Message.PARSE)
+        self.assertRaises(InvalidMessageOperation, self.r.add_question, q)
+
+    def test_make_response(self):
+        # Message.make_response() called in non-PARSE mode should assert
+        self.r.clear(Message.RENDER)
+        self.assertRaises(InvalidMessageOperation, self.r.make_response)
+
     def test_add_rrset(self):
     def test_add_rrset(self):
         self.assertRaises(TypeError, self.r.add_rrset, "wrong")
         self.assertRaises(TypeError, self.r.add_rrset, "wrong")
         self.assertRaises(TypeError, self.r.add_rrset)
         self.assertRaises(TypeError, self.r.add_rrset)
@@ -295,6 +309,7 @@ class MessageTest(unittest.TestCase):
         self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
         self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
         self.r.clear_section(Message.SECTION_QUESTION)
         self.r.clear_section(Message.SECTION_QUESTION)
         self.assertEqual(0, self.r.get_rr_count(Message.SECTION_QUESTION))
         self.assertEqual(0, self.r.get_rr_count(Message.SECTION_QUESTION))
+        self.assertEqual(0, len(self.r.get_question()))
 
 
     def test_clear_section(self):
     def test_clear_section(self):
         for section in [Message.SECTION_ANSWER, Message.SECTION_AUTHORITY,
         for section in [Message.SECTION_ANSWER, Message.SECTION_AUTHORITY,

+ 1 - 1
src/lib/dns/rdata.cc

@@ -119,7 +119,7 @@ Generic::Generic(isc::util::InputBuffer& buffer, size_t rdata_len) {
     impl_ = new GenericImpl(data);
     impl_ = new GenericImpl(data);
 }
 }
 
 
-Generic::Generic(const string& rdata_string) {
+Generic::Generic(const std::string& rdata_string) {
     istringstream iss(rdata_string);
     istringstream iss(rdata_string);
     string unknown_mark;
     string unknown_mark;
     iss >> unknown_mark;
     iss >> unknown_mark;

+ 18 - 15
src/lib/dns/rdata/any_255/tsig_250.cc

@@ -74,25 +74,28 @@ struct TSIG::TSIGImpl {
 /// \code <Alg> <Time> <Fudge> <MACsize> [<MAC>] <OrigID> <Error> <OtherLen> [<OtherData>]
 /// \code <Alg> <Time> <Fudge> <MACsize> [<MAC>] <OrigID> <Error> <OtherLen> [<OtherData>]
 /// \endcode
 /// \endcode
 /// where
 /// where
-/// - <Alg> is a valid textual representation of domain name.
-/// - <Time> is an unsigned 48-bit decimal integer.
-/// - <MACSize>, <OrigID>, and <OtherLen> are an unsigned 16-bit decimal
+/// - &lt;Alg&gt; is a valid textual representation of domain name.
+/// - &lt;Time&gt; is an unsigned 48-bit decimal integer.
+/// - &lt;MACSize&gt;, &lt;OrigID&gt;, and &lt;OtherLen&gt; are an unsigned
+///   16-bit decimal
 ///   integer.
 ///   integer.
-/// - <Error> is an unsigned 16-bit decimal integer or a valid mnemonic for
-///   the Error field specified in RFC2845.  Currently, "BADSIG", "BADKEY",
+/// - &lt;Error&gt; is an unsigned 16-bit decimal integer or a valid mnemonic
+///   for the Error field specified in RFC2845.  Currently, "BADSIG", "BADKEY",
 ///   and "BADTIME" are supported (case sensitive).  In future versions
 ///   and "BADTIME" are supported (case sensitive).  In future versions
 ///   other representations that are compatible with the DNS RCODE will be
 ///   other representations that are compatible with the DNS RCODE will be
 ///   supported.
 ///   supported.
-/// - <MAC> and <OtherData> is a BASE-64 encoded string that does not contain
-///   space characters.
-///   When <MACSize> and <OtherLen> is 0, <MAC> and <OtherData> must not
-///   appear in \c tsgi_str, respectively.
-/// - The decoded data of <MAC> is <MACSize> bytes of binary stream.
-/// - The decoded data of <OtherData> is <OtherLen> bytes of binary stream.
+/// - &lt;MAC&gt; and &lt;OtherData&gt; is a BASE-64 encoded string that does
+///   not contain space characters.
+///   When &lt;MACSize&gt; and &lt;OtherLen&gt; is 0, &lt;MAC&gt; and
+///   &lt;OtherData&gt; must not appear in \c tsig_str, respectively.
+/// - The decoded data of &lt;MAC&gt; is &lt;MACSize&gt; bytes of binary
+///   stream.
+/// - The decoded data of &lt;OtherData&gt; is &lt;OtherLen&gt; bytes of
+///   binary stream.
 ///
 ///
 /// An example of valid string is:
 /// An example of valid string is:
 /// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
 /// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
-/// In this example <OtherData> is missing because <OtherLen> is 0.
+/// In this example &lt;OtherData&gt; is missing because &lt;OtherLen&gt; is 0.
 ///
 ///
 /// Note that RFC2845 does not define the standard presentation format
 /// Note that RFC2845 does not define the standard presentation format
 /// of %TSIG RR, so the above syntax is implementation specific.
 /// of %TSIG RR, so the above syntax is implementation specific.
@@ -101,10 +104,10 @@ struct TSIG::TSIGImpl {
 ///
 ///
 /// <b>Exceptions</b>
 /// <b>Exceptions</b>
 ///
 ///
-/// If <Alg> is not a valid domain name, a corresponding exception from
+/// If &lt;Alg&gt; is not a valid domain name, a corresponding exception from
 /// the \c Name class will be thrown;
 /// the \c Name class will be thrown;
-/// if <MAC> or <OtherData> is not validly encoded in BASE-64, an exception
-/// of class \c isc::BadValue will be thrown;
+/// if &lt;MAC&gt; or &lt;OtherData&gt; is not validly encoded in BASE-64, an
+/// exception of class \c isc::BadValue will be thrown;
 /// if %any of the other bullet points above is not met, an exception of
 /// if %any of the other bullet points above is not met, an exception of
 /// class \c InvalidRdataText will be thrown.
 /// class \c InvalidRdataText will be thrown.
 /// This constructor internally involves resource allocation, and if it fails
 /// This constructor internally involves resource allocation, and if it fails

+ 1 - 1
src/lib/dns/rdata/ch_3/a_1.cc

@@ -27,7 +27,7 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
-A::A(const string&) {
+A::A(const std::string&) {
     // TBD
     // TBD
 }
 }
 
 

+ 1 - 1
src/lib/dns/rdata/generic/dlv_32769.cc

@@ -34,7 +34,7 @@ using namespace isc::dns::rdata::generic::detail;
 /// \brief Constructor from string.
 /// \brief Constructor from string.
 ///
 ///
 /// A copy of the implementation object is allocated and constructed.
 /// A copy of the implementation object is allocated and constructed.
-DLV::DLV(const string& ds_str) :
+DLV::DLV(const std::string& ds_str) :
     impl_(new DLVImpl(ds_str))
     impl_(new DLVImpl(ds_str))
 {}
 {}
 
 

+ 1 - 1
src/lib/dns/rdata/generic/dnskey_48.cc

@@ -51,7 +51,7 @@ struct DNSKEYImpl {
     const vector<uint8_t> keydata_;
     const vector<uint8_t> keydata_;
 };
 };
 
 
-DNSKEY::DNSKEY(const string& dnskey_str) :
+DNSKEY::DNSKEY(const std::string& dnskey_str) :
     impl_(NULL)
     impl_(NULL)
 {
 {
     istringstream iss(dnskey_str);
     istringstream iss(dnskey_str);

+ 1 - 1
src/lib/dns/rdata/generic/ds_43.cc

@@ -31,7 +31,7 @@ using namespace isc::dns::rdata::generic::detail;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
-DS::DS(const string& ds_str) :
+DS::DS(const std::string& ds_str) :
     impl_(new DSImpl(ds_str))
     impl_(new DSImpl(ds_str))
 {}
 {}
 
 

+ 1 - 1
src/lib/dns/rdata/generic/hinfo_13.cc

@@ -37,7 +37,7 @@ using namespace isc::dns::characterstr;
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
 
 
-HINFO::HINFO(const string& hinfo_str) {
+HINFO::HINFO(const std::string& hinfo_str) {
     string::const_iterator input_iterator = hinfo_str.begin();
     string::const_iterator input_iterator = hinfo_str.begin();
     cpu_ = getNextCharacterString(hinfo_str, input_iterator);
     cpu_ = getNextCharacterString(hinfo_str, input_iterator);
 
 

+ 1 - 1
src/lib/dns/rdata/generic/nsec3_50.cc

@@ -64,7 +64,7 @@ struct NSEC3Impl {
     const vector<uint8_t> typebits_;
     const vector<uint8_t> typebits_;
 };
 };
 
 
-NSEC3::NSEC3(const string& nsec3_str) :
+NSEC3::NSEC3(const std::string& nsec3_str) :
     impl_(NULL)
     impl_(NULL)
 {
 {
     istringstream iss(nsec3_str);
     istringstream iss(nsec3_str);

+ 1 - 1
src/lib/dns/rdata/generic/nsec3param_51.cc

@@ -46,7 +46,7 @@ struct NSEC3PARAMImpl {
     const vector<uint8_t> salt_;
     const vector<uint8_t> salt_;
 };
 };
 
 
-NSEC3PARAM::NSEC3PARAM(const string& nsec3param_str) :
+NSEC3PARAM::NSEC3PARAM(const std::string& nsec3param_str) :
     impl_(NULL)
     impl_(NULL)
 {
 {
     istringstream iss(nsec3param_str);
     istringstream iss(nsec3param_str);

+ 1 - 1
src/lib/dns/rdata/generic/nsec_47.cc

@@ -49,7 +49,7 @@ struct NSECImpl {
     vector<uint8_t> typebits_;
     vector<uint8_t> typebits_;
 };
 };
 
 
-NSEC::NSEC(const string& nsec_str) :
+NSEC::NSEC(const std::string& nsec_str) :
     impl_(NULL)
     impl_(NULL)
 {
 {
     istringstream iss(nsec_str);
     istringstream iss(nsec_str);

+ 1 - 1
src/lib/dns/rdata/generic/opt_41.cc

@@ -27,7 +27,7 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
-OPT::OPT(const string&) {
+OPT::OPT(const std::string&) {
     isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
     isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
 }
 }
 
 

+ 1 - 1
src/lib/dns/rdata/generic/ptr_12.cc

@@ -28,7 +28,7 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
-PTR::PTR(const string& type_str) :
+PTR::PTR(const std::string& type_str) :
     ptr_name_(type_str)
     ptr_name_(type_str)
 {}
 {}
 
 

+ 1 - 1
src/lib/dns/rdata/generic/rrsig_46.cc

@@ -72,7 +72,7 @@ struct RRSIGImpl {
     const vector<uint8_t> signature_;
     const vector<uint8_t> signature_;
 };
 };
 
 
-RRSIG::RRSIG(const string& rrsig_str) :
+RRSIG::RRSIG(const std::string& rrsig_str) :
     impl_(NULL)
     impl_(NULL)
 {
 {
     istringstream iss(rrsig_str);
     istringstream iss(rrsig_str);

+ 1 - 1
src/lib/dns/rdata/generic/soa_6.cc

@@ -41,7 +41,7 @@ SOA::SOA(InputBuffer& buffer, size_t) :
     buffer.readData(numdata_, sizeof(numdata_));
     buffer.readData(numdata_, sizeof(numdata_));
 }
 }
 
 
-SOA::SOA(const string& soastr) :
+SOA::SOA(const std::string& soastr) :
     mname_("."), rname_(".")    // quick hack workaround
     mname_("."), rname_(".")    // quick hack workaround
 {
 {
     istringstream iss(soastr);
     istringstream iss(soastr);

+ 1 - 1
src/lib/dns/rdata/generic/sshfp_44.cc

@@ -80,7 +80,7 @@ SSHFP::SSHFP(const std::string& sshfp_str)
     decodeHex(fingerprintbuf.str(), fingerprint_);
     decodeHex(fingerprintbuf.str(), fingerprint_);
 }
 }
 
 
-SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const string& fingerprint)
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const std::string& fingerprint)
 {
 {
     if ((algorithm < 1) || (algorithm > 2)) {
     if ((algorithm < 1) || (algorithm > 2)) {
       isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
       isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");

+ 1 - 1
src/lib/dns/rdata/hs_4/a_1.cc

@@ -27,7 +27,7 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
-A::A(const string&) {
+A::A(const std::string&) {
     // TBD
     // TBD
 }
 }
 
 

+ 1 - 1
src/lib/dns/rdata/in_1/a_1.cc

@@ -34,7 +34,7 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
-A::A(const string& addrstr) {
+A::A(const std::string& addrstr) {
     // RFC1035 states textual representation of IN/A RDATA is
     // RFC1035 states textual representation of IN/A RDATA is
     // "four decimal numbers separated by dots without any embedded spaces".
     // "four decimal numbers separated by dots without any embedded spaces".
     // This is exactly what inet_pton() accepts for AF_INET.  In particular,
     // This is exactly what inet_pton() accepts for AF_INET.  In particular,

+ 1 - 1
src/lib/dns/rdata/in_1/aaaa_28.cc

@@ -34,7 +34,7 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
 
-AAAA::AAAA(const string& addrstr) {
+AAAA::AAAA(const std::string& addrstr) {
     if (inet_pton(AF_INET6, addrstr.c_str(), &addr_) != 1) {
     if (inet_pton(AF_INET6, addrstr.c_str(), &addr_) != 1) {
         isc_throw(InvalidRdataText,
         isc_throw(InvalidRdataText,
                   "IN/AAAA RDATA construction from text failed: "
                   "IN/AAAA RDATA construction from text failed: "

+ 1 - 1
src/lib/dns/rdata/in_1/dhcid_49.cc

@@ -47,7 +47,7 @@ using namespace isc::util;
 ///           < n octets >    Digest (length depends on digest type)
 ///           < n octets >    Digest (length depends on digest type)
 /// If the data is less than 3 octets (i.e. it cannot contain id type code and
 /// If the data is less than 3 octets (i.e. it cannot contain id type code and
 /// digest type code), an exception of class \c InvalidRdataLength is thrown.
 /// digest type code), an exception of class \c InvalidRdataLength is thrown.
-DHCID::DHCID(const string& dhcid_str) {
+DHCID::DHCID(const std::string& dhcid_str) {
     istringstream iss(dhcid_str);
     istringstream iss(dhcid_str);
     stringbuf digestbuf;
     stringbuf digestbuf;
 
 

+ 6 - 6
src/lib/dns/rdata/in_1/srv_33.cc

@@ -52,22 +52,22 @@ struct SRVImpl {
 /// \code <Priority> <Weight> <Port> <Target>
 /// \code <Priority> <Weight> <Port> <Target>
 /// \endcode
 /// \endcode
 /// where
 /// where
-/// - <Priority>, <Weight>, and <Port> are an unsigned 16-bit decimal
-///   integer.
-/// - <Target> is a valid textual representation of domain name.
+/// - &lt;Priority&gt;, &lt;Weight&gt;, and &lt;Port&gt; are an unsigned
+///   16-bit decimal integer.
+/// - &lt;Target&gt; is a valid textual representation of domain name.
 ///
 ///
 /// An example of valid string is:
 /// An example of valid string is:
 /// \code "1 5 1500 example.com." \endcode
 /// \code "1 5 1500 example.com." \endcode
 ///
 ///
 /// <b>Exceptions</b>
 /// <b>Exceptions</b>
 ///
 ///
-/// If <Target> is not a valid domain name, a corresponding exception from
-/// the \c Name class will be thrown;
+/// If &lt;Target&gt; is not a valid domain name, a corresponding exception
+/// from the \c Name class will be thrown;
 /// if %any of the other bullet points above is not met, an exception of
 /// if %any of the other bullet points above is not met, an exception of
 /// class \c InvalidRdataText will be thrown.
 /// class \c InvalidRdataText will be thrown.
 /// This constructor internally involves resource allocation, and if it fails
 /// This constructor internally involves resource allocation, and if it fails
 /// a corresponding standard exception will be thrown.
 /// a corresponding standard exception will be thrown.
-SRV::SRV(const string& srv_str) :
+SRV::SRV(const std::string& srv_str) :
     impl_(NULL)
     impl_(NULL)
 {
 {
     istringstream iss(srv_str);
     istringstream iss(srv_str);

+ 1 - 1
src/lib/dns/rrclass.cc

@@ -30,7 +30,7 @@ using namespace isc::util;
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 
 
-RRClass::RRClass(const string& classstr) {
+RRClass::RRClass(const std::string& classstr) {
     classcode_ = RRParamRegistry::getRegistry().textToClassCode(classstr);
     classcode_ = RRParamRegistry::getRegistry().textToClassCode(classstr);
 }
 }
 
 

+ 4 - 4
src/lib/dns/rrparamregistry-placeholder.cc

@@ -224,7 +224,7 @@ RRParamRegistry::getRegistry() {
 }
 }
 
 
 void
 void
-RRParamRegistry::add(const string& typecode_string, uint16_t typecode,
+RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
                      RdataFactoryPtr rdata_factory)
                      RdataFactoryPtr rdata_factory)
 {
 {
     bool type_added = false;
     bool type_added = false;
@@ -242,8 +242,8 @@ RRParamRegistry::add(const string& typecode_string, uint16_t typecode,
 }
 }
 
 
 void
 void
-RRParamRegistry::add(const string& typecode_string, uint16_t typecode,
-                     const string& classcode_string, uint16_t classcode,
+RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
+                     const std::string& classcode_string, uint16_t classcode,
                      RdataFactoryPtr rdata_factory)
                      RdataFactoryPtr rdata_factory)
 {
 {
     // Rollback logic on failure is complicated.  If adding the new type or
     // Rollback logic on failure is complicated.  If adding the new type or
@@ -470,7 +470,7 @@ RRParamRegistry::codeToClassText(uint16_t code) const {
 
 
 RdataPtr
 RdataPtr
 RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
 RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
-                             const string& rdata_string)
+                             const std::string& rdata_string)
 {
 {
     // If the text indicates that it's rdata of an "unknown" type (beginning
     // If the text indicates that it's rdata of an "unknown" type (beginning
     // with '\# n'), parse it that way. (TBD)
     // with '\# n'), parse it that way. (TBD)

+ 1 - 1
src/lib/dns/rrttl.cc

@@ -28,7 +28,7 @@ using namespace isc::util;
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 
 
-RRTTL::RRTTL(const string& ttlstr) {
+RRTTL::RRTTL(const std::string& ttlstr) {
     // Some systems (at least gcc-4.4) flow negative values over into
     // Some systems (at least gcc-4.4) flow negative values over into
     // unsigned integer, where older systems failed to parse. We want
     // unsigned integer, where older systems failed to parse. We want
     // that failure here, so we extract into int64 and check the value
     // that failure here, so we extract into int64 and check the value

+ 1 - 1
src/lib/dns/rrtype.cc

@@ -31,7 +31,7 @@ using isc::dns::RRType;
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 
 
-RRType::RRType(const string& typestr) {
+RRType::RRType(const std::string& typestr) {
     typecode_ = RRParamRegistry::getRegistry().textToTypeCode(typestr);
     typecode_ = RRParamRegistry::getRegistry().textToTypeCode(typestr);
 }
 }
 
 

+ 2 - 0
src/lib/dns/tests/message_unittest.cc

@@ -406,6 +406,8 @@ TEST_F(MessageTest, clearQuestionSection) {
 
 
     message_render.clearSection(Message::SECTION_QUESTION);
     message_render.clearSection(Message::SECTION_QUESTION);
     EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
     EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+    EXPECT_TRUE(message_render.beginQuestion() ==
+                message_render.endQuestion());
 }
 }
 
 
 
 

+ 1 - 0
src/lib/log/Makefile.am

@@ -2,6 +2,7 @@ SUBDIRS = . compiler tests
 
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTOP_BUILDDIR=\"${abs_top_builddir}\"
 
 
 CLEANFILES = *.gcno *.gcda
 CLEANFILES = *.gcno *.gcda
 
 

+ 5 - 5
src/lib/log/compiler/message.cc

@@ -58,14 +58,14 @@ static const char* VERSION = "1.0-0";
 /// \b Invocation<BR>
 /// \b Invocation<BR>
 /// The program is invoked with the command:
 /// The program is invoked with the command:
 ///
 ///
-/// <tt>message [-v | -h | -p | -d <dir> | <message-file>]</tt>
+/// <tt>message [-v | -h | -p | -d &lt;dir&gt; | <message-file>]</tt>
 ///
 ///
 /// It reads the message file and writes out two files of the same
 /// It reads the message file and writes out two files of the same
 /// name in the current working directory (unless -d is used) but
 /// name in the current working directory (unless -d is used) but
 /// with extensions of .h and .cc, or .py if -p is used.
 /// with extensions of .h and .cc, or .py if -p is used.
 ///
 ///
 /// -v causes it to print the version number and exit. -h prints a help
 /// -v causes it to print the version number and exit. -h prints a help
-/// message (and exits). -p sets the output to python. -d <dir> will make
+/// message (and exits). -p sets the output to python. -d &lt;dir&gt; will make
 /// it write the output file(s) to dir instead of current working
 /// it write the output file(s) to dir instead of current working
 /// directory
 /// directory
 
 
@@ -119,9 +119,9 @@ currentTime() {
 
 
 /// \brief Create Header Sentinel
 /// \brief Create Header Sentinel
 ///
 ///
-/// Given the name of a file, create an #ifdef sentinel name.  The name is
-/// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
-/// extension less the leading period.  The sentinel will be upper-case.
+/// Given the name of a file, create an \#ifdef sentinel name.  The name is
+/// __<name>_<ext>, where &lt;name&gt; is the name of the file, and &lt;ext&gt;
+/// is the extension less the leading period.  The sentinel will be upper-case.
 ///
 ///
 /// \param file Filename object representing the file.
 /// \param file Filename object representing the file.
 ///
 ///

+ 7 - 0
src/lib/log/logger.cc

@@ -179,6 +179,13 @@ Logger::fatal(const isc::log::MessageID& ident) {
     }
     }
 }
 }
 
 
+// Replace the interprocess synchronization object
+
+void
+Logger::setInterprocessSync(isc::util::InterprocessSync* sync) {
+    getLoggerPtr()->setInterprocessSync(sync);
+}
+
 // Comparison (testing only)
 // Comparison (testing only)
 
 
 bool
 bool

+ 23 - 0
src/lib/log/logger.h

@@ -25,6 +25,7 @@
 #include <log/message_types.h>
 #include <log/message_types.h>
 #include <log/log_formatter.h>
 #include <log/log_formatter.h>
 
 
+#include <util/interprocess_sync.h>
 
 
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
@@ -98,6 +99,17 @@ public:
     {}
     {}
 };
 };
 
 
+/// \brief Bad Interprocess Sync
+///
+/// Exception thrown if a bad InterprocessSync object (such as NULL) is
+/// used.
+class BadInterprocessSync : public isc::Exception {
+public:
+    BadInterprocessSync(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
+};
+
 /// \brief Logger Class
 /// \brief Logger Class
 ///
 ///
 /// This class is the main class used for logging.  Use comprises:
 /// This class is the main class used for logging.  Use comprises:
@@ -237,6 +249,17 @@ public:
     /// \param ident Message identification.
     /// \param ident Message identification.
     Formatter fatal(const MessageID& ident);
     Formatter fatal(const MessageID& ident);
 
 
+    /// \brief Replace the interprocess synchronization object
+    ///
+    /// If this method is called with NULL as the argument, it throws a
+    /// BadInterprocessSync exception.
+    ///
+    /// \param sync The logger uses this synchronization object for
+    /// synchronizing output of log messages. It should be deletable and
+    /// the ownership is transferred to the logger. If NULL is passed,
+    /// a BadInterprocessSync exception is thrown.
+    void setInterprocessSync(isc::util::InterprocessSync* sync);
+
     /// \brief Equality
     /// \brief Equality
     ///
     ///
     /// Check if two instances of this logger refer to the same stream.
     /// Check if two instances of this logger refer to the same stream.

+ 33 - 2
src/lib/log/logger_impl.cc

@@ -32,12 +32,14 @@
 #include <log/message_types.h>
 #include <log/message_types.h>
 
 
 #include <util/strutil.h>
 #include <util/strutil.h>
+#include <util/interprocess_sync_file.h>
 
 
 // Note: as log4cplus and the BIND 10 logger have many concepts in common, and
 // Note: as log4cplus and the BIND 10 logger have many concepts in common, and
 // thus many similar names, to disambiguate types we don't "use" the log4cplus
 // thus many similar names, to disambiguate types we don't "use" the log4cplus
 // namespace: instead, all log4cplus types are explicitly qualified.
 // namespace: instead, all log4cplus types are explicitly qualified.
 
 
 using namespace std;
 using namespace std;
+using namespace isc::util;
 
 
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
@@ -47,14 +49,17 @@ namespace log {
 // one compiler requires that all member variables be constructed before the
 // one compiler requires that all member variables be constructed before the
 // constructor is run, but log4cplus::Logger (the type of logger_) has no
 // constructor is run, but log4cplus::Logger (the type of logger_) has no
 // default constructor.
 // default constructor.
-LoggerImpl::LoggerImpl(const string& name) : name_(expandLoggerName(name)),
-    logger_(log4cplus::Logger::getInstance(name_))
+LoggerImpl::LoggerImpl(const string& name) :
+    name_(expandLoggerName(name)),
+    logger_(log4cplus::Logger::getInstance(name_)),
+    sync_(new InterprocessSyncFile("logger"))
 {
 {
 }
 }
 
 
 // Destructor. (Here because of virtual declaration.)
 // Destructor. (Here because of virtual declaration.)
 
 
 LoggerImpl::~LoggerImpl() {
 LoggerImpl::~LoggerImpl() {
+    delete sync_;
 }
 }
 
 
 // Set the severity for logging.
 // Set the severity for logging.
@@ -102,8 +107,30 @@ LoggerImpl::lookupMessage(const MessageID& ident) {
                        MessageDictionary::globalDictionary().getText(ident)));
                        MessageDictionary::globalDictionary().getText(ident)));
 }
 }
 
 
+// Replace the interprocess synchronization object
+
+void
+LoggerImpl::setInterprocessSync(isc::util::InterprocessSync* sync) {
+    if (sync == NULL) {
+        isc_throw(BadInterprocessSync,
+                  "NULL was passed to setInterprocessSync()");
+    }
+
+    delete sync_;
+    sync_ = sync;
+}
+
 void
 void
 LoggerImpl::outputRaw(const Severity& severity, const string& message) {
 LoggerImpl::outputRaw(const Severity& severity, const string& message) {
+    // Use an interprocess sync locker for mutual exclusion from other
+    // processes to avoid log messages getting interspersed.
+
+    InterprocessSyncLocker locker(*sync_);
+
+    if (!locker.lock()) {
+        LOG4CPLUS_ERROR(logger_, "Unable to lock logger lockfile");
+    }
+
     switch (severity) {
     switch (severity) {
         case DEBUG:
         case DEBUG:
             LOG4CPLUS_DEBUG(logger_, message);
             LOG4CPLUS_DEBUG(logger_, message);
@@ -124,6 +151,10 @@ LoggerImpl::outputRaw(const Severity& severity, const string& message) {
         case FATAL:
         case FATAL:
             LOG4CPLUS_FATAL(logger_, message);
             LOG4CPLUS_FATAL(logger_, message);
     }
     }
+
+    if (!locker.unlock()) {
+        LOG4CPLUS_ERROR(logger_, "Unable to unlock logger lockfile");
+    }
 }
 }
 
 
 } // namespace log
 } // namespace log

+ 16 - 2
src/lib/log/logger_impl.h

@@ -32,6 +32,8 @@
 #include <log/logger_level_impl.h>
 #include <log/logger_level_impl.h>
 #include <log/message_types.h>
 #include <log/message_types.h>
 
 
+#include <util/interprocess_sync.h>
+
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
 
 
@@ -167,6 +169,17 @@ public:
     /// This gets you the unformatted text of message for given ID.
     /// This gets you the unformatted text of message for given ID.
     std::string* lookupMessage(const MessageID& id);
     std::string* lookupMessage(const MessageID& id);
 
 
+    /// \brief Replace the interprocess synchronization object
+    ///
+    /// If this method is called with NULL as the argument, it throws a
+    /// BadInterprocessSync exception.
+    ///
+    /// \param sync The logger uses this synchronization object for
+    /// synchronizing output of log messages. It should be deletable and
+    /// the ownership is transferred to the logger implementation.
+    /// If NULL is passed, a BadInterprocessSync exception is thrown.
+    void setInterprocessSync(isc::util::InterprocessSync* sync);
+
     /// \brief Equality
     /// \brief Equality
     ///
     ///
     /// Check if two instances of this logger refer to the same stream.
     /// Check if two instances of this logger refer to the same stream.
@@ -178,8 +191,9 @@ public:
     }
     }
 
 
 private:
 private:
-    std::string         name_;              ///< Full name of this logger
-    log4cplus::Logger   logger_;            ///< Underlying log4cplus logger
+    std::string                  name_;   ///< Full name of this logger
+    log4cplus::Logger            logger_; ///< Underlying log4cplus logger
+    isc::util::InterprocessSync* sync_;
 };
 };
 
 
 } // namespace log
 } // namespace log

+ 8 - 0
src/lib/log/logger_manager.cc

@@ -28,6 +28,7 @@
 #include <log/message_initializer.h>
 #include <log/message_initializer.h>
 #include <log/message_reader.h>
 #include <log/message_reader.h>
 #include <log/message_types.h>
 #include <log/message_types.h>
+#include "util/interprocess_sync_null.h"
 
 
 using namespace std;
 using namespace std;
 
 
@@ -148,6 +149,13 @@ LoggerManager::readLocalMessageFile(const char* file) {
 
 
     MessageDictionary& dictionary = MessageDictionary::globalDictionary();
     MessageDictionary& dictionary = MessageDictionary::globalDictionary();
     MessageReader reader(&dictionary);
     MessageReader reader(&dictionary);
+
+    // Turn off use of any lock files. This is because this logger can
+    // be used by standalone programs which may not have write access to
+    // the local state directory (to create lock files). So we switch to
+    // using a null interprocess sync object here.
+    logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+
     try {
     try {
 
 
         logger.info(LOG_READING_LOCAL_FILE).arg(file);
         logger.info(LOG_READING_LOCAL_FILE).arg(file);

+ 3 - 0
src/lib/log/logger_unittest_support.cc

@@ -160,6 +160,9 @@ void initLogger(isc::log::Severity severity, int dbglevel) {
     // Set the local message file
     // Set the local message file
     const char* localfile = getenv("B10_LOGGER_LOCALMSG");
     const char* localfile = getenv("B10_LOGGER_LOCALMSG");
 
 
+    // Set a directory for creating lockfiles when running tests
+    setenv("B10_LOCKFILE_DIR_FROM_BUILD", TOP_BUILDDIR, 1);
+
     // Initialize logging
     // Initialize logging
     initLogger(root, isc::log::DEBUG, isc::log::MAX_DEBUG_LEVEL, localfile);
     initLogger(root, isc::log::DEBUG, isc::log::MAX_DEBUG_LEVEL, localfile);
 
 

+ 3 - 3
src/lib/log/message_dictionary.cc

@@ -29,7 +29,7 @@ MessageDictionary::~MessageDictionary() {
 // Add message and note if ID already exists
 // Add message and note if ID already exists
 
 
 bool
 bool
-MessageDictionary::add(const string& ident, const string& text) {
+MessageDictionary::add(const std::string& ident, const std::string& text) {
     Dictionary::iterator i = dictionary_.find(ident);
     Dictionary::iterator i = dictionary_.find(ident);
     bool not_found = (i == dictionary_.end());
     bool not_found = (i == dictionary_.end());
     if (not_found) {
     if (not_found) {
@@ -44,7 +44,7 @@ MessageDictionary::add(const string& ident, const string& text) {
 // Add message and note if ID does not already exist
 // Add message and note if ID does not already exist
 
 
 bool
 bool
-MessageDictionary::replace(const string& ident, const string& text) {
+MessageDictionary::replace(const std::string& ident, const std::string& text) {
     Dictionary::iterator i = dictionary_.find(ident);
     Dictionary::iterator i = dictionary_.find(ident);
     bool found = (i != dictionary_.end());
     bool found = (i != dictionary_.end());
     if (found) {
     if (found) {
@@ -87,7 +87,7 @@ MessageDictionary::load(const char* messages[]) {
 // output.
 // output.
 
 
 const string&
 const string&
-MessageDictionary::getText(const string& ident) const {
+MessageDictionary::getText(const std::string& ident) const {
     static const string empty("");
     static const string empty("");
     Dictionary::const_iterator i = dictionary_.find(ident);
     Dictionary::const_iterator i = dictionary_.find(ident);
     if (i == dictionary_.end()) {
     if (i == dictionary_.end()) {

+ 9 - 0
src/lib/log/message_exception.h

@@ -38,6 +38,9 @@ public:
 
 
     /// \brief Constructor
     /// \brief Constructor
     ///
     ///
+    /// \param file Filename where the exception occurred.
+    /// \param line Line where exception occurred.
+    /// \param what Text description of the problem.
     /// \param id Message identification.
     /// \param id Message identification.
     /// \param lineno Line number on which error occurred (if > 0).
     /// \param lineno Line number on which error occurred (if > 0).
     MessageException(const char* file, size_t line, const char* what,
     MessageException(const char* file, size_t line, const char* what,
@@ -51,6 +54,9 @@ public:
 
 
     /// \brief Constructor
     /// \brief Constructor
     ///
     ///
+    /// \param file Filename where the exception occurred.
+    /// \param line Line where exception occurred.
+    /// \param what Text description of the problem.
     /// \param id Message identification.
     /// \param id Message identification.
     /// \param arg1 First message argument.
     /// \param arg1 First message argument.
     /// \param lineno Line number on which error occurred (if > 0).
     /// \param lineno Line number on which error occurred (if > 0).
@@ -66,6 +72,9 @@ public:
 
 
     /// \brief Constructor
     /// \brief Constructor
     ///
     ///
+    /// \param file Filename where the exception occurred.
+    /// \param line Line where exception occurred.
+    /// \param what Text description of the problem.
     /// \param id Message identification.
     /// \param id Message identification.
     /// \param arg1 First message argument.
     /// \param arg1 First message argument.
     /// \param arg2 Second message argument.
     /// \param arg2 Second message argument.

+ 4 - 0
src/lib/log/tests/.gitignore

@@ -6,6 +6,10 @@
 /initializer_unittests_2
 /initializer_unittests_2
 /local_file_test.sh
 /local_file_test.sh
 /logger_example
 /logger_example
+/logger_lock_test
+/logger_lock_test.sh
+/log_test_messages.cc
+/log_test_messages.h
 /run_unittests
 /run_unittests
 /severity_test.sh
 /severity_test.sh
 /tempdir.h
 /tempdir.h

+ 19 - 0
src/lib/log/tests/Makefile.am

@@ -12,6 +12,13 @@ endif
 
 
 CLEANFILES = *.gcno *.gcda
 CLEANFILES = *.gcno *.gcda
 
 
+EXTRA_DIST = log_test_messages.mes
+BUILT_SOURCES = log_test_messages.h log_test_messages.cc
+log_test_messages.h log_test_messages.cc: log_test_messages.mes
+	$(AM_V_GEN) $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/log/tests/log_test_messages.mes
+
+CLEANFILES += log_test_messages.h log_test_messages.cc
+
 noinst_PROGRAMS = logger_example
 noinst_PROGRAMS = logger_example
 logger_example_SOURCES = logger_example.cc
 logger_example_SOURCES = logger_example.cc
 logger_example_CPPFLAGS = $(AM_CPPFLAGS)
 logger_example_CPPFLAGS = $(AM_CPPFLAGS)
@@ -30,6 +37,16 @@ init_logger_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
 init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
 init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
 
 
+noinst_PROGRAMS += logger_lock_test
+logger_lock_test_SOURCES = logger_lock_test.cc
+nodist_logger_lock_test_SOURCES = log_test_messages.cc log_test_messages.h
+logger_lock_test_CPPFLAGS = $(AM_CPPFLAGS)
+logger_lock_test_LDFLAGS = $(AM_LDFLAGS)
+logger_lock_test_LDADD  = $(top_builddir)/src/lib/log/liblog.la
+logger_lock_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
+logger_lock_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+logger_lock_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+
 if HAVE_GTEST
 if HAVE_GTEST
 TESTS =
 TESTS =
 
 
@@ -62,6 +79,7 @@ run_unittests_SOURCES += logger_specification_unittest.cc
 run_unittests_SOURCES += message_dictionary_unittest.cc
 run_unittests_SOURCES += message_dictionary_unittest.cc
 run_unittests_SOURCES += message_reader_unittest.cc
 run_unittests_SOURCES += message_reader_unittest.cc
 run_unittests_SOURCES += output_option_unittest.cc
 run_unittests_SOURCES += output_option_unittest.cc
+nodist_run_unittests_SOURCES = log_test_messages.cc log_test_messages.h
 
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
 run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
@@ -104,4 +122,5 @@ check-local:
 	$(SHELL) $(abs_builddir)/destination_test.sh
 	$(SHELL) $(abs_builddir)/destination_test.sh
 	$(SHELL) $(abs_builddir)/init_logger_test.sh
 	$(SHELL) $(abs_builddir)/init_logger_test.sh
 	$(SHELL) $(abs_builddir)/local_file_test.sh
 	$(SHELL) $(abs_builddir)/local_file_test.sh
+	$(SHELL) $(abs_builddir)/logger_lock_test.sh
 	$(SHELL) $(abs_builddir)/severity_test.sh
 	$(SHELL) $(abs_builddir)/severity_test.sh

+ 26 - 0
src/lib/log/tests/log_test_messages.mes

@@ -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.
+
+# \brief Message Utility Message File
+#
+# This is the source of the set of messages generated by the message and
+# logging components.  The associated .h and .cc files are created by hand from
+# this file though and are not built during the build process; this is to avoid
+# the chicken-and-egg situation where we need the files to build the message
+# compiler, yet we need the compiler to build the files.
+
+$NAMESPACE isc::log
+
+% LOG_LOCK_TEST_MESSAGE this is a test message.
+This is a log message used in testing.

+ 9 - 1
src/lib/log/tests/logger_example.cc

@@ -41,6 +41,7 @@
 
 
 // Include a set of message definitions.
 // Include a set of message definitions.
 #include <log/log_messages.h>
 #include <log/log_messages.h>
+#include "util/interprocess_sync_null.h"
 
 
 using namespace isc::log;
 using namespace isc::log;
 using namespace std;
 using namespace std;
@@ -280,10 +281,17 @@ int main(int argc, char** argv) {
         LoggerManager::readLocalMessageFile(argv[optind]);
         LoggerManager::readLocalMessageFile(argv[optind]);
     }
     }
 
 
-    // Log a few messages to different loggers.
+    // Log a few messages to different loggers. Here, we switch to using
+    // null interprocess sync objects for the loggers below as the
+    // logger example can be used as a standalone program (which may not
+    // have write access to a local state directory to create
+    // lockfiles).
     isc::log::Logger logger_ex(ROOT_NAME);
     isc::log::Logger logger_ex(ROOT_NAME);
+    logger_ex.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
     isc::log::Logger logger_alpha("alpha");
     isc::log::Logger logger_alpha("alpha");
+    logger_alpha.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
     isc::log::Logger logger_beta("beta");
     isc::log::Logger logger_beta("beta");
+    logger_beta.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
 
 
     LOG_FATAL(logger_ex, LOG_WRITE_ERROR).arg("test1").arg("42");
     LOG_FATAL(logger_ex, LOG_WRITE_ERROR).arg("test1").arg("42");
     LOG_ERROR(logger_ex, LOG_READING_LOCAL_FILE).arg("dummy/file");
     LOG_ERROR(logger_ex, LOG_READING_LOCAL_FILE).arg("dummy/file");

+ 64 - 0
src/lib/log/tests/logger_lock_test.cc

@@ -0,0 +1,64 @@
+// 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 <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include "util/interprocess_sync.h"
+#include "log_test_messages.h"
+#include <iostream>
+
+using namespace std;
+using namespace isc::log;
+
+class MockLoggingSync : public isc::util::InterprocessSync {
+public:
+    /// \brief Constructor
+    MockLoggingSync(const std::string& component_name) :
+        InterprocessSync(component_name)
+    {}
+
+protected:
+    virtual bool lock() {
+        cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: LOCK\n";
+        return (true);
+    }
+
+    virtual bool tryLock() {
+        cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: TRYLOCK\n";
+        return (true);
+    }
+
+    virtual bool unlock() {
+        cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: UNLOCK\n";
+        return (true);
+    }
+};
+
+/// \brief Test logger lock sequence
+///
+/// A program used in testing the logger. It verifies that (1) an
+/// interprocess sync lock is first acquired by the logger, (2) the
+/// message is logged by the logger, and (3) the lock is released in
+/// that sequence.
+int
+main(int, char**) {
+    initLogger();
+    Logger logger("log");
+    logger.setInterprocessSync(new MockLoggingSync("log"));
+
+    LOG_INFO(logger, LOG_LOCK_TEST_MESSAGE);
+
+    return (0);
+}

+ 0 - 0
src/lib/log/tests/logger_lock_test.sh.in


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