Browse Source

Merge branch 'master' into trac1828

Conflicts:
	src/bin/ddns/tests/ddns_test.py
	src/bin/zonemgr/tests/zonemgr_test.py
Mukund Sivaraman 13 years ago
parent
commit
464682a218
100 changed files with 5996 additions and 2894 deletions
  1. 8 0
      .gitignore
  2. 21 0
      AUTHORS
  3. 6 0
      COPYING
  4. 164 1
      ChangeLog
  5. 11 0
      Makefile.am
  6. 16 7
      compatcheck/Makefile.am
  7. 0 60
      compatcheck/sqlite3-difftbl-check.py.in
  8. 17 29
      configure.ac
  9. 0 630
      depcomp
  10. 11 0
      dns++.pc.in
  11. 1 0
      doc/.gitignore
  12. 15 10
      doc/Doxyfile
  13. 13 0
      doc/Makefile.am
  14. 14 0
      doc/devel/01-dns.dox
  15. 117 0
      doc/devel/02-dhcp.dox
  16. 36 0
      doc/devel/mainpage.dox
  17. 4 3
      doc/guide/Makefile.am
  18. 215 87
      doc/guide/bind10-guide.html
  19. 371 246
      doc/guide/bind10-guide.txt
  20. 104 37
      doc/guide/bind10-guide.xml
  21. 207 19
      doc/guide/bind10-messages.html
  22. 142 8
      doc/guide/bind10-messages.xml
  23. BIN
      doc/images/isc-logo.png
  24. 23 0
      ext/LICENSE_1_0.txt
  25. 0 520
      install-sh
  26. 5 0
      m4macros/.gitignore
  27. 0 376
      missing
  28. 4 3
      src/bin/auth/Makefile.am
  29. 4 0
      src/bin/auth/auth.spec.pre.in
  30. 62 128
      src/bin/auth/auth_config.cc
  31. 14 0
      src/bin/auth/auth_messages.mes
  32. 171 30
      src/bin/auth/auth_srv.cc
  33. 47 25
      src/bin/auth/auth_srv.h
  34. 17 5
      src/bin/auth/b10-auth.8
  35. 14 7
      src/bin/auth/b10-auth.xml
  36. 6 2
      src/bin/auth/benchmarks/query_bench.cc
  37. 89 12
      src/bin/auth/command.cc
  38. 19 1
      src/bin/auth/common.cc
  39. 14 0
      src/bin/auth/common.h
  40. 4 1
      src/bin/auth/main.cc
  41. 2 1
      src/bin/auth/spec_config.h.pre.in
  42. 20 9
      src/bin/auth/tests/Makefile.am
  43. 305 46
      src/bin/auth/tests/auth_srv_unittest.cc
  44. 191 12
      src/bin/auth/tests/command_unittest.cc
  45. 39 13
      src/bin/auth/tests/common_unittest.cc
  46. 71 0
      src/bin/auth/tests/config_syntax_unittest.cc
  47. 154 24
      src/bin/auth/tests/config_unittest.cc
  48. 77 0
      src/bin/auth/tests/datasrc_util.cc
  49. 58 0
      src/bin/auth/tests/datasrc_util.h
  50. BIN
      src/bin/auth/tests/testdata/example.sqlite3
  51. 10 63
      src/bin/bind10/bind10.8
  52. 21 21
      src/bin/bind10/bind10.xml
  53. 4 5
      src/bin/bind10/bind10_messages.mes
  54. 24 0
      src/bin/bind10/bind10_src.py.in
  55. 0 8
      src/bin/bind10/bob.spec
  56. 1 0
      src/bin/bind10/tests/Makefile.am
  57. 35 0
      src/bin/bind10/tests/bind10_test.py.in
  58. 1 1
      src/bin/bindctl/Makefile.am
  59. 94 12
      src/bin/bindctl/bindcmd.py
  60. 2 0
      src/bin/bindctl/bindctl_main.py.in
  61. 94 0
      src/bin/bindctl/command_sets.py
  62. 12 5
      src/bin/cfgmgr/b10-cfgmgr.8
  63. 29 4
      src/bin/cfgmgr/b10-cfgmgr.py.in
  64. 26 5
      src/bin/cfgmgr/b10-cfgmgr.xml
  65. 17 7
      src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
  66. 1 0
      src/bin/cmdctl/tests/Makefile.am
  67. 2 5
      src/bin/dbutil/dbutil.py.in
  68. 1 0
      src/bin/dbutil/tests/Makefile.am
  69. 21 26
      src/bin/dbutil/tests/dbutil_test.sh.in
  70. 439 27
      src/bin/ddns/ddns.py.in
  71. 19 5
      src/bin/ddns/ddns.spec
  72. 139 0
      src/bin/ddns/ddns_messages.mes
  73. 1 0
      src/bin/ddns/tests/Makefile.am
  74. 927 13
      src/bin/ddns/tests/ddns_test.py
  75. 9 5
      src/bin/dhcp4/Makefile.am
  76. 26 33
      src/bin/dhcp4/dhcp4_srv.cc
  77. 14 21
      src/bin/dhcp4/dhcp4_srv.h
  78. 19 6
      src/bin/dhcp4/main.cc
  79. 21 2
      src/bin/dhcp4/tests/Makefile.am
  80. 170 0
      src/bin/dhcp4/tests/dhcp4_test.py
  81. 13 9
      src/bin/dhcp6/Makefile.am
  82. 127 79
      src/bin/dhcp6/dhcp6_srv.cc
  83. 18 28
      src/bin/dhcp6/dhcp6_srv.h
  84. 0 10
      src/bin/dhcp6/interfaces.txt
  85. 17 6
      src/bin/dhcp6/main.cc
  86. 11 9
      src/bin/dhcp6/tests/Makefile.am
  87. 94 26
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  88. 126 25
      src/bin/dhcp6/tests/dhcp6_test.py
  89. 1 1
      src/bin/host/host.cc
  90. 29 29
      src/bin/resolver/resolver_messages.mes
  91. 1 1
      src/bin/sockcreator/Makefile.am
  92. 6 1
      src/bin/stats/stats_httpd.py.in
  93. 16 16
      src/bin/stats/stats_httpd_messages.mes
  94. 13 13
      src/bin/stats/stats_messages.mes
  95. 1 0
      src/bin/stats/tests/Makefile.am
  96. 18 0
      src/bin/stats/tests/b10-stats-httpd_test.py
  97. BIN
      src/bin/xfrin/tests/testdata/example.com.sqlite3
  98. 316 0
      src/bin/xfrin/tests/xfrin_test.py
  99. 107 15
      src/bin/xfrin/xfrin.py.in
  100. 0 0
      src/bin/xfrin/xfrin_messages.mes

+ 8 - 0
.gitignore

@@ -1,3 +1,6 @@
+*.gcda
+*.gcno
+*.gcov
 *.la
 *.lo
 *.o
@@ -27,3 +30,8 @@ TAGS
 /missing
 /py-compile
 /stamp-h1
+
+/all.info
+/coverage-cpp-html
+/dns++.pc
+/report.info

+ 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

+ 6 - 0
COPYING

@@ -11,3 +11,9 @@ 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.
+
+-----------------------------------------------------------------------------
+
+The ext/asio and ext/coroutine code is externally maintained and
+distributed under the Boost Software License, Version 1.0.
+(See accompanying file ext/LICENSE_1_0.txt.)

+ 164 - 1
ChangeLog

@@ -1,3 +1,166 @@
+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
+	b10-dhcp4, b10-dhcp6: Both DHCP servers now accept -p parameter
+	that can be used to specify listening port number. This capability
+	is useful only for testing purposes.
+	(Trac #1503, git e60af9fa16a6094d2204f27c40a648fae313bdae)
+
+441.	[func]		tomek
+	libdhcp++: Stub interface detection (support for interfaces.txt
+	file) was removed.
+	(Trac #1281, git 900fc8b420789a8c636bcf20fdaffc60bc1041e0)
+
+bind10-devel-20120517 released on May 17. 2012
+
+440.	[func]		muks
+	bindctl: improved some error messages so they will be more
+	helpful.  Those include the one when the zone name is unspecified
+	or the name is invalid in the b10-auth configuration.
+	(Trac #1627, git 1a4d0ae65b2c1012611f4c15c5e7a29d65339104)
+
+439.	[func]		team
+	The in-memory data source can now load zones from the
+	sqlite3 data source, so that zones stored in the database
+	(and updated for example by xfrin) can be served from memory.
+	(Trac #1789,#1790,#1792,#1793,#1911,
+	git 93f11d2a96ce4dba9308889bdb9be6be4a765b27)
+
+438.	[bug]		naokikambe
+	b10-stats-httpd now sends the system a notification that
+	it is shutting down if it encounters a fatal error during
+	startup.
+	(Trac #1852, git a475ef271d4606f791e5ed88d9b8eb8ed8c90ce6)
+
+437.	[build]		jinmei
+	Building BIND 10 may fail on MacOS if Python has been
+	installed via Homebrew unless --without-werror is specified.
+	The configure script now includes a URL that explains this
+	issue when it detects failure that is possibly because of
+	this problem.
+	(Trac #1907, git 0d03b06138e080cc0391fb912a5a5e75f0f97cec)
+
+436.	[bug]		jelte
+	The --config-file option now works correctly with relative paths if
+	--data-path is not given.
+	(Trac #1889, git ce7d1aef2ca88084e4dacef97132337dd3e50d6c)
+
+435.	[func]		team
+	The in-memory datasource now supports NSEC-signed zones.
+	(Trac #1802-#1810, git 2f9aa4a553a05aa1d9eac06f1140d78f0c99408b)
+
+434.	[func]		tomek
+	libdhcp++: Linux interface detection refactored. The code is
+	now cleaner. Tests better support certain versions of ifconfig.
+	(Trac #1528, git 221f5649496821d19a40863e53e72685524b9ab2)
+
+433.	[func]		tomek
+	libdhcp++: Option6 and Pkt6 now follow the same design as
+	options and packet for DHCPv4. General code refactoring after
+	end of 2011 year release.
+	(Trac #1540, git a40b6c665617125eeb8716b12d92d806f0342396)
+
+432.	[bug]*		muks
+	BIND 10 now installs its header files in a BIND 10 specific
+	sub-directory in the install prefix.
+	(Trac #1930, git fcf2f08db9ebc2198236bfa25cf73286821cba6b)
+
+431.	[func]*		muks
+	BIND 10 no longer starts b10-stats-httpd by default.
+	(Trac #1885, git 5c8bbd7ab648b6b7c48e366e7510dedca5386f6c)
+
+430.	[bug]		jelte
+	When displaying configuration data, bindctl no longer treats
+	optional list items as an error, but shows them as an empty list.
+	(Trac #1520, git 0f18039bc751a8f498c1f832196e2ecc7b997b2a)
+
+429.	[func]		jelte
+	Added an 'execute' component to bindctl, which executes either a set
+	of commands from a file or a built-in set of commands. Currently,
+	only 'init_authoritative_server' is provided as a built-in set, but
+	it is expected that more will be added later.
+	(Trac #1843, git 551657702a4197ef302c567b5c0eaf2fded3e121)
+
+428.	[bug]		marcin
+	perfdhcp: bind to local address to allow reception of
+	replies from IPv6 DHCP servers.
+	(Trac #1908, git 597e059afaa4a89e767f8f10d2a4d78223af3940)
+
+427.	[bug]		jinmei
+	libdatasrc, b10-xfrin: the zone updater for database-based data
+	sources now correctly distinguishes NSEC3-related RRs (NSEC3 and
+	NSEC3-covering RRSIG) from others, and the SQLite3 implementation
+	now manipulates them in the separate table for the NSEC3 namespace.
+	As a result b10-xfrin now correctly updates NSEC3-signed zones by
+	inbound zone transfers.
+	(Trac #1781,#1788,#1891, git 672f129700dae33b701bb02069cf276238d66be3)
+
+426.	[bug]		vorner
+	The NSEC3 records are now included when transferring a
+	signed zone out.
+	(Trac #1782, git 36efa7d10ecc4efd39d2ce4dfffa0cbdeffa74b0)
+
+425.	[func]*		muks
+	Don't autostart b10-auth, b10-xfrin, b10-xfrout and b10-zonemgr in
+	the default configuration.
+	(Trac #1818, git 31de885ba0409f54d9a1615eff5a4b03ed420393)
+
+424.	[bug]		jelte
+	Fixed a bug in bindctl where in some cases, configuration settings
+	in a named set could disappear, if a child element is modified.
+	(Trac #1491, git 00a36e752802df3cc683023d256687bf222e256a)
+
+423.	[bug]		jinmei
+	The database based zone iterator now correctly resets mixed TTLs
+	of the same RRset (when that happens) to the lowest one.  The
+	previous implementation could miss lower ones if it appears in a
+	later part of the RRset.
+	(part of Trac #1791, git f1f0bc00441057e7050241415ee0367a09c35032)
+
+422.	[bug]		jinmei
+	The database based zone iterator now separates RRSIGs of the same
+	name and type but for different covered types.
+	(part of Trac #1791, git b4466188150a50872bc3c426242bc7bba4c5f38d)
+
+421.	[build]		jinmei
+	Made sure BIND 10 can be built with clang++ 3.1.  (It failed on
+	MacOS 10.7 using Xcode 4.3, but it's more likely to be a matter of
+	clang version.)
+	(Trac #1773, git ceaa247d89ac7d97594572bc17f005144c5efb8d)
+
+420.	[bug]*		jinmei, stephen
+	Updated the DB schema used in the SQLite3 data source so it can
+	use SQL indices more effectively.  The previous schema had several
+	issues in this sense and could be very slow for some queries on a
+	very large zone (especially for negative answers).  This change
+	requires a major version up of the schema; use b10-dbutil to
+	upgrade existing database files.  Note: 'make install' will fail
+	unless old DB files installed in the standard location have been
+	upgraded.
+	(Trac #324, git 8644866497053f91ada4e99abe444d7876ed00ff)
+
 419.	[bug]		jelte
 	JSON handler has been improved; escaping now works correctly
 	(including quotes in strings), and it now rejects more types of
@@ -107,7 +270,7 @@ bind10-devel-20120329 released on March 29, 2012
 	providing result for random instance.
 	(Trac #1751, git 3285353a660e881ec2b645e1bc10d94e5020f357)
 
-403.	[build]*	jelte
+403.	[build]*		jelte
 	The configure option for botan (--with-botan=PATH) is replaced by
 	--with-botan-config=PATH, which takes a full path to a botan-config
 	script, instead of the botan 'install' directory. Also, if not

+ 11 - 0
Makefile.am

@@ -1,3 +1,7 @@
+ACLOCAL_AMFLAGS = -I m4macros ${ACLOCAL_FLAGS}
+# ^^^^^^^^ This has to be the first line and cannot come later in this
+# Makefile.am due to some bork in some versions of autotools.
+
 SUBDIRS = compatcheck doc src tests
 USE_LCOV=@USE_LCOV@
 LCOV=@LCOV@
@@ -12,6 +16,8 @@ DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations
 # Use same --with-gtest flag if set
 DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG)
 
+dist_doc_DATA = AUTHORS COPYING ChangeLog README
+
 clean-cpp-coverage:
 	@if [ $(USE_LCOV) = yes ] ; then \
 		$(LCOV) --directory . --zerocounters; \
@@ -398,3 +404,8 @@ EXTRA_DIST += ext/asio/asio/system_error.hpp
 EXTRA_DIST += ext/asio/asio/deadline_timer.hpp
 EXTRA_DIST += ext/asio/asio/stream_socket_service.hpp
 EXTRA_DIST += ext/coroutine/coroutine.h
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = dns++.pc
+
+CLEANFILES = $(abs_top_builddir)/logger_lockfile

+ 16 - 7
compatcheck/Makefile.am

@@ -1,8 +1,17 @@
-noinst_SCRIPTS = sqlite3-difftbl-check.py
-
-# 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:
-	$(PYTHON) sqlite3-difftbl-check.py \
-	$(localstatedir)/$(PACKAGE)/zone.sqlite3
+	if test -e $(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3; then \
+		$(SHELL) $(top_builddir)/src/bin/dbutil/run_dbutil.sh --check \
+		$(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3 || \
+		(echo "\nSQLite3 DB file schema version is old.  " \
+		"Please run: " \
+		"$(abs_top_builddir)/src/bin/dbutil/run_dbutil.sh --upgrade " \
+		"$(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3"; exit 1) \
+	fi

+ 0 - 60
compatcheck/sqlite3-difftbl-check.py.in

@@ -1,60 +0,0 @@
-#!@PYTHON@
-
-# Copyright (C) 2011  Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import os, sqlite3, sys
-from optparse import OptionParser
-
-usage = 'usage: %prog [options] db_file'
-parser = OptionParser(usage=usage)
-parser.add_option("-u", "--upgrade", action="store_true",
-                  dest="upgrade", default=False,
-                  help="Upgrade the database file [default: %default]")
-(options, args) = parser.parse_args()
-if len(args) == 0:
-    parser.error('missing argument')
-
-db_file = args[0]
-
-# If the file doesn't exist, there's nothing to do
-if not os.path.exists(db_file):
-    sys.exit(0)
-
-conn = sqlite3.connect(db_file)
-cur = conn.cursor()
-try:
-    # This can be anything that works iff the "diffs" table exists
-    cur.execute('SELECT name FROM diffs DESC LIMIT 1')
-except sqlite3.OperationalError as ex:
-    # If it fails with 'no such table', create a new one or fail with
-    # warning depending on the --upgrade command line option.
-    if str(ex) == 'no such table: diffs':
-        if options.upgrade:
-            cur.execute('CREATE TABLE diffs (id INTEGER PRIMARY KEY, ' +
-                        'zone_id INTEGER NOT NULL, ' +
-                        'version INTEGER NOT NULL, ' +
-                        'operation INTEGER NOT NULL, ' +
-                        'name STRING NOT NULL COLLATE NOCASE, ' +
-                        'rrtype STRING NOT NULL COLLATE NOCASE, ' +
-                        'ttl INTEGER NOT NULL, rdata STRING NOT NULL)')
-        else:
-            sys.stdout.write('Found an older version of SQLite3 DB file: ' +
-                             db_file + '\n' + "Perform '" + os.getcwd() +
-                             "/sqlite3-difftbl-check.py --upgrade " +
-                             db_file + "'\n" +
-                             'before continuing install.\n')
-            sys.exit(1)
-conn.close()

+ 17 - 29
configure.ac

@@ -2,11 +2,12 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20120316, bind10-dev@isc.org)
+AC_INIT(bind10-devel, 20120405, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
 AM_INIT_AUTOMAKE
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
 AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_MACRO_DIR([m4macros])
 
 # Checks for programs.
 AC_PROG_CXX
@@ -361,7 +362,7 @@ if test $werror_ok = 1; then
 		 PYTHON_CXXFLAGS="${PYTHON_CXXFLAGS} -Wno-unused-parameter"
 		 AC_SUBST(PYTHON_CXXFLAGS)
 		],
-		[AC_MSG_ERROR([Can't compile against Python.h])]
+		[AC_MSG_ERROR([Can't compile against Python.h.  If you're using MacOS X and have installed Python with Homebrew, see http://bind10.isc.org/wiki/SystemNotesMacOSX])]
                 )
                 ]
 	)
@@ -406,9 +407,9 @@ case $system in
       OS_TYPE="BSD"
       CPPFLAGS="$CPPFLAGS -DOS_BSD"
       ;;
-    Solaris)
+    SunOS)
       OS_TYPE="Solaris"
-      CPPFLAGS="$CPPFLAGS -DOS_SOLARIS"
+      CPPFLAGS="$CPPFLAGS -DOS_SUN"
       ;;
     *)
       OS_TYPE="Unknown"
@@ -924,29 +925,6 @@ CPPFLAGS="$CPPFLAGS -I\$(top_srcdir)/ext/coroutine"
 #
 # Disable threads: Currently we don't use them.
 CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_THREADS=1"
-#
-# kqueue portability: ASIO uses kqueue by default if it's available (it's
-# generally available in BSD variants).  Unfortunately, some public
-# implementation of kqueue forces a conversion from a pointer to an integer,
-# which is prohibited in C++ unless reinterpret_cast, C++'s most evil beast
-# (and ASIO doesn't use it anyway) is used.  This will cause build error for
-# some of our C++ files including ASIO header files.  The following check
-# detects such cases and tells ASIO not to use kqueue if so.
-AC_CHECK_FUNC(kqueue, ac_cv_have_kqueue=yes, ac_cv_have_kqueue=no)
-if test "X$ac_cv_have_kqueue" = "Xyes"; then
-	AC_MSG_CHECKING([whether kqueue EV_SET compiles in C++])
-	AC_TRY_COMPILE([
-#include <sys/types.h>
-#include <sys/param.h>
-#include <sys/event.h>],
-[char* udata;
-struct kevent kevent;
-EV_SET(&kevent, 0, 0, 0, 0, 0, udata);],
-	[AC_MSG_RESULT(yes)],
-	[AC_MSG_RESULT([no, disable kqueue for ASIO])
-	 CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_KQUEUE=1"
-	])
-fi
 
 # Check for functions that are not available on all platforms
 AC_CHECK_FUNCS([pselect])
@@ -999,6 +977,11 @@ AC_ARG_ENABLE(install-configurations,
 
 AM_CONDITIONAL(INSTALL_CONFIGURATIONS, test x$install_configurations = xyes || test x$install_configurations = xtrue)
 
+AC_ARG_ENABLE(logger-checks, [AC_HELP_STRING([--enable-logger-checks],
+  [check logger messages [default=no]])], enable_logger_checks=$enableval, enable_logger_checks=no)
+AM_CONDITIONAL(ENABLE_LOGGER_CHECKS, test x$enable_logger_checks != xno)
+AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Check logger messages?])])
+
 AC_CONFIG_FILES([Makefile
                  doc/Makefile
                  doc/guide/Makefile
@@ -1084,6 +1067,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/testutils/Makefile
                  src/lib/python/isc/bind10/Makefile
                  src/lib/python/isc/bind10/tests/Makefile
+                 src/lib/python/isc/ddns/Makefile
+                 src/lib/python/isc/ddns/tests/Makefile
                  src/lib/python/isc/xfrin/Makefile
                  src/lib/python/isc/xfrin/tests/Makefile
                  src/lib/python/isc/server_common/Makefile
@@ -1137,9 +1122,10 @@ AC_CONFIG_FILES([Makefile
                  tests/tools/badpacket/Makefile
                  tests/tools/badpacket/tests/Makefile
                  tests/tools/perfdhcp/Makefile
+                 tests/tools/perfdhcp/tests/Makefile
+                 dns++.pc
                ])
 AC_OUTPUT([doc/version.ent
-           compatcheck/sqlite3-difftbl-check.py
            src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/cfgmgr/tests/b10-cfgmgr_test.py
            src/bin/cmdctl/cmdctl.py
@@ -1201,6 +1187,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/log/tests/destination_test.sh
            src/lib/log/tests/init_logger_test.sh
            src/lib/log/tests/local_file_test.sh
+           src/lib/log/tests/logger_lock_test.sh
            src/lib/log/tests/severity_test.sh
            src/lib/log/tests/tempdir.h
            src/lib/util/python/mkpywrapper.py
@@ -1225,7 +1212,6 @@ AC_OUTPUT([doc/version.ent
            tests/system/ixfr/in-3/setup.sh
            tests/system/ixfr/in-4/setup.sh
           ], [
-           chmod +x compatcheck/sqlite3-difftbl-check.py
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
            chmod +x src/bin/xfrout/run_b10-xfrout.sh
@@ -1250,6 +1236,7 @@ AC_OUTPUT([doc/version.ent
            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/local_file_test.sh
+           chmod +x src/lib/log/tests/logger_lock_test.sh
            chmod +x src/lib/log/tests/severity_test.sh
            chmod +x src/lib/util/python/mkpywrapper.py
            chmod +x src/lib/util/python/gen_wiredata.py
@@ -1307,6 +1294,7 @@ Developer:
   Google Tests:  $gtest_path
   C++ Code Coverage: $USE_LCOV
   Python Code Coverage: $USE_PYCOVERAGE
+  Logger checks: $enable_logger_checks
   Generate Manuals:  $enable_man
 
 END

+ 0 - 630
depcomp

@@ -1,630 +0,0 @@
-#! /bin/sh
-# depcomp - compile a program generating dependencies as side-effects
-
-scriptversion=2009-04-28.21; # UTC
-
-# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009 Free
-# Software Foundation, Inc.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
-
-case $1 in
-  '')
-     echo "$0: No command.  Try \`$0 --help' for more information." 1>&2
-     exit 1;
-     ;;
-  -h | --h*)
-    cat <<\EOF
-Usage: depcomp [--help] [--version] PROGRAM [ARGS]
-
-Run PROGRAMS ARGS to compile a file, generating dependencies
-as side-effects.
-
-Environment variables:
-  depmode     Dependency tracking mode.
-  source      Source file read by `PROGRAMS ARGS'.
-  object      Object file output by `PROGRAMS ARGS'.
-  DEPDIR      directory where to store dependencies.
-  depfile     Dependency file to output.
-  tmpdepfile  Temporary file to use when outputing dependencies.
-  libtool     Whether libtool is used (yes/no).
-
-Report bugs to <bug-automake@gnu.org>.
-EOF
-    exit $?
-    ;;
-  -v | --v*)
-    echo "depcomp $scriptversion"
-    exit $?
-    ;;
-esac
-
-if test -z "$depmode" || test -z "$source" || test -z "$object"; then
-  echo "depcomp: Variables source, object and depmode must be set" 1>&2
-  exit 1
-fi
-
-# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
-depfile=${depfile-`echo "$object" |
-  sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
-tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
-
-rm -f "$tmpdepfile"
-
-# Some modes work just like other modes, but use different flags.  We
-# parameterize here, but still list the modes in the big case below,
-# to make depend.m4 easier to write.  Note that we *cannot* use a case
-# here, because this file can only contain one case statement.
-if test "$depmode" = hp; then
-  # HP compiler uses -M and no extra arg.
-  gccflag=-M
-  depmode=gcc
-fi
-
-if test "$depmode" = dashXmstdout; then
-   # This is just like dashmstdout with a different argument.
-   dashmflag=-xM
-   depmode=dashmstdout
-fi
-
-cygpath_u="cygpath -u -f -"
-if test "$depmode" = msvcmsys; then
-   # This is just like msvisualcpp but w/o cygpath translation.
-   # Just convert the backslash-escaped backslashes to single forward
-   # slashes to satisfy depend.m4
-   cygpath_u="sed s,\\\\\\\\,/,g"
-   depmode=msvisualcpp
-fi
-
-case "$depmode" in
-gcc3)
-## gcc 3 implements dependency tracking that does exactly what
-## we want.  Yay!  Note: for some reason libtool 1.4 doesn't like
-## it if -MD -MP comes after the -MF stuff.  Hmm.
-## Unfortunately, FreeBSD c89 acceptance of flags depends upon
-## the command line argument order; so add the flags where they
-## appear in depend2.am.  Note that the slowdown incurred here
-## affects only configure: in makefiles, %FASTDEP% shortcuts this.
-  for arg
-  do
-    case $arg in
-    -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
-    *)  set fnord "$@" "$arg" ;;
-    esac
-    shift # fnord
-    shift # $arg
-  done
-  "$@"
-  stat=$?
-  if test $stat -eq 0; then :
-  else
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  mv "$tmpdepfile" "$depfile"
-  ;;
-
-gcc)
-## There are various ways to get dependency output from gcc.  Here's
-## why we pick this rather obscure method:
-## - Don't want to use -MD because we'd like the dependencies to end
-##   up in a subdir.  Having to rename by hand is ugly.
-##   (We might end up doing this anyway to support other compilers.)
-## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
-##   -MM, not -M (despite what the docs say).
-## - Using -M directly means running the compiler twice (even worse
-##   than renaming).
-  if test -z "$gccflag"; then
-    gccflag=-MD,
-  fi
-  "$@" -Wp,"$gccflag$tmpdepfile"
-  stat=$?
-  if test $stat -eq 0; then :
-  else
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-  echo "$object : \\" > "$depfile"
-  alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
-## The second -e expression handles DOS-style file names with drive letters.
-  sed -e 's/^[^:]*: / /' \
-      -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
-## This next piece of magic avoids the `deleted header file' problem.
-## The problem is that when a header file which appears in a .P file
-## is deleted, the dependency causes make to die (because there is
-## typically no way to rebuild the header).  We avoid this by adding
-## dummy dependencies for each header file.  Too bad gcc doesn't do
-## this for us directly.
-  tr ' ' '
-' < "$tmpdepfile" |
-## Some versions of gcc put a space before the `:'.  On the theory
-## that the space means something, we add a space to the output as
-## well.
-## Some versions of the HPUX 10.20 sed can't process this invocation
-## correctly.  Breaking it into two sed invocations is a workaround.
-    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-hp)
-  # This case exists only to let depend.m4 do its work.  It works by
-  # looking at the text of this script.  This case will never be run,
-  # since it is checked for above.
-  exit 1
-  ;;
-
-sgi)
-  if test "$libtool" = yes; then
-    "$@" "-Wp,-MDupdate,$tmpdepfile"
-  else
-    "$@" -MDupdate "$tmpdepfile"
-  fi
-  stat=$?
-  if test $stat -eq 0; then :
-  else
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-
-  if test -f "$tmpdepfile"; then  # yes, the sourcefile depend on other files
-    echo "$object : \\" > "$depfile"
-
-    # Clip off the initial element (the dependent).  Don't try to be
-    # clever and replace this with sed code, as IRIX sed won't handle
-    # lines with more than a fixed number of characters (4096 in
-    # IRIX 6.2 sed, 8192 in IRIX 6.5).  We also remove comment lines;
-    # the IRIX cc adds comments like `#:fec' to the end of the
-    # dependency line.
-    tr ' ' '
-' < "$tmpdepfile" \
-    | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \
-    tr '
-' ' ' >> "$depfile"
-    echo >> "$depfile"
-
-    # The second pass generates a dummy entry for each header file.
-    tr ' ' '
-' < "$tmpdepfile" \
-   | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
-   >> "$depfile"
-  else
-    # The sourcefile does not contain any dependencies, so just
-    # store a dummy comment line, to avoid errors with the Makefile
-    # "include basename.Plo" scheme.
-    echo "#dummy" > "$depfile"
-  fi
-  rm -f "$tmpdepfile"
-  ;;
-
-aix)
-  # The C for AIX Compiler uses -M and outputs the dependencies
-  # in a .u file.  In older versions, this file always lives in the
-  # current directory.  Also, the AIX compiler puts `$object:' at the
-  # start of each line; $object doesn't have directory information.
-  # Version 6 uses the directory in both cases.
-  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
-  test "x$dir" = "x$object" && dir=
-  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
-  if test "$libtool" = yes; then
-    tmpdepfile1=$dir$base.u
-    tmpdepfile2=$base.u
-    tmpdepfile3=$dir.libs/$base.u
-    "$@" -Wc,-M
-  else
-    tmpdepfile1=$dir$base.u
-    tmpdepfile2=$dir$base.u
-    tmpdepfile3=$dir$base.u
-    "$@" -M
-  fi
-  stat=$?
-
-  if test $stat -eq 0; then :
-  else
-    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
-    exit $stat
-  fi
-
-  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
-  do
-    test -f "$tmpdepfile" && break
-  done
-  if test -f "$tmpdepfile"; then
-    # Each line is of the form `foo.o: dependent.h'.
-    # Do two passes, one to just change these to
-    # `$object: dependent.h' and one to simply `dependent.h:'.
-    sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
-    # That's a tab and a space in the [].
-    sed -e 's,^.*\.[a-z]*:[	 ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
-  else
-    # The sourcefile does not contain any dependencies, so just
-    # store a dummy comment line, to avoid errors with the Makefile
-    # "include basename.Plo" scheme.
-    echo "#dummy" > "$depfile"
-  fi
-  rm -f "$tmpdepfile"
-  ;;
-
-icc)
-  # Intel's C compiler understands `-MD -MF file'.  However on
-  #    icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c
-  # ICC 7.0 will fill foo.d with something like
-  #    foo.o: sub/foo.c
-  #    foo.o: sub/foo.h
-  # which is wrong.  We want:
-  #    sub/foo.o: sub/foo.c
-  #    sub/foo.o: sub/foo.h
-  #    sub/foo.c:
-  #    sub/foo.h:
-  # ICC 7.1 will output
-  #    foo.o: sub/foo.c sub/foo.h
-  # and will wrap long lines using \ :
-  #    foo.o: sub/foo.c ... \
-  #     sub/foo.h ... \
-  #     ...
-
-  "$@" -MD -MF "$tmpdepfile"
-  stat=$?
-  if test $stat -eq 0; then :
-  else
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-  # Each line is of the form `foo.o: dependent.h',
-  # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
-  # Do two passes, one to just change these to
-  # `$object: dependent.h' and one to simply `dependent.h:'.
-  sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
-  # Some versions of the HPUX 10.20 sed can't process this invocation
-  # correctly.  Breaking it into two sed invocations is a workaround.
-  sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" |
-    sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-hp2)
-  # The "hp" stanza above does not work with aCC (C++) and HP's ia64
-  # compilers, which have integrated preprocessors.  The correct option
-  # to use with these is +Maked; it writes dependencies to a file named
-  # 'foo.d', which lands next to the object file, wherever that
-  # happens to be.
-  # Much of this is similar to the tru64 case; see comments there.
-  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
-  test "x$dir" = "x$object" && dir=
-  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
-  if test "$libtool" = yes; then
-    tmpdepfile1=$dir$base.d
-    tmpdepfile2=$dir.libs/$base.d
-    "$@" -Wc,+Maked
-  else
-    tmpdepfile1=$dir$base.d
-    tmpdepfile2=$dir$base.d
-    "$@" +Maked
-  fi
-  stat=$?
-  if test $stat -eq 0; then :
-  else
-     rm -f "$tmpdepfile1" "$tmpdepfile2"
-     exit $stat
-  fi
-
-  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
-  do
-    test -f "$tmpdepfile" && break
-  done
-  if test -f "$tmpdepfile"; then
-    sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile"
-    # Add `dependent.h:' lines.
-    sed -ne '2,${
-	       s/^ *//
-	       s/ \\*$//
-	       s/$/:/
-	       p
-	     }' "$tmpdepfile" >> "$depfile"
-  else
-    echo "#dummy" > "$depfile"
-  fi
-  rm -f "$tmpdepfile" "$tmpdepfile2"
-  ;;
-
-tru64)
-   # The Tru64 compiler uses -MD to generate dependencies as a side
-   # effect.  `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'.
-   # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
-   # dependencies in `foo.d' instead, so we check for that too.
-   # Subdirectories are respected.
-   dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
-   test "x$dir" = "x$object" && dir=
-   base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
-
-   if test "$libtool" = yes; then
-      # With Tru64 cc, shared objects can also be used to make a
-      # static library.  This mechanism is used in libtool 1.4 series to
-      # handle both shared and static libraries in a single compilation.
-      # With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d.
-      #
-      # With libtool 1.5 this exception was removed, and libtool now
-      # generates 2 separate objects for the 2 libraries.  These two
-      # compilations output dependencies in $dir.libs/$base.o.d and
-      # in $dir$base.o.d.  We have to check for both files, because
-      # one of the two compilations can be disabled.  We should prefer
-      # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
-      # automatically cleaned when .libs/ is deleted, while ignoring
-      # the former would cause a distcleancheck panic.
-      tmpdepfile1=$dir.libs/$base.lo.d   # libtool 1.4
-      tmpdepfile2=$dir$base.o.d          # libtool 1.5
-      tmpdepfile3=$dir.libs/$base.o.d    # libtool 1.5
-      tmpdepfile4=$dir.libs/$base.d      # Compaq CCC V6.2-504
-      "$@" -Wc,-MD
-   else
-      tmpdepfile1=$dir$base.o.d
-      tmpdepfile2=$dir$base.d
-      tmpdepfile3=$dir$base.d
-      tmpdepfile4=$dir$base.d
-      "$@" -MD
-   fi
-
-   stat=$?
-   if test $stat -eq 0; then :
-   else
-      rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
-      exit $stat
-   fi
-
-   for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
-   do
-     test -f "$tmpdepfile" && break
-   done
-   if test -f "$tmpdepfile"; then
-      sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
-      # That's a tab and a space in the [].
-      sed -e 's,^.*\.[a-z]*:[	 ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
-   else
-      echo "#dummy" > "$depfile"
-   fi
-   rm -f "$tmpdepfile"
-   ;;
-
-#nosideeffect)
-  # This comment above is used by automake to tell side-effect
-  # dependency tracking mechanisms from slower ones.
-
-dashmstdout)
-  # Important note: in order to support this mode, a compiler *must*
-  # always write the preprocessed file to stdout, regardless of -o.
-  "$@" || exit $?
-
-  # Remove the call to Libtool.
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-
-  # Remove `-o $object'.
-  IFS=" "
-  for arg
-  do
-    case $arg in
-    -o)
-      shift
-      ;;
-    $object)
-      shift
-      ;;
-    *)
-      set fnord "$@" "$arg"
-      shift # fnord
-      shift # $arg
-      ;;
-    esac
-  done
-
-  test -z "$dashmflag" && dashmflag=-M
-  # Require at least two characters before searching for `:'
-  # in the target name.  This is to cope with DOS-style filenames:
-  # a dependency such as `c:/foo/bar' could be seen as target `c' otherwise.
-  "$@" $dashmflag |
-    sed 's:^[  ]*[^: ][^:][^:]*\:[    ]*:'"$object"'\: :' > "$tmpdepfile"
-  rm -f "$depfile"
-  cat < "$tmpdepfile" > "$depfile"
-  tr ' ' '
-' < "$tmpdepfile" | \
-## Some versions of the HPUX 10.20 sed can't process this invocation
-## correctly.  Breaking it into two sed invocations is a workaround.
-    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-dashXmstdout)
-  # This case only exists to satisfy depend.m4.  It is never actually
-  # run, as this mode is specially recognized in the preamble.
-  exit 1
-  ;;
-
-makedepend)
-  "$@" || exit $?
-  # Remove any Libtool call
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-  # X makedepend
-  shift
-  cleared=no eat=no
-  for arg
-  do
-    case $cleared in
-    no)
-      set ""; shift
-      cleared=yes ;;
-    esac
-    if test $eat = yes; then
-      eat=no
-      continue
-    fi
-    case "$arg" in
-    -D*|-I*)
-      set fnord "$@" "$arg"; shift ;;
-    # Strip any option that makedepend may not understand.  Remove
-    # the object too, otherwise makedepend will parse it as a source file.
-    -arch)
-      eat=yes ;;
-    -*|$object)
-      ;;
-    *)
-      set fnord "$@" "$arg"; shift ;;
-    esac
-  done
-  obj_suffix=`echo "$object" | sed 's/^.*\././'`
-  touch "$tmpdepfile"
-  ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
-  rm -f "$depfile"
-  cat < "$tmpdepfile" > "$depfile"
-  sed '1,2d' "$tmpdepfile" | tr ' ' '
-' | \
-## Some versions of the HPUX 10.20 sed can't process this invocation
-## correctly.  Breaking it into two sed invocations is a workaround.
-    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile" "$tmpdepfile".bak
-  ;;
-
-cpp)
-  # Important note: in order to support this mode, a compiler *must*
-  # always write the preprocessed file to stdout.
-  "$@" || exit $?
-
-  # Remove the call to Libtool.
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-
-  # Remove `-o $object'.
-  IFS=" "
-  for arg
-  do
-    case $arg in
-    -o)
-      shift
-      ;;
-    $object)
-      shift
-      ;;
-    *)
-      set fnord "$@" "$arg"
-      shift # fnord
-      shift # $arg
-      ;;
-    esac
-  done
-
-  "$@" -E |
-    sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
-       -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
-    sed '$ s: \\$::' > "$tmpdepfile"
-  rm -f "$depfile"
-  echo "$object : \\" > "$depfile"
-  cat < "$tmpdepfile" >> "$depfile"
-  sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-msvisualcpp)
-  # Important note: in order to support this mode, a compiler *must*
-  # always write the preprocessed file to stdout.
-  "$@" || exit $?
-
-  # Remove the call to Libtool.
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-
-  IFS=" "
-  for arg
-  do
-    case "$arg" in
-    -o)
-      shift
-      ;;
-    $object)
-      shift
-      ;;
-    "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
-	set fnord "$@"
-	shift
-	shift
-	;;
-    *)
-	set fnord "$@" "$arg"
-	shift
-	shift
-	;;
-    esac
-  done
-  "$@" -E 2>/dev/null |
-  sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
-  rm -f "$depfile"
-  echo "$object : \\" > "$depfile"
-  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::	\1 \\:p' >> "$depfile"
-  echo "	" >> "$depfile"
-  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-msvcmsys)
-  # This case exists only to let depend.m4 do its work.  It works by
-  # looking at the text of this script.  This case will never be run,
-  # since it is checked for above.
-  exit 1
-  ;;
-
-none)
-  exec "$@"
-  ;;
-
-*)
-  echo "Unknown depmode $depmode" 1>&2
-  exit 1
-  ;;
-esac
-
-exit 0
-
-# Local Variables:
-# mode: shell-script
-# sh-indentation: 2
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC"
-# time-stamp-end: "; # UTC"
-# End:

+ 11 - 0
dns++.pc.in

@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: dns++
+Description: BIND 10 DNS library
+Version: @PACKAGE_VERSION@
+Requires: botan-1.8
+Cflags: -I${includedir}/@PACKAGE_NAME@
+Libs: -L${libdir} -ldns++ -lcryptolink -lutil -lexceptions -lm

+ 1 - 0
doc/.gitignore

@@ -1 +1,2 @@
 /version.ent
+html

+ 15 - 10
doc/Doxyfile

@@ -25,13 +25,17 @@ DOXYFILE_ENCODING      = UTF-8
 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded
 # by quotes) that should identify the project.
 
-PROJECT_NAME           = BIND
+PROJECT_NAME           = BIND10
 
 # The PROJECT_NUMBER tag can be used to enter a project or revision number.
 # This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
-PROJECT_NUMBER         = 10.0.0
+# Currently this variable is overwritten (see devel target in Makefile.am)
+# If the number of parameters to overwrite increases, we should generate
+# Doxyfile (rename it to Doxyfile.in and generate during configure phase)
+
+PROJECT_NUMBER         =
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
 # base path where the generated documentation will be put.
@@ -47,7 +51,7 @@ OUTPUT_DIRECTORY       = html
 # source files, where putting all generated files in the same directory would
 # otherwise cause performance problems for the file system.
 
-CREATE_SUBDIRS         = NO
+CREATE_SUBDIRS         = YES
 
 # The OUTPUT_LANGUAGE tag is used to specify the language in which all
 # documentation generated by doxygen is written. Doxygen will use this
@@ -281,7 +285,7 @@ TYPEDEF_HIDES_STRUCT   = NO
 # causing a significant performance penality.
 # If the system has enough physical memory increasing the cache will improve the
 # performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will rougly double the
+# a logarithmic scale so increasing the size by one will roughly double the
 # memory usage. The cache size is given by this formula:
 # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
 # corresponding to a cache size of 2^16 = 65536 symbols
@@ -574,7 +578,8 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
-    ../src/lib/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 ../tests/tools/perfdhcp devel
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -591,7 +596,7 @@ INPUT_ENCODING         = UTF-8
 # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
 # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
 
-FILE_PATTERNS          =
+FILE_PATTERNS          = *.c *.cc *.h *.hpp *.dox
 
 # The RECURSIVE tag can be used to turn specify whether or not subdirectories
 # should be searched for input files as well. Possible values are YES and NO.
@@ -651,7 +656,7 @@ EXAMPLE_RECURSIVE      = NO
 # directories that contain image that are included in the documentation (see
 # the \image command).
 
-IMAGE_PATH             =
+IMAGE_PATH             = ../doc/images
 
 # The INPUT_FILTER tag can be used to specify a program that doxygen should
 # invoke to filter for each input file. Doxygen will invoke the filter program
@@ -773,7 +778,7 @@ GENERATE_HTML          = YES
 # If a relative path is entered the value of OUTPUT_DIRECTORY will be
 # put in front of it. If left blank `html' will be used as the default path.
 
-HTML_OUTPUT            = cpp
+HTML_OUTPUT            = ../html
 
 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for
 # each generated HTML page (for example: .htm,.php,.asp). If it is left blank
@@ -954,7 +959,7 @@ ENUM_VALUES_PER_LINE   = 4
 # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
 # Windows users are probably better off using the HTML help feature.
 
-GENERATE_TREEVIEW      = NO
+GENERATE_TREEVIEW      = YES
 
 # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
 # and Class Hierarchy pages using a tree view instead of an ordered list.
@@ -965,7 +970,7 @@ USE_INLINE_TREES       = NO
 # used to set the initial width (in pixels) of the frame in which the tree
 # is shown.
 
-TREEVIEW_WIDTH         = 250
+TREEVIEW_WIDTH         = 180
 
 # Use this tag to change the font size of Latex formulas included
 # as images in the HTML documentation. The default is 10. Note that

+ 13 - 0
doc/Makefile.am

@@ -1,3 +1,16 @@
 SUBDIRS = guide
 
 EXTRA_DIST = version.ent.in
+
+devel:
+	mkdir -p html
+	(cat Doxyfile; echo PROJECT_NUMBER=$(PACKAGE_VERSION)) | doxygen - > html/doxygen.log 2> html/doxygen-error.log
+	echo `grep -i ": warning:" html/doxygen-error.log | wc -l` warnings/errors detected.
+
+clean:
+	rm -rf html
+
+# That's a bit of a hack, but we are making sure that devel target
+# is always valid. The alternative is to make devel depend on all
+# *.cc *.h files in the whole tree.
+.PHONY: devel

+ 14 - 0
doc/devel/01-dns.dox

@@ -0,0 +1,14 @@
+/**
+ *
+ * @page dns BIND10 DNS
+ *
+ * @section dns-auth b10-auth
+ *
+ * @todo: Describe b10-auth here.
+ *
+ * @section b10-cfgmgr b10-cfgmgr Overview
+ *
+ * @todo: Descibe b10-cfgmgr here.
+ *
+ *
+ */

+ 117 - 0
doc/devel/02-dhcp.dox

@@ -0,0 +1,117 @@
+/**
+ * @page dhcpv4 DHCPv4 Server Component
+ *
+ * BIND10 offers DHCPv4 server implementation. It is implemented as
+ * b10-dhcp4 component.  Its primary code is located in
+ * isc::dhcp::Dhcpv4Srv class. It uses \ref libdhcp extensively,
+ * especially isc::dhcp::Pkt4, isc::dhcp::Option and
+ * isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+ * functionality, i.e. it is able to receive and process incoming
+ * requests and trasmit responses. However, it does not have database
+ * management, so it returns only one, hardcoded lease to whoever asks
+ * for it.
+ *
+ * DHCPv4 server component does not support direct traffic (relayed
+ * only), as support for transmission to hosts without IPv4 address
+ * assigned is not implemented in IfaceMgr yet.
+ *
+ * DHCPv4 server component does not listen to BIND10 message queue.
+ *
+ * DHCPv4 server component does not use BIND10 logging yet.
+ *
+ * DHCPv4 server component is not integrated with boss yet.
+ *
+ * @page dhcpv6 DHCPv6 Server Component
+ *
+ * BIND10 offers DHCPv6 server implementation. It is implemented as
+ * b10-dhcp6 component. Its primary code is located in
+ * isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
+ * especially lib::dhcp::Pkt6, isc::dhcp::Option and
+ * isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+ * functionality, i.e. it is able to receive and process incoming
+ * requests and trasmit responses. However, it does not have database
+ * management, so it returns only one, hardcoded lease to whoever asks
+ * for it.
+ *
+ * DHCPv6 server component does not support relayed traffic yet, as
+ * support for relay decapsulation is not implemented yet.
+ *
+ * DHCPv6 server component does not listen to BIND10 message queue.
+ *
+ * DHCPv6 server component does not use BIND10 logging yet.
+ *
+ * DHCPv6 server component is not integrated with boss yet.
+ *
+ * @page libdhcp libdhcp++ library
+ *
+ * @section libdhcpIntro Libdhcp++ Introduction
+ *
+ * libdhcp++ is an all-purpose DHCP-manipulation library, written in
+ * C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
+ * options parsing and ssembly, interface detection (currently on
+ * Linux systems only) and socket operations. Following classes are
+ * implemented:
+ *
+ * - isc::dhcp::Pkt4 - represents DHCPv4 packet.
+ * - isc::dhcp::Pkt6 - represents DHCPv6 packet.
+ *
+ * There are two pointer types defined: Pkt4Ptr and Pkt6Ptr. They are
+ * smart pointer and are using boost::shared_ptr. There are not const
+ * versions defined, as we assume that hooks can modify any aspect of
+ * the packet at almost any stage of processing.
+ *
+ * Both packets use collection of Option objects to represent DHCPv4
+ * and DHCPv6 options. The base class -- Option -- can be used to
+ * represent generic option that contains collection of
+ * bytes. Depending on if the option is instantiated as v4 or v6
+ * option, it will adjust its header (DHCPv4 options use 1 octet for
+ * type and 1 octet for length, while DHCPv6 options use 2 bytes for
+ * each).
+ *
+ * There are many specialized classes that are intended to handle options with
+ * specific content:
+ * - isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses;
+ * - isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses;
+ * - isc::dhcp::Option6IAAddr -- DHCPv6 option, represents IAADDR_OPTION (an option that
+ *                     contains IPv6 address with extra parameters);
+ * - isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions.
+ *
+ * All options can store sub-options (i.e. options that are stored within option
+ * rather than in a message directly). This functionality is commonly used in
+ * DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
+ * isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
+ * for that purpose.
+ *
+ * @section lidhcpIfaceMgr Interface Manager
+ *
+ * Interface Manager (or IfaceMgr) is an abstraction layer about low-level
+ * network operations. In particlar, it provides information about existing
+ * network interfaces See isc::dhcp::IfaceMgr::Iface class and
+ * isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface().
+ *
+ * Currently there is interface detection is implemented in Linux only. There
+ * are plans to implement such support for other OSes, but they remain low
+ * priority for now.
+ *
+ * Generic parts of the code are isc::dhcp::IfaceMgr class in
+ * src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
+ * files, e.g. iface_mgr_linux.cc. Such separation should be maintained when
+ * additional code will be developed.
+ *
+ * For systems that interface detection is not supported on, there is a stub
+ * mechanism implemented. It assumes that interface name is read from a text
+ * file. This is a temporary solution and will be removed as soon as proper
+ * interface detection is implemented. It is not going to be developed further.
+ * To use this feature, store interfaces.txt file. It uses a simple syntax.
+ * Each line represents an interface name, followed by IPv4 or IPv6 address
+ * that follows it. This is usually link-local IPv6 address that the server
+ * should bind to. In theory this mechanism also supports IPv4, but it was
+ * never tested. The code currently supports only a single interface defined
+ * that way.
+ *
+ * Another useful methods are dedicated to transmission
+ * (isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
+ * (isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()).
+ * Note that receive4() and receive6() methods may return NULL, e.g.
+ * when timeout is reached or if dhcp daemon receives a signal.
+ */

+ 36 - 0
doc/devel/mainpage.dox

@@ -0,0 +1,36 @@
+/**
+ *
+ * @mainpage BIND10 Developer's Guide
+ *
+ * Welcome to BIND10 Developer's Guide. This documentation is addressed
+ * at existing and prospecting developers and programmers, who would like
+ * to gain insight into internal workings of BIND 10. It could also be useful
+ * for existing and prospective contributors.
+ *
+ * If you are a user or system administrator, rather than software engineer,
+ * you should read <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
+ * Guide (Administrator Reference for BIND10)</a> instead.
+ *
+ * Regardless of your field of expertise, you are encouraged to visit
+ * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
+ *
+ * @section DNS
+ * - @subpage DataScrubbing
+ *
+ * @section DHCP
+ * - @subpage dhcpv4
+ * - @subpage dhcpv6
+ * - @subpage libdhcp
+ *
+ * @section misc Miscellaneous topics
+ * - @subpage LoggingApi
+ *   - @subpage LoggingApiOverview
+ *   - @subpage LoggingApiLoggerNames
+ *   - @subpage LoggingApiLoggingMessages
+ * - @subpage SocketSessionUtility
+ * - <a href="./doxygen-error.log">Documentation warnings and errors</a>
+ *
+ * @todo: Move this logo to the right (and possibly up). Not sure what
+ * is the best way to do it in Doxygen, without using CSS hacks.
+ * @image html isc-logo.png
+ */

+ 4 - 3
doc/guide/Makefile.am

@@ -1,6 +1,7 @@
-EXTRA_DIST = bind10-guide.css
-EXTRA_DIST += bind10-guide.xml bind10-guide.html bind10-guide.txt
-EXTRA_DIST += bind10-messages.xml bind10-messages.html
+dist_doc_DATA = bind10-guide.txt
+dist_html_DATA = bind10-guide.css bind10-guide.html bind10-messages.html
+
+EXTRA_DIST = bind10-guide.xml bind10-messages.xml
 
 # This is not a "man" manual, but reuse this for now for docbook.
 if ENABLE_MAN

File diff suppressed because it is too large
+ 215 - 87
doc/guide/bind10-guide.html


File diff suppressed because it is too large
+ 371 - 246
doc/guide/bind10-guide.txt


+ 104 - 37
doc/guide/bind10-guide.xml

@@ -131,7 +131,9 @@
         and <command>b10-zonemgr</command> components require the
         libpython3 library and the Python _sqlite3.so module
         (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>
 <!-- TODO: this will change ... -->
 
@@ -770,13 +772,8 @@ as a dependency earlier -->
       In its default configuration, the <command>bind10</command>
       master process will also start up
       <command>b10-cmdctl</command> for administration tools to
-      communicate with the system,
-      <command>b10-auth</command> for authoritative DNS service,
-      <command>b10-stats</command> for statistics collection,
-      <command>b10-stats-httpd</command> for statistics reporting,
-      <command>b10-xfrin</command> for inbound DNS zone transfers,
-      <command>b10-xfrout</command> for outbound DNS zone transfers,
-      and <command>b10-zonemgr</command> for secondary service.
+      communicate with the system, and
+      <command>b10-stats</command> for statistics collection.
     </para>
 
     <section id="start">
@@ -810,12 +807,7 @@ as a dependency earlier -->
         The configuration is in the Boss/components section. Each element
         represents one component, which is an abstraction of a process
         (currently there's also one component which doesn't represent
-        a process). If you didn't want to transfer out at all (your server
-        is a slave only), you would just remove the corresponding component
-        from the set, like this and the process would be stopped immediately
-        (and not started on the next startup):
-      <screen>&gt; <userinput>config remove Boss/components b10-xfrout</userinput>
-&gt; <userinput>config commit</userinput></screen>
+        a process).
       </para>
 
       <para>
@@ -1331,9 +1323,10 @@ This may be a temporary setting until then.
       <varname>class</varname> to optionally select the class
       (it defaults to <quote>IN</quote>);
       and
-      <varname>zones</varname> to define the
-      <varname>file</varname> path name and the
-      <varname>origin</varname> (default domain).
+      <varname>zones</varname> to define
+      the <varname>file</varname> path name,
+      the <varname>filetype</varname> (e.g., <varname>sqlite3</varname>),
+      and the <varname>origin</varname> (default domain).
 
       By default, this is empty.
 
@@ -1343,7 +1336,8 @@ This may be a temporary setting until then.
         Only the IN class is supported at this time.
         By default, the memory data source is disabled.
         Also, currently the zone file must be canonical such as
-        generated by <command>named-compilezone -D</command>.
+        generated by <command>named-compilezone -D</command>, or
+        must be an SQLite3 database.
       </simpara></note>
 
               </simpara>
@@ -1360,6 +1354,24 @@ This may be a temporary setting until then.
       and <varname>port</varname> number.
       By default, <command>b10-auth</command> listens on port 53
       on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses.
+      <note>
+        <simpara>
+          The default configuration is currently not appropriate for a multi-homed host.
+          In case you have multiple public IP addresses, it is possible the
+          query UDP packet comes through one interface and the answer goes out
+          through another. The answer will probably be dropped by the client, as it
+          has a different source address than the one it sent the query to. The
+          client would fallback on TCP after several attempts, which works
+          well in this situation, but is clearly not ideal.
+        </simpara>
+        <simpara>
+          There are plans to solve the problem such that the server handles
+          it by itself. But until it is actually implemented, it is recommended to
+          alter the configuration &mdash; remove the wildcard addresses and list all
+          addresses explicitly. Then the server will answer on the same
+          interface the request came on, preserving the correct address.
+        </simpara>
+      </note>
               </simpara>
             </listitem>
           </varlistentry>
@@ -1488,6 +1500,39 @@ This may be a temporary setting until then.
 	  after it is loaded.
 	</para>
 
+      </section>
+
+      <section id="in-memory-datasource-with-sqlite3-backend">
+	<title>In-memory Data Source With SQLite3 Backend</title>
+
+	<para>
+<!--	  How to configure it. -->
+	  The following commands to <command>bindctl</command>
+	  provide an example of configuring an in-memory data
+	  source containing the <quote>example.org</quote> zone
+	  with a SQLite3 backend file named <quote>example.org.sqlite3</quote>:
+
+<!--
+	  <screen>&gt; <userinput> config set Auth/datasources/ [{"type": "memory", "zones": [{"origin": "example.org", "file": "example.org.sqlite3", "filetype": "sqlite3"}]}]</userinput></screen>
+-->
+
+          <screen>&gt; <userinput>config add Auth/datasources</userinput>
+&gt; <userinput>config set Auth/datasources[1]/type "<option>memory</option>"</userinput>
+&gt; <userinput>config add Auth/datasources[1]/zones</userinput>
+&gt; <userinput>config set Auth/datasources[1]/zones[0]/origin "<option>example.org</option>"</userinput>
+&gt; <userinput>config set Auth/datasources[1]/zones[0]/file "<option>example.org.sqlite3</option>"</userinput>
+&gt; <userinput>config set Auth/datasources[1]/zones[0]/filetype "<option>sqlite3</option>"</userinput>
+&gt; <userinput>config commit</userinput></screen>
+
+	  The authoritative server will begin serving it immediately
+	  after it is loaded.
+	</para>
+
+      </section>
+
+      <section id="in-memory-datasource-loading">
+	<title>Reloading an In-memory Data Source</title>
+
 	<para>
 	  Use the <command>Auth loadzone</command> command in
 	  <command>bindctl</command> to reload a changed master
@@ -1506,6 +1551,10 @@ This may be a temporary setting until then.
 	</para>
 -->
 
+      </section>
+      <section id="in-memory-datasource-disabling">
+	<title>Disabling In-memory Data Sources</title>
+
         <para>
 	By default, the memory data source is disabled; it must be
 	configured explicitly.  To disable all the in-memory zones,
@@ -1638,12 +1687,6 @@ TODO
     </para>
 <!-- TODO: http://bind10.isc.org/ticket/1279 -->
 
-    <note><simpara>
-     In the current development release of BIND 10, incoming zone
-     transfers are only available for SQLite3-based data sources,
-     that is, they don't work for an in-memory data source.
-    </simpara></note>
-
     <section>
       <title>Configuration for Incoming Zone Transfers</title>
       <para>
@@ -1763,6 +1806,26 @@ what if a NOTIFY is sent?
       </para>
     </section>
 
+    <section>
+      <title>Incoming Transfers with In-memory Datasource</title>
+
+      <para>
+        In the case of an incoming zone transfer, the received zone is
+        first stored in the corresponding BIND 10 datasource. In
+        case the secondary zone is served by an in-memory datasource
+        with an SQLite3 backend, <command>b10-auth</command> is
+        automatically sent a <varname>loadzone</varname> command to
+        reload the corresponding zone into memory from the backend.
+      </para>
+
+      <para>
+	The administrator doesn't have to do anything for
+	<command>b10-auth</command> to serve the new version of the
+	zone, except for the configuration such as the one described in
+	<xref linkend="in-memory-datasource-with-sqlite3-backend" />.
+      </para>
+    </section>
+
 <!-- TODO: can that retransfer be used to identify a new zone? -->
 <!-- TODO: what if doesn't exist at that master IP? -->
 
@@ -1861,15 +1924,10 @@ what is XfroutClient xfr_client??
     <para>
       The main <command>bind10</command> process can be configured
       to select to run either the authoritative or resolver or both.
-      By default, it starts the authoritative service.
-<!-- TODO: later both -->
-
-      You may change this using <command>bindctl</command>, for example:
+      By default, it doesn't start either one. You may change this using
+      <command>bindctl</command>, for example:
 
       <screen>
-&gt; <userinput>config remove Boss/components b10-xfrout</userinput>
-&gt; <userinput>config remove Boss/components b10-xfrin</userinput>
-&gt; <userinput>config remove Boss/components b10-auth</userinput>
 &gt; <userinput>config add Boss/components b10-resolver</userinput>
 &gt; <userinput>config set Boss/components/b10-resolver/special resolver</userinput>
 &gt; <userinput>config set Boss/components/b10-resolver/kind needed</userinput>
@@ -2823,34 +2881,43 @@ TODO; there's a ticket to determine these levels, see #1074
           <varlistentry>
             <term><option>destination</option> is <quote>console</quote></term>
             <listitem>
-              <simpara>
+              <para>
                  The value of output must be one of <quote>stdout</quote>
                  (messages printed to standard output) or
                  <quote>stderr</quote> (messages printed to standard
                  error).
-              </simpara>
+              </para>
+              <para>
+                Note: if output is set to <quote>stderr</quote> and a lot of
+                messages are produced in a short time (e.g. if the logging
+                level is set to DEBUG), you may occasionally see some messages
+                jumbled up together.  This is due to a combination of the way
+                that messages are written to the screen and the unbuffered
+                nature of the standard error stream.  If this occurs, it is
+                recommended that output be set to <quote>stdout</quote>.
+              </para>
             </listitem>
           </varlistentry>
 
           <varlistentry>
             <term><option>destination</option> is <quote>file</quote></term>
             <listitem>
-              <simpara>
+              <para>
                 The value of output is interpreted as a file name;
                 log messages will be appended to this file.
-              </simpara>
+              </para>
             </listitem>
           </varlistentry>
 
           <varlistentry>
             <term><option>destination</option> is <quote>syslog</quote></term>
             <listitem>
-              <simpara>
+              <para>
                 The value of output is interpreted as the
                 <command>syslog</command> facility (e.g.
                 <emphasis>local0</emphasis>) that should be used
                 for log messages.
-              </simpara>
+              </para>
             </listitem>
           </varlistentry>
 

File diff suppressed because it is too large
+ 207 - 19
doc/guide/bind10-messages.html


+ 142 - 8
doc/guide/bind10-messages.xml

@@ -1399,7 +1399,7 @@ Debug message. The RRset is updating its data with this given RRset.
 </varlistentry>
 
 <varlistentry id="CC_ASYNC_READ_FAILED">
-<term>CC_ASYNC_READ_FAILED asynchronous read failed</term>
+<term>CC_ASYNC_READ_FAILED asynchronous read failed (error code = %1)</term>
 <listitem><para>
 This marks a low level error, we tried to read data from the message queue
 daemon asynchronously, but the ASIO library returned an error.
@@ -1607,6 +1607,14 @@ system. The most likely cause is that msgq is not running.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="CFGMGR_CONFIG_FILE">
+<term>CFGMGR_CONFIG_FILE Configuration manager starting with configuration file: %1</term>
+<listitem><para>
+The configuration manager is starting, reading and saving the configuration
+settings to the shown file.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="CFGMGR_DATA_READ_ERROR">
 <term>CFGMGR_DATA_READ_ERROR error reading configuration database from disk: %1</term>
 <listitem><para>
@@ -2057,6 +2065,55 @@ in the answer as a result.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="DATASRC_DATABASE_FINDNSEC3">
+<term>DATASRC_DATABASE_FINDNSEC3 Looking for NSEC3 for %1 in %2 mode</term>
+<listitem><para>
+Debug information. A search in an database data source for NSEC3 that
+matches or covers the given name is being started.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_FINDNSEC3_COVER">
+<term>DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1: %2</term>
+<listitem><para>
+Debug information. An NSEC3 that covers the given name is found and
+being returned.  The found NSEC3 RRset is also displayed.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_FINDNSEC3_MATCH">
+<term>DATASRC_DATABASE_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3</term>
+<listitem><para>
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned.  When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_DATABSE_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_FINDNSEC3_TRYHASH">
+<term>DATASRC_DATABASE_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)</term>
+<listitem><para>
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space.  When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried, as "." is 1 label long).
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV">
+<term>DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV looking for previous NSEC3 for %1 at label count %2 (hash %3)</term>
+<listitem><para>
+Debug information. An exact match on hash (see
+DATASRC_DATABASE_FINDNSEC3_TRYHASH) was unsuccessful. We get the previous hash
+to that one instead.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="DATASRC_DATABASE_FIND_RECORDS">
 <term>DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4</term>
 <listitem><para>
@@ -2189,10 +2246,12 @@ The name and RRtype of the RRset is indicated in the message.
 <varlistentry id="DATASRC_DATABASE_ITERATE_TTL_MISMATCH">
 <term>DATASRC_DATABASE_ITERATE_TTL_MISMATCH TTL values differ for RRs of %1/%2/%3, setting to %4</term>
 <listitem><para>
-While iterating through the zone, the time to live for RRs of the given RRset
-were found to be different. This isn't allowed on the wire and is considered
-an error, so we set it to the lowest value we found (but we don't modify the
-database). The data in database should be checked and fixed.
+While iterating through the zone, the time to live for RRs of the
+given RRset were found to be different. Since an RRset cannot have
+multiple TTLs, we set it to the lowest value we found (but we don't
+modify the database). This is what the client would do when such RRs
+were given in a DNS response according to RFC2181. The data in
+database should be checked and fixed.
 </para></listitem>
 </varlistentry>
 
@@ -3096,6 +3155,21 @@ Debug information. The SQLite data source is closing the database file.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="DATASRC_SQLITE_COMPATIBLE_VERSION">
+<term>DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible</term>
+<listitem><para>
+The version of the SQLite3 database schema used to hold the zone data
+is not the latest one - the current version of BIND 10 was written
+with a later schema version in mind.  However, the database is
+compatible with the current version of BIND 10, and BIND 10 will run
+without any problems.
+</para><para>
+Consult the release notes for your version of BIND 10.  Depending on
+the changes made to the database schema, it is possible that improved
+performance could result if the database were upgraded.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="DATASRC_SQLITE_CONNCLOSE">
 <term>DATASRC_SQLITE_CONNCLOSE Closing sqlite database</term>
 <listitem><para>
@@ -3235,6 +3309,18 @@ But it doesn't contain that zone.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="DATASRC_SQLITE_INCOMPATIBLE_VERSION">
+<term>DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected</term>
+<listitem><para>
+The version of the SQLite3 database schema used to hold the zone data
+is incompatible with the version expected by BIND 10.  As a result,
+BIND 10 is unable to run using the database file as the data source.
+</para><para>
+The database should be updated using the means described in the BIND
+10 documentation.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="DATASRC_SQLITE_NEWCONN">
 <term>DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized</term>
 <listitem><para>
@@ -5388,6 +5474,29 @@ Please check your installation.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="XFRIN_AUTH_CONFIG_NAME_PARSER_ERROR">
+<term>XFRIN_AUTH_CONFIG_NAME_PARSER_ERROR Invalid name when parsing Auth configuration: %1</term>
+<listitem><para>
+There was an invalid name when parsing Auth configuration.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="XFRIN_AUTH_CONFIG_RRCLASS_ERROR">
+<term>XFRIN_AUTH_CONFIG_RRCLASS_ERROR Invalid RRClass when parsing Auth configuration: %1</term>
+<listitem><para>
+There was an invalid RR class when parsing Auth configuration.
+</para></listitem>
+</varlistentry>
+
+<varlistentry id="XFRIN_AUTH_LOADZONE">
+<term>XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3</term>
+<listitem><para>
+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
+"loadzone" command for the zone to b10-auth.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="XFRIN_AXFR_INCONSISTENT_SOA">
 <term>XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received</term>
 <listitem><para>
@@ -5542,6 +5651,14 @@ was killed.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="XFRIN_MSGQ_SEND_ERROR_AUTH">
+<term>XFRIN_MSGQ_SEND_ERROR_AUTH error while contacting %1</term>
+<listitem><para>
+There was a problem sending a message to b10-auth. This most likely
+means that the msgq daemon has quit or was killed.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER">
 <term>XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER error while contacting %1</term>
 <listitem><para>
@@ -5570,10 +5687,11 @@ zone name in the configuration.
 </para></listitem>
 </varlistentry>
 
-<varlistentry id="XFRIN_STARTING">
-<term>XFRIN_STARTING starting resolver with command line '%1'</term>
+<varlistentry id="XFRIN_STARTED">
+<term>XFRIN_STARTED xfrin started</term>
 <listitem><para>
-An informational message, this is output when the resolver starts up.
+This informational message is output by xfrin when all initialization
+has been completed and it is entering its main loop.
 </para></listitem>
 </varlistentry>
 
@@ -6001,6 +6119,14 @@ log message.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="XFROUT_STARTED">
+<term>XFROUT_STARTED xfrout started</term>
+<listitem><para>
+This informational message is output by xfrout when all initialization
+has been completed and it is entering its main loop.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="XFROUT_STOPPED_BY_KEYBOARD">
 <term>XFROUT_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down</term>
 <listitem><para>
@@ -6257,6 +6383,14 @@ A debug message, output when the zone manager has shut down completely.
 </para></listitem>
 </varlistentry>
 
+<varlistentry id="ZONEMGR_STARTED">
+<term>ZONEMGR_STARTED zonemgr started</term>
+<listitem><para>
+This informational message is output by zonemgr when all initialization
+has been completed and it is entering its main loop.
+</para></listitem>
+</varlistentry>
+
 <varlistentry id="ZONEMGR_STARTING">
 <term>ZONEMGR_STARTING zone manager starting</term>
 <listitem><para>

BIN
doc/images/isc-logo.png


+ 23 - 0
ext/LICENSE_1_0.txt

@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 0 - 520
install-sh

@@ -1,520 +0,0 @@
-#!/bin/sh
-# install - install a program, script, or datafile
-
-scriptversion=2009-04-28.21; # UTC
-
-# This originates from X11R5 (mit/util/scripts/install.sh), which was
-# later released in X11R6 (xc/config/util/install.sh) with the
-# following copyright and license.
-#
-# Copyright (C) 1994 X Consortium
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-# sell copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
-# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
-# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
-# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the name of the X Consortium shall not
-# be used in advertising or otherwise to promote the sale, use or other deal-
-# ings in this Software without prior written authorization from the X Consor-
-# tium.
-#
-#
-# FSF changes to this file are in the public domain.
-#
-# Calling this script install-sh is preferred over install.sh, to prevent
-# `make' implicit rules from creating a file called install from it
-# when there is no Makefile.
-#
-# This script is compatible with the BSD install script, but was written
-# from scratch.
-
-nl='
-'
-IFS=" ""	$nl"
-
-# set DOITPROG to echo to test this script
-
-# Don't use :- since 4.3BSD and earlier shells don't like it.
-doit=${DOITPROG-}
-if test -z "$doit"; then
-  doit_exec=exec
-else
-  doit_exec=$doit
-fi
-
-# Put in absolute file names if you don't have them in your path;
-# or use environment vars.
-
-chgrpprog=${CHGRPPROG-chgrp}
-chmodprog=${CHMODPROG-chmod}
-chownprog=${CHOWNPROG-chown}
-cmpprog=${CMPPROG-cmp}
-cpprog=${CPPROG-cp}
-mkdirprog=${MKDIRPROG-mkdir}
-mvprog=${MVPROG-mv}
-rmprog=${RMPROG-rm}
-stripprog=${STRIPPROG-strip}
-
-posix_glob='?'
-initialize_posix_glob='
-  test "$posix_glob" != "?" || {
-    if (set -f) 2>/dev/null; then
-      posix_glob=
-    else
-      posix_glob=:
-    fi
-  }
-'
-
-posix_mkdir=
-
-# Desired mode of installed file.
-mode=0755
-
-chgrpcmd=
-chmodcmd=$chmodprog
-chowncmd=
-mvcmd=$mvprog
-rmcmd="$rmprog -f"
-stripcmd=
-
-src=
-dst=
-dir_arg=
-dst_arg=
-
-copy_on_change=false
-no_target_directory=
-
-usage="\
-Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
-   or: $0 [OPTION]... SRCFILES... DIRECTORY
-   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
-   or: $0 [OPTION]... -d DIRECTORIES...
-
-In the 1st form, copy SRCFILE to DSTFILE.
-In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
-In the 4th, create DIRECTORIES.
-
-Options:
-     --help     display this help and exit.
-     --version  display version info and exit.
-
-  -c            (ignored)
-  -C            install only if different (preserve the last data modification time)
-  -d            create directories instead of installing files.
-  -g GROUP      $chgrpprog installed files to GROUP.
-  -m MODE       $chmodprog installed files to MODE.
-  -o USER       $chownprog installed files to USER.
-  -s            $stripprog installed files.
-  -t DIRECTORY  install into DIRECTORY.
-  -T            report an error if DSTFILE is a directory.
-
-Environment variables override the default commands:
-  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
-  RMPROG STRIPPROG
-"
-
-while test $# -ne 0; do
-  case $1 in
-    -c) ;;
-
-    -C) copy_on_change=true;;
-
-    -d) dir_arg=true;;
-
-    -g) chgrpcmd="$chgrpprog $2"
-	shift;;
-
-    --help) echo "$usage"; exit $?;;
-
-    -m) mode=$2
-	case $mode in
-	  *' '* | *'	'* | *'
-'*	  | *'*'* | *'?'* | *'['*)
-	    echo "$0: invalid mode: $mode" >&2
-	    exit 1;;
-	esac
-	shift;;
-
-    -o) chowncmd="$chownprog $2"
-	shift;;
-
-    -s) stripcmd=$stripprog;;
-
-    -t) dst_arg=$2
-	shift;;
-
-    -T) no_target_directory=true;;
-
-    --version) echo "$0 $scriptversion"; exit $?;;
-
-    --)	shift
-	break;;
-
-    -*)	echo "$0: invalid option: $1" >&2
-	exit 1;;
-
-    *)  break;;
-  esac
-  shift
-done
-
-if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
-  # When -d is used, all remaining arguments are directories to create.
-  # When -t is used, the destination is already specified.
-  # Otherwise, the last argument is the destination.  Remove it from $@.
-  for arg
-  do
-    if test -n "$dst_arg"; then
-      # $@ is not empty: it contains at least $arg.
-      set fnord "$@" "$dst_arg"
-      shift # fnord
-    fi
-    shift # arg
-    dst_arg=$arg
-  done
-fi
-
-if test $# -eq 0; then
-  if test -z "$dir_arg"; then
-    echo "$0: no input file specified." >&2
-    exit 1
-  fi
-  # It's OK to call `install-sh -d' without argument.
-  # This can happen when creating conditional directories.
-  exit 0
-fi
-
-if test -z "$dir_arg"; then
-  trap '(exit $?); exit' 1 2 13 15
-
-  # Set umask so as not to create temps with too-generous modes.
-  # However, 'strip' requires both read and write access to temps.
-  case $mode in
-    # Optimize common cases.
-    *644) cp_umask=133;;
-    *755) cp_umask=22;;
-
-    *[0-7])
-      if test -z "$stripcmd"; then
-	u_plus_rw=
-      else
-	u_plus_rw='% 200'
-      fi
-      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
-    *)
-      if test -z "$stripcmd"; then
-	u_plus_rw=
-      else
-	u_plus_rw=,u+rw
-      fi
-      cp_umask=$mode$u_plus_rw;;
-  esac
-fi
-
-for src
-do
-  # Protect names starting with `-'.
-  case $src in
-    -*) src=./$src;;
-  esac
-
-  if test -n "$dir_arg"; then
-    dst=$src
-    dstdir=$dst
-    test -d "$dstdir"
-    dstdir_status=$?
-  else
-
-    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
-    # might cause directories to be created, which would be especially bad
-    # if $src (and thus $dsttmp) contains '*'.
-    if test ! -f "$src" && test ! -d "$src"; then
-      echo "$0: $src does not exist." >&2
-      exit 1
-    fi
-
-    if test -z "$dst_arg"; then
-      echo "$0: no destination specified." >&2
-      exit 1
-    fi
-
-    dst=$dst_arg
-    # Protect names starting with `-'.
-    case $dst in
-      -*) dst=./$dst;;
-    esac
-
-    # If destination is a directory, append the input filename; won't work
-    # if double slashes aren't ignored.
-    if test -d "$dst"; then
-      if test -n "$no_target_directory"; then
-	echo "$0: $dst_arg: Is a directory" >&2
-	exit 1
-      fi
-      dstdir=$dst
-      dst=$dstdir/`basename "$src"`
-      dstdir_status=0
-    else
-      # Prefer dirname, but fall back on a substitute if dirname fails.
-      dstdir=`
-	(dirname "$dst") 2>/dev/null ||
-	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
-	     X"$dst" : 'X\(//\)[^/]' \| \
-	     X"$dst" : 'X\(//\)$' \| \
-	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
-	echo X"$dst" |
-	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
-		   s//\1/
-		   q
-		 }
-		 /^X\(\/\/\)[^/].*/{
-		   s//\1/
-		   q
-		 }
-		 /^X\(\/\/\)$/{
-		   s//\1/
-		   q
-		 }
-		 /^X\(\/\).*/{
-		   s//\1/
-		   q
-		 }
-		 s/.*/./; q'
-      `
-
-      test -d "$dstdir"
-      dstdir_status=$?
-    fi
-  fi
-
-  obsolete_mkdir_used=false
-
-  if test $dstdir_status != 0; then
-    case $posix_mkdir in
-      '')
-	# Create intermediate dirs using mode 755 as modified by the umask.
-	# This is like FreeBSD 'install' as of 1997-10-28.
-	umask=`umask`
-	case $stripcmd.$umask in
-	  # Optimize common cases.
-	  *[2367][2367]) mkdir_umask=$umask;;
-	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
-
-	  *[0-7])
-	    mkdir_umask=`expr $umask + 22 \
-	      - $umask % 100 % 40 + $umask % 20 \
-	      - $umask % 10 % 4 + $umask % 2
-	    `;;
-	  *) mkdir_umask=$umask,go-w;;
-	esac
-
-	# With -d, create the new directory with the user-specified mode.
-	# Otherwise, rely on $mkdir_umask.
-	if test -n "$dir_arg"; then
-	  mkdir_mode=-m$mode
-	else
-	  mkdir_mode=
-	fi
-
-	posix_mkdir=false
-	case $umask in
-	  *[123567][0-7][0-7])
-	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
-	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
-	    ;;
-	  *)
-	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
-	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
-
-	    if (umask $mkdir_umask &&
-		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
-	    then
-	      if test -z "$dir_arg" || {
-		   # Check for POSIX incompatibilities with -m.
-		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
-		   # other-writeable bit of parent directory when it shouldn't.
-		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
-		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
-		   case $ls_ld_tmpdir in
-		     d????-?r-*) different_mode=700;;
-		     d????-?--*) different_mode=755;;
-		     *) false;;
-		   esac &&
-		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
-		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
-		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
-		   }
-		 }
-	      then posix_mkdir=:
-	      fi
-	      rmdir "$tmpdir/d" "$tmpdir"
-	    else
-	      # Remove any dirs left behind by ancient mkdir implementations.
-	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
-	    fi
-	    trap '' 0;;
-	esac;;
-    esac
-
-    if
-      $posix_mkdir && (
-	umask $mkdir_umask &&
-	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
-      )
-    then :
-    else
-
-      # The umask is ridiculous, or mkdir does not conform to POSIX,
-      # or it failed possibly due to a race condition.  Create the
-      # directory the slow way, step by step, checking for races as we go.
-
-      case $dstdir in
-	/*) prefix='/';;
-	-*) prefix='./';;
-	*)  prefix='';;
-      esac
-
-      eval "$initialize_posix_glob"
-
-      oIFS=$IFS
-      IFS=/
-      $posix_glob set -f
-      set fnord $dstdir
-      shift
-      $posix_glob set +f
-      IFS=$oIFS
-
-      prefixes=
-
-      for d
-      do
-	test -z "$d" && continue
-
-	prefix=$prefix$d
-	if test -d "$prefix"; then
-	  prefixes=
-	else
-	  if $posix_mkdir; then
-	    (umask=$mkdir_umask &&
-	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
-	    # Don't fail if two instances are running concurrently.
-	    test -d "$prefix" || exit 1
-	  else
-	    case $prefix in
-	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
-	      *) qprefix=$prefix;;
-	    esac
-	    prefixes="$prefixes '$qprefix'"
-	  fi
-	fi
-	prefix=$prefix/
-      done
-
-      if test -n "$prefixes"; then
-	# Don't fail if two instances are running concurrently.
-	(umask $mkdir_umask &&
-	 eval "\$doit_exec \$mkdirprog $prefixes") ||
-	  test -d "$dstdir" || exit 1
-	obsolete_mkdir_used=true
-      fi
-    fi
-  fi
-
-  if test -n "$dir_arg"; then
-    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
-    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
-    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
-      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
-  else
-
-    # Make a couple of temp file names in the proper directory.
-    dsttmp=$dstdir/_inst.$$_
-    rmtmp=$dstdir/_rm.$$_
-
-    # Trap to clean up those temp files at exit.
-    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
-
-    # Copy the file name to the temp name.
-    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
-
-    # and set any options; do chmod last to preserve setuid bits.
-    #
-    # If any of these fail, we abort the whole thing.  If we want to
-    # ignore errors from any of these, just make sure not to ignore
-    # errors from the above "$doit $cpprog $src $dsttmp" command.
-    #
-    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
-    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
-    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
-    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
-
-    # If -C, don't bother to copy if it wouldn't change the file.
-    if $copy_on_change &&
-       old=`LC_ALL=C ls -dlL "$dst"	2>/dev/null` &&
-       new=`LC_ALL=C ls -dlL "$dsttmp"	2>/dev/null` &&
-
-       eval "$initialize_posix_glob" &&
-       $posix_glob set -f &&
-       set X $old && old=:$2:$4:$5:$6 &&
-       set X $new && new=:$2:$4:$5:$6 &&
-       $posix_glob set +f &&
-
-       test "$old" = "$new" &&
-       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
-    then
-      rm -f "$dsttmp"
-    else
-      # Rename the file to the real destination.
-      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
-
-      # The rename failed, perhaps because mv can't rename something else
-      # to itself, or perhaps because mv is so ancient that it does not
-      # support -f.
-      {
-	# Now remove or move aside any old file at destination location.
-	# We try this two ways since rm can't unlink itself on some
-	# systems and the destination file might be busy for other
-	# reasons.  In this case, the final cleanup might fail but the new
-	# file should still install successfully.
-	{
-	  test ! -f "$dst" ||
-	  $doit $rmcmd -f "$dst" 2>/dev/null ||
-	  { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
-	    { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
-	  } ||
-	  { echo "$0: cannot unlink or rename $dst" >&2
-	    (exit 1); exit 1
-	  }
-	} &&
-
-	# Now rename the file to the real destination.
-	$doit $mvcmd "$dsttmp" "$dst"
-      }
-    fi || exit 1
-
-    trap '' 0
-  fi
-done
-
-# Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC"
-# time-stamp-end: "; # UTC"
-# End:

+ 5 - 0
m4macros/.gitignore

@@ -0,0 +1,5 @@
+/libtool.m4
+/lt~obsolete.m4
+/ltoptions.m4
+/ltsugar.m4
+/ltversion.m4

+ 0 - 376
missing

@@ -1,376 +0,0 @@
-#! /bin/sh
-# Common stub for a few missing GNU programs while installing.
-
-scriptversion=2009-04-28.21; # UTC
-
-# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006,
-# 2008, 2009 Free Software Foundation, Inc.
-# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-if test $# -eq 0; then
-  echo 1>&2 "Try \`$0 --help' for more information"
-  exit 1
-fi
-
-run=:
-sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p'
-sed_minuso='s/.* -o \([^ ]*\).*/\1/p'
-
-# In the cases where this matters, `missing' is being run in the
-# srcdir already.
-if test -f configure.ac; then
-  configure_ac=configure.ac
-else
-  configure_ac=configure.in
-fi
-
-msg="missing on your system"
-
-case $1 in
---run)
-  # Try to run requested program, and just exit if it succeeds.
-  run=
-  shift
-  "$@" && exit 0
-  # Exit code 63 means version mismatch.  This often happens
-  # when the user try to use an ancient version of a tool on
-  # a file that requires a minimum version.  In this case we
-  # we should proceed has if the program had been absent, or
-  # if --run hadn't been passed.
-  if test $? = 63; then
-    run=:
-    msg="probably too old"
-  fi
-  ;;
-
-  -h|--h|--he|--hel|--help)
-    echo "\
-$0 [OPTION]... PROGRAM [ARGUMENT]...
-
-Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
-error status if there is no known handling for PROGRAM.
-
-Options:
-  -h, --help      display this help and exit
-  -v, --version   output version information and exit
-  --run           try to run the given command, and emulate it if it fails
-
-Supported PROGRAM values:
-  aclocal      touch file \`aclocal.m4'
-  autoconf     touch file \`configure'
-  autoheader   touch file \`config.h.in'
-  autom4te     touch the output file, or create a stub one
-  automake     touch all \`Makefile.in' files
-  bison        create \`y.tab.[ch]', if possible, from existing .[ch]
-  flex         create \`lex.yy.c', if possible, from existing .c
-  help2man     touch the output file
-  lex          create \`lex.yy.c', if possible, from existing .c
-  makeinfo     touch the output file
-  tar          try tar, gnutar, gtar, then tar without non-portable flags
-  yacc         create \`y.tab.[ch]', if possible, from existing .[ch]
-
-Version suffixes to PROGRAM as well as the prefixes \`gnu-', \`gnu', and
-\`g' are ignored when checking the name.
-
-Send bug reports to <bug-automake@gnu.org>."
-    exit $?
-    ;;
-
-  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
-    echo "missing $scriptversion (GNU Automake)"
-    exit $?
-    ;;
-
-  -*)
-    echo 1>&2 "$0: Unknown \`$1' option"
-    echo 1>&2 "Try \`$0 --help' for more information"
-    exit 1
-    ;;
-
-esac
-
-# normalize program name to check for.
-program=`echo "$1" | sed '
-  s/^gnu-//; t
-  s/^gnu//; t
-  s/^g//; t'`
-
-# Now exit if we have it, but it failed.  Also exit now if we
-# don't have it and --version was passed (most likely to detect
-# the program).  This is about non-GNU programs, so use $1 not
-# $program.
-case $1 in
-  lex*|yacc*)
-    # Not GNU programs, they don't have --version.
-    ;;
-
-  tar*)
-    if test -n "$run"; then
-       echo 1>&2 "ERROR: \`tar' requires --run"
-       exit 1
-    elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
-       exit 1
-    fi
-    ;;
-
-  *)
-    if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
-       # We have it, but it failed.
-       exit 1
-    elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
-       # Could not run --version or --help.  This is probably someone
-       # running `$TOOL --version' or `$TOOL --help' to check whether
-       # $TOOL exists and not knowing $TOOL uses missing.
-       exit 1
-    fi
-    ;;
-esac
-
-# If it does not exist, or fails to run (possibly an outdated version),
-# try to emulate it.
-case $program in
-  aclocal*)
-    echo 1>&2 "\
-WARNING: \`$1' is $msg.  You should only need it if
-         you modified \`acinclude.m4' or \`${configure_ac}'.  You might want
-         to install the \`Automake' and \`Perl' packages.  Grab them from
-         any GNU archive site."
-    touch aclocal.m4
-    ;;
-
-  autoconf*)
-    echo 1>&2 "\
-WARNING: \`$1' is $msg.  You should only need it if
-         you modified \`${configure_ac}'.  You might want to install the
-         \`Autoconf' and \`GNU m4' packages.  Grab them from any GNU
-         archive site."
-    touch configure
-    ;;
-
-  autoheader*)
-    echo 1>&2 "\
-WARNING: \`$1' is $msg.  You should only need it if
-         you modified \`acconfig.h' or \`${configure_ac}'.  You might want
-         to install the \`Autoconf' and \`GNU m4' packages.  Grab them
-         from any GNU archive site."
-    files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
-    test -z "$files" && files="config.h"
-    touch_files=
-    for f in $files; do
-      case $f in
-      *:*) touch_files="$touch_files "`echo "$f" |
-				       sed -e 's/^[^:]*://' -e 's/:.*//'`;;
-      *) touch_files="$touch_files $f.in";;
-      esac
-    done
-    touch $touch_files
-    ;;
-
-  automake*)
-    echo 1>&2 "\
-WARNING: \`$1' is $msg.  You should only need it if
-         you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
-         You might want to install the \`Automake' and \`Perl' packages.
-         Grab them from any GNU archive site."
-    find . -type f -name Makefile.am -print |
-	   sed 's/\.am$/.in/' |
-	   while read f; do touch "$f"; done
-    ;;
-
-  autom4te*)
-    echo 1>&2 "\
-WARNING: \`$1' is needed, but is $msg.
-         You might have modified some files without having the
-         proper tools for further handling them.
-         You can get \`$1' as part of \`Autoconf' from any GNU
-         archive site."
-
-    file=`echo "$*" | sed -n "$sed_output"`
-    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
-    if test -f "$file"; then
-	touch $file
-    else
-	test -z "$file" || exec >$file
-	echo "#! /bin/sh"
-	echo "# Created by GNU Automake missing as a replacement of"
-	echo "#  $ $@"
-	echo "exit 0"
-	chmod +x $file
-	exit 1
-    fi
-    ;;
-
-  bison*|yacc*)
-    echo 1>&2 "\
-WARNING: \`$1' $msg.  You should only need it if
-         you modified a \`.y' file.  You may need the \`Bison' package
-         in order for those modifications to take effect.  You can get
-         \`Bison' from any GNU archive site."
-    rm -f y.tab.c y.tab.h
-    if test $# -ne 1; then
-        eval LASTARG="\${$#}"
-	case $LASTARG in
-	*.y)
-	    SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
-	    if test -f "$SRCFILE"; then
-	         cp "$SRCFILE" y.tab.c
-	    fi
-	    SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
-	    if test -f "$SRCFILE"; then
-	         cp "$SRCFILE" y.tab.h
-	    fi
-	  ;;
-	esac
-    fi
-    if test ! -f y.tab.h; then
-	echo >y.tab.h
-    fi
-    if test ! -f y.tab.c; then
-	echo 'main() { return 0; }' >y.tab.c
-    fi
-    ;;
-
-  lex*|flex*)
-    echo 1>&2 "\
-WARNING: \`$1' is $msg.  You should only need it if
-         you modified a \`.l' file.  You may need the \`Flex' package
-         in order for those modifications to take effect.  You can get
-         \`Flex' from any GNU archive site."
-    rm -f lex.yy.c
-    if test $# -ne 1; then
-        eval LASTARG="\${$#}"
-	case $LASTARG in
-	*.l)
-	    SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
-	    if test -f "$SRCFILE"; then
-	         cp "$SRCFILE" lex.yy.c
-	    fi
-	  ;;
-	esac
-    fi
-    if test ! -f lex.yy.c; then
-	echo 'main() { return 0; }' >lex.yy.c
-    fi
-    ;;
-
-  help2man*)
-    echo 1>&2 "\
-WARNING: \`$1' is $msg.  You should only need it if
-	 you modified a dependency of a manual page.  You may need the
-	 \`Help2man' package in order for those modifications to take
-	 effect.  You can get \`Help2man' from any GNU archive site."
-
-    file=`echo "$*" | sed -n "$sed_output"`
-    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
-    if test -f "$file"; then
-	touch $file
-    else
-	test -z "$file" || exec >$file
-	echo ".ab help2man is required to generate this page"
-	exit $?
-    fi
-    ;;
-
-  makeinfo*)
-    echo 1>&2 "\
-WARNING: \`$1' is $msg.  You should only need it if
-         you modified a \`.texi' or \`.texinfo' file, or any other file
-         indirectly affecting the aspect of the manual.  The spurious
-         call might also be the consequence of using a buggy \`make' (AIX,
-         DU, IRIX).  You might want to install the \`Texinfo' package or
-         the \`GNU make' package.  Grab either from any GNU archive site."
-    # The file to touch is that specified with -o ...
-    file=`echo "$*" | sed -n "$sed_output"`
-    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
-    if test -z "$file"; then
-      # ... or it is the one specified with @setfilename ...
-      infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
-      file=`sed -n '
-	/^@setfilename/{
-	  s/.* \([^ ]*\) *$/\1/
-	  p
-	  q
-	}' $infile`
-      # ... or it is derived from the source name (dir/f.texi becomes f.info)
-      test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info
-    fi
-    # If the file does not exist, the user really needs makeinfo;
-    # let's fail without touching anything.
-    test -f $file || exit 1
-    touch $file
-    ;;
-
-  tar*)
-    shift
-
-    # We have already tried tar in the generic part.
-    # Look for gnutar/gtar before invocation to avoid ugly error
-    # messages.
-    if (gnutar --version > /dev/null 2>&1); then
-       gnutar "$@" && exit 0
-    fi
-    if (gtar --version > /dev/null 2>&1); then
-       gtar "$@" && exit 0
-    fi
-    firstarg="$1"
-    if shift; then
-	case $firstarg in
-	*o*)
-	    firstarg=`echo "$firstarg" | sed s/o//`
-	    tar "$firstarg" "$@" && exit 0
-	    ;;
-	esac
-	case $firstarg in
-	*h*)
-	    firstarg=`echo "$firstarg" | sed s/h//`
-	    tar "$firstarg" "$@" && exit 0
-	    ;;
-	esac
-    fi
-
-    echo 1>&2 "\
-WARNING: I can't seem to be able to run \`tar' with the given arguments.
-         You may want to install GNU tar or Free paxutils, or check the
-         command line arguments."
-    exit 1
-    ;;
-
-  *)
-    echo 1>&2 "\
-WARNING: \`$1' is needed, and is $msg.
-         You might have modified some files without having the
-         proper tools for further handling them.  Check the \`README' file,
-         it often tells you about the needed prerequisites for installing
-         this package.  You may also peek at any GNU archive site, in case
-         some other package would contain this missing \`$1' program."
-    exit 1
-    ;;
-esac
-
-exit 0
-
-# Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC"
-# time-stamp-end: "; # UTC"
-# End:

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

@@ -51,9 +51,9 @@ b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc
 # This is a temporary workaround for #1206, where the InMemoryClient has been
 # moved to an ldopened library. We could add that library to LDADD, but that
-# is nonportable. When #1207 is done this becomes moot anyway, and the
-# specific workaround is not needed anymore, so we can then remove this
-# line again.
+# is nonportable. This should've been moot after #1207, but there is still
+# one dependency; the in-memory-specific zone loader call is still in
+# auth.
 b10_auth_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
@@ -62,6 +62,7 @@ EXTRA_DIST += auth_messages.mes
 b10_auth_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 b10_auth_LDADD += $(top_builddir)/src/lib/util/libutil.la
+b10_auth_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
 b10_auth_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
 b10_auth_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la

+ 4 - 0
src/bin/auth/auth.spec.pre.in

@@ -47,6 +47,10 @@
                 "item_type": "string",
                 "item_optional": false,
                 "item_default": ""
+              },
+              { "item_name": "filetype",
+                "item_type": "string",
+                "item_optional": true
               }]
             }
           }]

+ 62 - 128
src/bin/auth/auth_config.cc

@@ -12,14 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <set>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <boost/foreach.hpp>
-#include <boost/shared_ptr.hpp>
-
 #include <dns/name.h>
 #include <dns/rrclass.h>
 
@@ -27,6 +19,7 @@
 
 #include <datasrc/memory_datasrc.h>
 #include <datasrc/zonetable.h>
+#include <datasrc/factory.h>
 
 #include <auth/auth_srv.h>
 #include <auth/auth_config.h>
@@ -34,6 +27,15 @@
 
 #include <server_common/portconfig.h>
 
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
 using namespace std;
 using namespace isc::dns;
 using namespace isc::data;
@@ -41,22 +43,19 @@ using namespace isc::datasrc;
 using namespace isc::server_common::portconfig;
 
 namespace {
-// Forward declaration
-AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id,
-                       bool internal);
-
 /// A derived \c AuthConfigParser class for the "datasources" configuration
 /// identifier.
 class DatasourcesConfig : public AuthConfigParser {
 public:
-    DatasourcesConfig(AuthSrv& server) : server_(server) {}
+    DatasourcesConfig(AuthSrv& server) : server_(server)
+    {}
     virtual void build(ConstElementPtr config_value);
     virtual void commit();
 private:
     AuthSrv& server_;
     vector<boost::shared_ptr<AuthConfigParser> > datasources_;
     set<string> configured_sources_;
+    vector<pair<RRClass, DataSourceClientContainerPtr> > clients_;
 };
 
 /// A derived \c AuthConfigParser for the version value
@@ -84,114 +83,60 @@ DatasourcesConfig::build(ConstElementPtr config_value) {
             isc_throw(AuthConfigError, "Data source type '" <<
                       datasrc_type->stringValue() << "' already configured");
         }
-        
-        boost::shared_ptr<AuthConfigParser> datasrc_config =
-            boost::shared_ptr<AuthConfigParser>(
-                createAuthConfigParser(server_, string("datasources/") +
-                                       datasrc_type->stringValue(),
-                                       true));
-        datasrc_config->build(datasrc_elem);
-        datasources_.push_back(datasrc_config);
 
-        configured_sources_.insert(datasrc_type->stringValue());
-    }
-}
+        // Apart from that it's not really easy to get at the default
+        // class value for the class here, it should probably really
+        // be a property of the instantiated data source. For now
+        // use hardcoded default IN.
+        const RRClass rrclass =
+            datasrc_elem->contains("class") ?
+            RRClass(datasrc_elem->get("class")->stringValue()) : RRClass::IN();
+
+        // Right now, we only support the in-memory data source for the
+        // RR class of IN.  We reject other cases explicitly by hardcoded
+        // checks.  This will soon be generalized, at which point these
+        // checks will also have to be cleaned up.
+        if (rrclass != RRClass::IN()) {
+            isc_throw(isc::InvalidParameter, "Unsupported data source class: "
+                      << rrclass);
+        }
+        if (datasrc_type->stringValue() != "memory") {
+            isc_throw(AuthConfigError, "Unsupported data source type: "
+                      << datasrc_type->stringValue());
+        }
 
-void
-DatasourcesConfig::commit() {
-    // XXX a short term workaround: clear all data sources and then reset
-    // to new ones so that we can remove data sources that don't exist in
-    // the new configuration and have been used in the server.
-    // This could be inefficient and requires knowledge about
-    // server implementation details, and isn't scalable wrt the number of
-    // data source types, and should eventually be improved.
-    // Currently memory data source for class IN is the only possibility.
-    server_.setInMemoryClient(RRClass::IN(), AuthSrv::InMemoryClientPtr());
+        // Create a new client for the specified data source and store it
+        // in the local vector.  For now, we always build a new client
+        // from the scratch, and replace any existing ones with the new ones.
+        // We might eventually want to optimize building zones (in case of
+        // reloading) by selectively loading fresh zones for data source
+        // where zone loading is expensive (such as in-memory).
+        clients_.push_back(
+            pair<RRClass, DataSourceClientContainerPtr>(
+                rrclass,
+                DataSourceClientContainerPtr(new DataSourceClientContainer(
+                                                 datasrc_type->stringValue(),
+                                                 datasrc_elem))));
 
-    BOOST_FOREACH(boost::shared_ptr<AuthConfigParser> datasrc_config,
-                  datasources_) {
-        datasrc_config->commit();
+        configured_sources_.insert(datasrc_type->stringValue());
     }
 }
 
-/// A derived \c AuthConfigParser class for the memory type datasource
-/// configuration.  It does not correspond to the configuration syntax;
-/// it's instantiated for internal use.
-class MemoryDatasourceConfig : public AuthConfigParser {
-public:
-    MemoryDatasourceConfig(AuthSrv& server) :
-        server_(server),
-        rrclass_(0)              // XXX: dummy initial value
-    {}
-    virtual void build(ConstElementPtr config_value);
-    virtual void commit() {
-        server_.setInMemoryClient(rrclass_, memory_client_);
-    }
-private:
-    AuthSrv& server_;
-    RRClass rrclass_;
-    AuthSrv::InMemoryClientPtr memory_client_;
-};
-
 void
-MemoryDatasourceConfig::build(ConstElementPtr config_value) {
-    // XXX: apparently we cannot retrieve the default RR class from the
-    // module spec.  As a temporary workaround we hardcode the default value.
-    ConstElementPtr rrclass_elem = config_value->get("class");
-    rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
-
-    // We'd eventually optimize building zones (in case of reloading) by
-    // selectively loading fresh zones.  Right now we simply check the
-    // RR class is supported by the server implementation.
-    server_.getInMemoryClient(rrclass_);
-    memory_client_ = AuthSrv::InMemoryClientPtr(new InMemoryClient());
-
-    ConstElementPtr zones_config = config_value->get("zones");
-    if (!zones_config) {
-        // XXX: Like the RR class, we cannot retrieve the default value here,
-        // so we assume an empty zone list in this case.
-        return;
-    }
-
-    BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
-        ConstElementPtr origin = zone_config->get("origin");
-        const string origin_txt = origin ? origin->stringValue() : "";
-        if (origin_txt.empty()) {
-            isc_throw(AuthConfigError, "Missing zone origin");
-        }
-        ConstElementPtr file = zone_config->get("file");
-        const string file_txt = file ? file->stringValue() : "";
-        if (file_txt.empty()) {
-            isc_throw(AuthConfigError, "Missing zone file for zone: "
-                      << origin->str());
-        }
-
-        // Note: we don't want to have such small try-catch blocks for each
-        // specific error.  We may eventually want to introduce some unified
-        // error handling framework as we have more configuration parameters.
-        // See bug #1627 for the relevant discussion.
-        InMemoryZoneFinder* imzf = NULL;
-        try {
-            imzf = new InMemoryZoneFinder(rrclass_, Name(origin_txt));
-        } catch (const isc::dns::NameParserException& ex) {
-            isc_throw(AuthConfigError, "unable to parse zone's origin: " <<
-                      ex.what());
-        }
-
-        boost::shared_ptr<InMemoryZoneFinder> zone_finder(imzf);
-        const result::Result result = memory_client_->addZone(zone_finder);
-        if (result == result::EXIST) {
-            isc_throw(AuthConfigError, "zone "<< origin->str()
-                      << " already exists");
-        }
-
-        /*
-         * TODO: Once we have better reloading of configuration (something
-         * else than throwing everything away and loading it again), we will
-         * need the load method to be split into some kind of build and
-         * commit/abort parts.
-         */
-        zone_finder->load(file_txt);
+DatasourcesConfig::commit() {
+    // As noted in build(), the current implementation only supports the
+    // in-memory data source for class IN, and build() should have ensured
+    // it.  So, depending on the vector is empty or not, we either clear
+    // or install an in-memory data source for the server.
+    //
+    // When we generalize it, we'll somehow install all data source clients
+    // built in the vector, clearing deleted ones from the server.
+    if (clients_.empty()) {
+        server_.setInMemoryClient(RRClass::IN(),
+                                  DataSourceClientContainerPtr());
+    } else {
+        server_.setInMemoryClient(clients_.front().first,
+                                  clients_.front().second);
     }
 }
 
@@ -289,13 +234,10 @@ private:
      */
     AddrListPtr rollbackAddresses_;
 };
+} // end of unnamed namespace
 
-// This is a generalized version of create function that can create
-// an AuthConfigParser object for "internal" use.
 AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id,
-                       bool internal)
-{
+createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
     // For the initial implementation we use a naive if-else blocks for
     // simplicity.  In future we'll probably generalize it using map-like
     // data structure, and may even provide external register interface so
@@ -304,8 +246,6 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
         return (new DatasourcesConfig(server));
     } else if (config_id == "statistics-interval") {
         return (new StatisticsIntervalConfig(server));
-    } else if (internal && config_id == "datasources/memory") {
-        return (new MemoryDatasourceConfig(server));
     } else if (config_id == "listen_on") {
         return (new ListenAddressConfig(server));
     } else if (config_id == "_commit_throw") {
@@ -326,12 +266,6 @@ createAuthConfigParser(AuthSrv& server, const std::string& config_id,
                   config_id);
     }
 }
-} // end of unnamed namespace
-
-AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
-    return (createAuthConfigParser(server, config_id, false));
-}
 
 void
 configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {

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

@@ -255,6 +255,20 @@ processed by the authoritative server has been found to contain an
 unsupported opcode. (The opcode is included in the message.) The server
 will return an error code of NOTIMPL to the sender.
 
+% AUTH_MESSAGE_FORWARD_ERROR failed to forward %1 request from %2: %3
+The authoritative server tried to forward some type DNS request
+message to a separate process (e.g., forwarding dynamic update
+requests to b10-ddns) to handle it, but it failed.  The authoritative
+server returns SERVFAIL to the client on behalf of the separate
+process.  The error could be configuration mismatch between b10-auth
+and the recipient component, or it may be because the requests are
+coming too fast and the receipient process cannot keep up with the
+rate, or some system level failure.  In either case this means the
+BIND 10 system is not working as expected, so the administrator should
+look into the cause and address the issue.  The log message includes
+the client's address (and port), and the error message sent from the
+lower layer that detects the failure.
+
 % AUTH_XFRIN_CHANNEL_CREATED XFRIN session channel created
 This is a debug message indicating that the authoritative server has
 created a channel to the XFRIN (Transfer-in) process.  It is issued

+ 171 - 30
src/bin/auth/auth_srv.cc

@@ -14,18 +14,10 @@
 
 #include <config.h>
 
-#include <sys/types.h>
-#include <netinet/in.h>
-
-#include <algorithm>
-#include <cassert>
-#include <iostream>
-#include <vector>
-#include <memory>
-
-#include <boost/bind.hpp>
+#include <util/io/socketsession.h>
 
 #include <asiolink/asiolink.h>
+#include <asiolink/io_endpoint.h>
 
 #include <config/ccsession.h>
 
@@ -64,6 +56,18 @@
 #include <auth/statistics.h>
 #include <auth/auth_log.h>
 
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <vector>
+#include <memory>
+
+#include <sys/types.h>
+#include <netinet/in.h>
+
 using namespace std;
 
 using namespace isc;
@@ -71,6 +75,7 @@ using namespace isc::cc;
 using namespace isc::datasrc;
 using namespace isc::dns;
 using namespace isc::util;
+using namespace isc::util::io;
 using namespace isc::auth;
 using namespace isc::dns::rdata;
 using namespace isc::data;
@@ -107,6 +112,107 @@ public:
 private:
     MessageRenderer& renderer_;
 };
+
+// A helper container of socket session forwarder.
+//
+// This class provides a simple wrapper interface to SocketSessionForwarder
+// so that the caller doesn't have to worry about connection management,
+// exception handling or parameter building.
+//
+// It internally maintains whether the underlying forwarder establishes a
+// connection to the receiver.  On a forwarding request, if the connection
+// hasn't been established yet, it automatically opens a new one, then
+// pushes the session over it.  It also closes the connection on destruction,
+// or a non-recoverable error happens, automatically.  So the only thing
+// the application has to do is to create this object and push any session
+// to be forwarded.
+class SocketSessionForwarderHolder {
+public:
+    /// \brief The constructor.
+    ///
+    /// \param message_name Any string that can identify the type of messages
+    /// to be forwarded via this session.  It will be only used as part of
+    /// log message, so it can be anything, but in practice something like
+    /// "update" or "xfr" is expected.
+    /// \param forwarder The underlying socket session forwarder.
+    SocketSessionForwarderHolder(const string& message_name,
+                                 BaseSocketSessionForwarder& forwarder) :
+        message_name_(message_name), forwarder_(forwarder), connected_(false)
+    {}
+
+    ~SocketSessionForwarderHolder() {
+        if (connected_) {
+            forwarder_.close();
+        }
+    }
+
+    /// \brief Push a socket session corresponding to given IOMessage.
+    ///
+    /// If the connection with the receiver process hasn't been established,
+    /// it automatically establishes one, then push the session over it.
+    ///
+    /// If either connect or push fails, the underlying forwarder object should
+    /// throw an exception.  This method logs the event, and propagates the
+    /// exception to the caller, which will eventually result in SERVFAIL.
+    /// The connection, if established, is automatically closed, so the next
+    /// forward request will trigger reopening a new connection.
+    ///
+    /// \note: Right now, there's no API to retrieve the local address from
+    /// the IOMessage.  Until it's added, we pass the remote address as
+    /// local.
+    ///
+    /// \param io_message The request message to be forwarded as a socket
+    /// session.  It will be converted to the parameters that the underlying
+    /// SocketSessionForwarder expects.
+    void push(const IOMessage& io_message) {
+        const IOEndpoint& remote_ep = io_message.getRemoteEndpoint();
+        const int protocol = remote_ep.getProtocol();
+        const int sock_type = getSocketType(protocol);
+        try {
+            connect();
+            forwarder_.push(io_message.getSocket().getNative(),
+                            remote_ep.getFamily(), sock_type, protocol,
+                            remote_ep.getSockAddr(), remote_ep.getSockAddr(),
+                            io_message.getData(), io_message.getDataSize());
+        } catch (const SocketSessionError& ex) {
+            LOG_ERROR(auth_logger, AUTH_MESSAGE_FORWARD_ERROR).
+                arg(message_name_).arg(remote_ep).arg(ex.what());
+            close();
+            throw;
+        }
+    }
+
+private:
+    const string message_name_;
+    BaseSocketSessionForwarder& forwarder_;
+    bool connected_;
+
+    void connect() {
+        if (!connected_) {
+            forwarder_.connectToReceiver();
+            connected_ = true;
+        }
+    }
+
+    void close() {
+        if (connected_) {
+            forwarder_.close();
+            connected_ = false;
+        }
+    }
+
+    static int getSocketType(int protocol) {
+        switch (protocol) {
+        case IPPROTO_UDP:
+            return (SOCK_DGRAM);
+        case IPPROTO_TCP:
+            return (SOCK_STREAM);
+        default:
+            isc_throw(isc::InvalidParameter,
+                      "Unexpected socket address family: " << protocol);
+        }
+    }
+};
 }
 
 class AuthSrvImpl {
@@ -115,7 +221,8 @@ private:
     AuthSrvImpl(const AuthSrvImpl& source);
     AuthSrvImpl& operator=(const AuthSrvImpl& source);
 public:
-    AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client);
+    AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client,
+                BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrvImpl();
     isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
 
@@ -128,6 +235,7 @@ public:
     bool processNotify(const IOMessage& io_message, Message& message,
                        OutputBuffer& buffer,
                        auto_ptr<TSIGContext> tsig_context);
+    bool processUpdate(const IOMessage& io_message);
 
     IOService io_service_;
 
@@ -141,7 +249,7 @@ public:
 
     /// In-memory data source.  Currently class IN only for simplicity.
     const RRClass memory_client_class_;
-    AuthSrv::InMemoryClientPtr memory_client_;
+    isc::datasrc::DataSourceClientContainerPtr memory_client_container_;
 
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
@@ -189,6 +297,9 @@ private:
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
 
+    // Socket session forwarder for dynamic update requests
+    SocketSessionForwarderHolder ddns_forwarder_;
+
     /// Increment query counter
     void incCounter(const int protocol);
 
@@ -199,7 +310,8 @@ private:
 };
 
 AuthSrvImpl::AuthSrvImpl(const bool use_cache,
-                         AbstractXfroutClient& xfrout_client) :
+                         AbstractXfroutClient& xfrout_client,
+                         BaseSocketSessionForwarder& ddns_forwarder) :
     config_session_(NULL),
     xfrin_session_(NULL),
     memory_client_class_(RRClass::IN()),
@@ -207,7 +319,8 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
     counters_(),
     keyring_(NULL),
     xfrout_connected_(false),
-    xfrout_client_(xfrout_client)
+    xfrout_client_(xfrout_client),
+    ddns_forwarder_("update", ddns_forwarder)
 {
     // cur_datasrc_ is automatically initialized by the default constructor,
     // effectively being an empty (sqlite) data source.  once ccsession is up
@@ -277,9 +390,11 @@ private:
     AuthSrv* server_;
 };
 
-AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client)
+AuthSrv::AuthSrv(const bool use_cache,
+                 isc::xfr::AbstractXfroutClient& xfrout_client,
+                 isc::util::io::BaseSocketSessionForwarder& ddns_forwarder)
 {
-    impl_ = new AuthSrvImpl(use_cache, xfrout_client);
+    impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
     checkin_ = new ConfigChecker(this);
     dns_lookup_ = new MessageLookup(this);
     dns_answer_ = new MessageAnswer(this);
@@ -389,34 +504,46 @@ AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
 }
 
-AuthSrv::InMemoryClientPtr
-AuthSrv::getInMemoryClient(const RRClass& rrclass) {
-    // XXX: for simplicity, we only support the IN class right now.
+isc::datasrc::DataSourceClientContainerPtr
+AuthSrv::getInMemoryClientContainer(const RRClass& rrclass) {
     if (rrclass != impl_->memory_client_class_) {
         isc_throw(InvalidParameter,
                   "Memory data source is not supported for RR class "
                   << rrclass);
     }
-    return (impl_->memory_client_);
+    return (impl_->memory_client_container_);
+}
+
+isc::datasrc::DataSourceClient*
+AuthSrv::getInMemoryClient(const RRClass& rrclass) {
+    if (hasInMemoryClient()) {
+        return (&getInMemoryClientContainer(rrclass)->getInstance());
+    } else {
+        return (NULL);
+    }
+}
+
+bool
+AuthSrv::hasInMemoryClient() const {
+    return (impl_->memory_client_container_);
 }
 
 void
 AuthSrv::setInMemoryClient(const isc::dns::RRClass& rrclass,
-                           InMemoryClientPtr memory_client)
+                           DataSourceClientContainerPtr memory_client)
 {
-    // XXX: see above
     if (rrclass != impl_->memory_client_class_) {
         isc_throw(InvalidParameter,
                   "Memory data source is not supported for RR class "
                   << rrclass);
-    } else if (!impl_->memory_client_ && memory_client) {
+    } else if (!impl_->memory_client_container_ && memory_client) {
         LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_ENABLED)
                   .arg(rrclass);
-    } else if (impl_->memory_client_ && !memory_client) {
+    } else if (impl_->memory_client_container_ && !memory_client) {
         LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_DISABLED)
                   .arg(rrclass);
     }
-    impl_->memory_client_ = memory_client;
+    impl_->memory_client_container_ = memory_client;
 }
 
 uint32_t
@@ -515,16 +642,19 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         return;
     }
 
+    const Opcode opcode = message.getOpcode();
     bool send_answer = true;
     try {
         // update per opcode statistics counter.  This can only be reliable
         // after TSIG check succeeds.
         impl_->counters_.inc(message.getOpcode());
 
-        if (message.getOpcode() == Opcode::NOTIFY()) {
+        if (opcode == Opcode::NOTIFY()) {
             send_answer = impl_->processNotify(io_message, message, buffer,
                                                tsig_context);
-        } else if (message.getOpcode() != Opcode::QUERY()) {
+        } else if (opcode == Opcode::UPDATE()) {
+            send_answer = impl_->processUpdate(io_message);
+        } else if (opcode != Opcode::QUERY()) {
             LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
                       .arg(message.getOpcode().toText());
             makeErrorMessage(impl_->renderer_, message, buffer,
@@ -534,7 +664,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                              Rcode::FORMERR(), tsig_context);
         } else {
             ConstQuestionPtr question = *message.beginQuestion();
-            const RRType &qtype = question->getType();
+            const RRType& qtype = question->getType();
             if (qtype == RRType::AXFR()) {
                 send_answer = impl_->processXfrQuery(io_message, message,
                                                      buffer, tsig_context);
@@ -585,10 +715,12 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
         // If a memory data source is configured call the separate
         // Query::process()
         const ConstQuestionPtr question = *message.beginQuestion();
-        if (memory_client_ && memory_client_class_ == question->getClass()) {
+        if (memory_client_container_ &&
+            memory_client_class_ == question->getClass()) {
             const RRType& qtype = question->getType();
             const Name& qname = question->getName();
-            query_.process(*memory_client_, qname, qtype, message, dnssec_ok);
+            query_.process(memory_client_container_->getInstance(),
+                           qname, qtype, message, dnssec_ok);
         } else {
             datasrc::Query query(message, cache_, dnssec_ok);
             data_sources_.doQuery(query);
@@ -740,6 +872,15 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     return (true);
 }
 
+bool
+AuthSrvImpl::processUpdate(const IOMessage& io_message) {
+    // Push the update request to a separate process via the forwarder.
+    // On successful push, the request shouldn't be responded from b10-auth,
+    // so we return false.
+    ddns_forwarder_.push(io_message);
+    return (false);
+}
+
 void
 AuthSrvImpl::incCounter(const int protocol) {
     // Increment query counter.

+ 47 - 25
src/bin/auth/auth_srv.h

@@ -17,12 +17,9 @@
 
 #include <string>
 
-// For InMemoryClientPtr below.  This should be a temporary definition until
-// we reorganize the data source framework.
-#include <boost/shared_ptr.hpp>
-
 #include <cc/data.h>
 #include <config/ccsession.h>
+#include <datasrc/factory.h>
 #include <dns/message.h>
 #include <dns/opcode.h>
 #include <util/buffer.h>
@@ -40,6 +37,11 @@
 #include <auth/statistics.h>
 
 namespace isc {
+namespace util {
+namespace io {
+class BaseSocketSessionForwarder;
+}
+}
 namespace datasrc {
 class InMemoryClient;
 }
@@ -96,7 +98,8 @@ public:
     /// but can refer to a local mock object for testing (or other
     /// experimental) purposes.
     AuthSrv(const bool use_cache,
-            isc::xfr::AbstractXfroutClient& xfrout_client);
+            isc::xfr::AbstractXfroutClient& xfrout_client,
+            isc::util::io::BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrv();
     //@}
 
@@ -235,19 +238,14 @@ public:
     ///
     void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
 
-    /// A shared pointer type for \c InMemoryClient.
-    ///
-    /// This is defined inside the \c AuthSrv class as it's supposed to be
-    /// a short term interface until we integrate the in-memory and other
-    /// data source frameworks.
-    typedef boost::shared_ptr<isc::datasrc::InMemoryClient> InMemoryClientPtr;
-
-    /// An immutable shared pointer type for \c InMemoryClient.
-    typedef boost::shared_ptr<const isc::datasrc::InMemoryClient>
-    ConstInMemoryClientPtr;
-
     /// Returns the in-memory data source configured for the \c AuthSrv,
-    /// if any.
+    /// if any, as a pointer.
+    ///
+    /// This is mostly a convenience function around
+    /// \c getInMemoryClientContainer, which saves the caller the step
+    /// of having to call getInstance().
+    /// The pointer is of course only valid as long as the container
+    /// exists.
     ///
     /// The in-memory data source is configured per RR class.  However,
     /// the data source may not be available for all RR classes.
@@ -262,24 +260,48 @@ public:
     /// \param rrclass The RR class of the requested in-memory data source.
     /// \return A pointer to the in-memory data source, if configured;
     /// otherwise NULL.
-    InMemoryClientPtr getInMemoryClient(const isc::dns::RRClass& rrclass);
+    isc::datasrc::DataSourceClient* getInMemoryClient(
+        const isc::dns::RRClass& rrclass);
+
+    /// Returns the DataSourceClientContainer of the in-memory datasource
+    ///
+    /// \exception InvalidParameter if the given class does not match
+    ///            the one in the memory data source, or if the memory
+    ///            datasource has not been set (callers can check with
+    ///            \c hasMemoryDataSource())
+    ///
+    /// \param rrclass The RR class of the requested in-memory data source.
+    /// \return A shared pointer to the in-memory data source, if configured;
+    /// otherwise an empty shared pointer.
+    isc::datasrc::DataSourceClientContainerPtr getInMemoryClientContainer(
+        const isc::dns::RRClass& rrclass);
+
+    /// Checks if the in-memory data source has been set.
+    ///
+    /// Right now, only one datasource at a time is effectively supported.
+    /// This is a helper method to check whether it is the in-memory one.
+    /// This is mostly useful for current testing, and is expected to be
+    /// removed (or changed in behaviour) soon, when the general
+    /// multi-data-source framework is completed.
+    ///
+    /// \return True if the in-memory datasource has been set.
+    bool hasInMemoryClient() const;
 
     /// Sets or replaces the in-memory data source of the specified RR class.
     ///
-    /// As noted in \c getInMemoryClient(), some RR classes may not be
-    /// supported, in which case an exception of class \c InvalidParameter
-    /// will be thrown.
+    /// Some RR classes may not be supported, in which case an exception
+    /// of class \c InvalidParameter will be thrown.
     /// This method never throws an exception otherwise.
     ///
     /// If there is already an in memory data source configured, it will be
     /// replaced with the newly specified one.
-    /// \c memory_datasrc can be NULL, in which case it will (re)disable the
-    /// in-memory data source.
+    /// \c memory_client can be an empty shared pointer, in which case it
+    /// will (re)disable the in-memory data source.
     ///
     /// \param rrclass The RR class of the in-memory data source to be set.
-    /// \param memory_datasrc A (shared) pointer to \c InMemoryClient to be set.
+    /// \param memory_client A (shared) pointer to \c InMemoryClient to be set.
     void setInMemoryClient(const isc::dns::RRClass& rrclass,
-                           InMemoryClientPtr memory_client);
+        isc::datasrc::DataSourceClientContainerPtr memory_client);
 
     /// \brief Set the communication session with Statistics.
     ///

+ 17 - 5
src/bin/auth/b10-auth.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-auth
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: March 28, 2012
+.\"      Date: May 16, 2012
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-AUTH" "8" "March 28, 2012" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "May 16, 2012" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -72,9 +72,21 @@ to optionally select the class (it defaults to
 \fIzones\fR
 to define the
 \fIfile\fR
-path name and the
+path name,
 \fIorigin\fR
-(default domain)\&. By default, this is empty\&.
+(default domain), and optional
+\fIfiletype\fR\&. By default,
+\fIzones\fR
+is empty\&. For the in\-memory data source (i\&.e\&., the
+\fItype\fR
+is
+\(lqmemory\(rq), the optional
+\fIfiletype\fR
+configuration item for
+\fIzones\fR
+can be specified so the in\-memory zone data can be built from another data source that is based on a database backend (in practice with current implementation, it would be an SQLite3 database file for the SQLite3 data source)\&. See the
+BIND 10 Guide
+for configuration details\&.
 .if n \{\
 .sp
 .\}
@@ -88,7 +100,7 @@ path name and the
 .ps -1
 .br
 .sp
-In this development version, currently this is only used for the memory data source\&. Only the IN class is supported at this time\&. By default, the memory data source is disabled\&. Also, currently the zone file must be canonical such as generated by \fBnamed\-compilezone \-D\fR\&.
+Only the IN class is supported at this time\&. By default, the memory data source is disabled\&. Also, currently the zone file must be canonical such as generated by \fBnamed\-compilezone \-D\fR\&.
 .sp .5v
 .RE
 .PP

+ 14 - 7
src/bin/auth/b10-auth.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>March 28, 2012</date>
+    <date>May 16, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -125,14 +125,21 @@
       (it defaults to <quote>IN</quote>);
       and
       <varname>zones</varname> to define the
-      <varname>file</varname> path name and the
-      <varname>origin</varname> (default domain).
-
-      By default, this is empty.
+      <varname>file</varname> path name,
+      <varname>origin</varname> (default domain), and optional
+      <varname>filetype</varname>.
+      By default, <varname>zones</varname> is empty.
+      For the in-memory data source (i.e., the <varname>type</varname>
+      is <quote>memory</quote>), the optional <varname>filetype</varname>
+      configuration item for <varname>zones</varname> can be
+      specified so the in-memory zone data can be built from another
+      data source that is based on a database backend (in practice
+      with current implementation, it would be an SQLite3 database
+      file for the SQLite3 data source).
+      See the <citetitle>BIND 10 Guide</citetitle> for configuration
+      details.
 
       <note><simpara>
-        In this development version, currently this is only used for the
-        memory data source.
         Only the IN class is supported at this time.
         By default, the memory data source is disabled.
         Also, currently the zone file must be canonical such as

+ 6 - 2
src/bin/auth/benchmarks/query_bench.cc

@@ -31,9 +31,10 @@
 #include <dns/rrclass.h>
 
 #include <log/logger_support.h>
-
 #include <xfr/xfrout_client.h>
 
+#include <util/unittests/mock_socketsession.h>
+
 #include <auth/auth_srv.h>
 #include <auth/auth_config.h>
 #include <auth/query.h>
@@ -48,6 +49,7 @@ using namespace isc::auth;
 using namespace isc::dns;
 using namespace isc::log;
 using namespace isc::util;
+using namespace isc::util::unittests;
 using namespace isc::xfr;
 using namespace isc::bench;
 using namespace isc::asiodns;
@@ -78,7 +80,7 @@ protected:
     QueryBenchMark(const bool enable_cache,
                    const BenchQueries& queries, Message& query_message,
                    OutputBuffer& buffer) :
-        server_(new AuthSrv(enable_cache, xfrout_client)),
+        server_(new AuthSrv(enable_cache, xfrout_client, ddns_forwarder)),
         queries_(queries),
         query_message_(query_message),
         buffer_(buffer),
@@ -103,6 +105,8 @@ public:
 
         return (queries_.size());
     }
+private:
+    MockSocketSessionForwarder ddns_forwarder;
 protected:
     AuthSrvPtr server_;
 private:

+ 89 - 12
src/bin/auth/command.cc

@@ -18,6 +18,7 @@
 
 #include <cc/data.h>
 #include <datasrc/memory_datasrc.h>
+#include <datasrc/factory.h>
 #include <config/ccsession.h>
 #include <exceptions/exceptions.h>
 #include <dns/rrclass.h>
@@ -149,26 +150,39 @@ public:
             return;
         }
 
+        const ConstElementPtr zone_config = getZoneConfig(server);
+
         // Load a new zone and replace the current zone with the new one.
         // TODO: eventually this should be incremental or done in some way
         // that doesn't block other server operations.
         // TODO: we may (should?) want to check the "last load time" and
         // the timestamp of the file and skip loading if the file isn't newer.
+        const ConstElementPtr type(zone_config->get("filetype"));
         boost::shared_ptr<InMemoryZoneFinder> zone_finder(
-            new InMemoryZoneFinder(old_zone_finder->getClass(),
-                                   old_zone_finder->getOrigin()));
-        zone_finder->load(old_zone_finder->getFileName());
-        old_zone_finder->swap(*zone_finder);
+            new InMemoryZoneFinder(old_zone_finder_->getClass(),
+                                   old_zone_finder_->getOrigin()));
+        if (type && type->stringValue() == "sqlite3") {
+            scoped_ptr<DataSourceClientContainer>
+                container(new DataSourceClientContainer("sqlite3",
+                                                        Element::fromJSON(
+                    "{\"database_file\": \"" +
+                    zone_config->get("file")->stringValue() + "\"}")));
+            zone_finder->load(*container->getInstance().getIterator(
+                old_zone_finder_->getOrigin()));
+        } else {
+            zone_finder->load(old_zone_finder_->getFileName());
+        }
+        old_zone_finder_->swap(*zone_finder);
         LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
                   .arg(zone_finder->getOrigin()).arg(zone_finder->getClass());
     }
 
 private:
     // zone finder to be updated with the new file.
-    boost::shared_ptr<InMemoryZoneFinder> old_zone_finder;
+    boost::shared_ptr<InMemoryZoneFinder> old_zone_finder_;
 
     // A helper private method to parse and validate command parameters.
-    // On success, it sets 'old_zone_finder' to the zone to be updated.
+    // On success, it sets 'old_zone_finder_' to the zone to be updated.
     // It returns true if everything is okay; and false if the command is
     // valid but there's no need for further process.
     bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
@@ -193,10 +207,11 @@ private:
         }
 
         ConstElementPtr class_elem = args->get("class");
-        const RRClass zone_class = class_elem ?
-            RRClass(class_elem->stringValue()) : RRClass::IN();
+        const RRClass zone_class =
+            class_elem ? RRClass(class_elem->stringValue()) : RRClass::IN();
 
-        AuthSrv::InMemoryClientPtr datasrc(server.getInMemoryClient(zone_class));
+        isc::datasrc::DataSourceClient* datasrc(
+            server.getInMemoryClient(zone_class));
         if (datasrc == NULL) {
             isc_throw(AuthCommandError, "Memory data source is disabled");
         }
@@ -205,20 +220,82 @@ private:
         if (!origin_elem) {
             isc_throw(AuthCommandError, "Zone origin is missing");
         }
-        const Name origin(origin_elem->stringValue());
+        const Name origin = Name(origin_elem->stringValue());
 
         // Get the current zone
-        const InMemoryClient::FindResult result = datasrc->findZone(origin);
+        const DataSourceClient::FindResult result = datasrc->findZone(origin);
         if (result.code != result::SUCCESS) {
             isc_throw(AuthCommandError, "Zone " << origin <<
                       " is not found in data source");
         }
 
-        old_zone_finder = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
+        // It would appear that dynamic_cast does not work on all systems;
+        // it seems to confuse the RTTI system, resulting in NULL return
+        // values. So we use the more dangerous static_pointer_cast here.
+        old_zone_finder_ = boost::static_pointer_cast<InMemoryZoneFinder>(
             result.zone_finder);
 
         return (true);
     }
+
+    ConstElementPtr getZoneConfig(const AuthSrv &server) {
+        if (!server.getConfigSession()) {
+            // FIXME: This is a hack to make older tests pass. We should
+            // update these tests as well sometime and remove this hack.
+            // (note that under normal situation, the
+            // server.getConfigSession() does not return NULL)
+
+            // We provide an empty map, which means no configuration --
+            // defaults.
+            return (ConstElementPtr(new MapElement()));
+        }
+
+        // Find the config corresponding to the zone.
+        // We expect the configuration to be valid, as we have it and we
+        // accepted it before, therefore it must be validated.
+        const ConstElementPtr config(server.getConfigSession()->
+                                     getValue("datasources"));
+        ConstElementPtr zone_list;
+        // Unfortunately, we need to walk the list to find the correct data
+        // source.
+        // TODO: Make it named sets. These lists are uncomfortable.
+        for (size_t i(0); i < config->size(); ++i) {
+            // We use the getValue to get defaults as well
+            const ConstElementPtr dsrc_config(config->get(i));
+            const ConstElementPtr class_config(dsrc_config->get("class"));
+            const string class_type(class_config ?
+                                    class_config->stringValue() : "IN");
+            // It is in-memory and our class matches.
+            // FIXME: Is it allowed to have two datasources for the same
+            // type and class at once? It probably would not work now
+            // anyway and we may want to change the configuration of
+            // datasources somehow.
+            if (dsrc_config->get("type")->stringValue() == "memory" &&
+                RRClass(class_type) == old_zone_finder_->getClass()) {
+                zone_list = dsrc_config->get("zones");
+                break;
+            }
+        }
+
+        if (!zone_list) {
+            isc_throw(AuthCommandError,
+                      "Corresponding data source configuration was not found");
+        }
+
+        // Now we need to walk the zones and find the correct one.
+        for (size_t i(0); i < zone_list->size(); ++i) {
+            const ConstElementPtr zone_config(zone_list->get(i));
+            if (Name(zone_config->get("origin")->stringValue()) ==
+                old_zone_finder_->getOrigin()) {
+                // The origins are the same, so we consider this config to be
+                // for the zone.
+                return (zone_config);
+            }
+        }
+
+        isc_throw(AuthCommandError,
+                  "Corresponding zone configuration was not found");
+    }
 };
 
 // The factory of command objects.

+ 19 - 1
src/bin/auth/common.cc

@@ -33,7 +33,25 @@ getXfroutSocketPath() {
         if (getenv("BIND10_XFROUT_SOCKET_FILE") != NULL) {
             return (getenv("BIND10_XFROUT_SOCKET_FILE"));
         } else {
-            return (UNIX_SOCKET_FILE);
+            return (UNIX_XFROUT_SOCKET_FILE);
+        }
+    }
+}
+
+string
+getDDNSSocketPath() {
+    if (getenv("B10_FROM_BUILD") != NULL) {
+        if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR") != NULL) {
+            return (string(getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) +
+                    "/ddns_socket");
+        } else {
+            return (string(getenv("B10_FROM_BUILD")) + "/ddns_socket");
+        }
+    } else {
+        if (getenv("BIND10_DDNS_SOCKET_FILE") != NULL) {
+            return (getenv("BIND10_DDNS_SOCKET_FILE"));
+        } else {
+            return (UNIX_DDNS_SOCKET_FILE);
         }
     }
 }

+ 14 - 0
src/bin/auth/common.h

@@ -38,6 +38,20 @@ public:
 /// The logic should be the same as in b10-xfrout, so they find each other.
 std::string getXfroutSocketPath();
 
+/// \brief Get the path of socket to talk to ddns
+///
+/// It takes some environment variables into account (B10_FROM_BUILD,
+/// B10_FROM_SOURCE_LOCALSTATEDIR and BIND10_DDNS_SOCKET_FILE). It
+/// also considers the installation prefix.
+///
+/// The logic should be the same as in b10-ddns, so they find each other.
+///
+/// Note: eventually we should find a better way so that we don't have to
+/// repeat the same magic value (and how to tweak it with some magic
+/// environment variable) twice, at which point this function may be able
+/// to be deprecated.
+std::string getDDNSSocketPath();
+
 /// \brief The name used when identifieng the process
 ///
 /// This is currently b10-auth, but it can be changed easily in one place.

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

@@ -28,6 +28,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
+#include <util/io/socketsession.h>
 
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
@@ -60,6 +61,7 @@ using namespace isc::data;
 using namespace isc::dns;
 using namespace isc::log;
 using namespace isc::util;
+using namespace isc::util::io;
 using namespace isc::xfr;
 
 namespace {
@@ -130,6 +132,7 @@ main(int argc, char* argv[]) {
     bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
     XfroutClient xfrout_client(getXfroutSocketPath());
+    SocketSessionForwarder ddns_forwarder(getDDNSSocketPath());
     try {
         string specfile;
         if (getenv("B10_FROM_BUILD")) {
@@ -139,7 +142,7 @@ main(int argc, char* argv[]) {
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
 
-        auth_server = new AuthSrv(cache, xfrout_client);
+        auth_server = new AuthSrv(cache, xfrout_client, ddns_forwarder);
         LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
 
         SimpleCallback* checkin = auth_server->getCheckinProvider();

+ 2 - 1
src/bin/auth/spec_config.h.pre.in

@@ -13,4 +13,5 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #define AUTH_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/auth.spec"
-#define UNIX_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
+#define UNIX_XFROUT_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
+#define UNIX_DDNS_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/ddns_socket"

+ 20 - 9
src/bin/auth/tests/Makefile.am

@@ -3,6 +3,7 @@ AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
@@ -11,14 +12,18 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 if USE_STATIC_LINK
 AM_LDFLAGS = -static
+# Some test cases cannot work with static link.  To selectively disable such
+# tests we signal it via a definition.
+AM_CPPFLAGS += -DUSE_STATIC_LINK=1
 endif
 
 CLEANFILES = *.gcno *.gcda
 
+# Do not define global tests, use check-local so
+# environment can be set (needed for dynamic loading)
 TESTS =
 if HAVE_GTEST
 
-TESTS += run_unittests
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
@@ -28,8 +33,10 @@ run_unittests_SOURCES += ../auth_config.h ../auth_config.cc
 run_unittests_SOURCES += ../command.h ../command.cc
 run_unittests_SOURCES += ../common.h ../common.cc
 run_unittests_SOURCES += ../statistics.h ../statistics.cc
+run_unittests_SOURCES += datasrc_util.h datasrc_util.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
+run_unittests_SOURCES += config_syntax_unittest.cc
 run_unittests_SOURCES += command_unittest.cc
 run_unittests_SOURCES += common_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
@@ -37,9 +44,9 @@ run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 # This is a temporary workaround for #1206, where the InMemoryClient has been
 # moved to an ldopened library. We could add that library to LDADD, but that
-# is nonportable. When #1207 is done this becomes moot anyway, and the
-# specific workaround is not needed anymore, so we can then remove this
-# line again.
+# is nonportable. This should've been moot after #1207, but there is still
+# one dependency; the in-memory-specific zone loader call is still in
+# auth.
 run_unittests_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 
@@ -47,9 +54,7 @@ nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(SQLITE_LIBS)
-run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
+run_unittests_LDADD = $(top_builddir)/src/lib/testutils/libtestutils.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
@@ -64,6 +69,12 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
 run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/statistics/libstatistics.la
-endif
+run_unittests_LDADD += $(GTEST_LDADD)
+run_unittests_LDADD += $(SQLITE_LIBS)
 
-noinst_PROGRAMS = $(TESTS)
+check-local:
+	B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
+
+noinst_PROGRAMS = run_unittests
+
+endif

+ 305 - 46
src/bin/auth/tests/auth_srv_unittest.cc

@@ -14,11 +14,7 @@
 
 #include <config.h>
 
-#include <vector>
-
-#include <boost/shared_ptr.hpp>
-
-#include <gtest/gtest.h>
+#include <util/io/sockaddr_util.h>
 
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
@@ -38,6 +34,7 @@
 #include <auth/common.h>
 #include <auth/statistics.h>
 
+#include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
@@ -45,10 +42,24 @@
 #include <testutils/portconfig.h>
 #include <testutils/socket_request.h>
 
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <vector>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
 using namespace std;
 using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::util;
+using namespace isc::util::io::internal;
+using namespace isc::util::unittests;
 using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::xfr;
@@ -57,6 +68,7 @@ using namespace isc::asiolink;
 using namespace isc::testutils;
 using namespace isc::server_common::portconfig;
 using isc::UnitTestUtil;
+using boost::scoped_ptr;
 
 namespace {
 const char* const CONFIG_TESTDB =
@@ -77,7 +89,7 @@ class AuthSrvTest : public SrvTestBase {
 protected:
     AuthSrvTest() :
         dnss_(),
-        server(true, xfrout),
+        server(true, xfrout, ddns_forwarder),
         rrclass(RRClass::IN()),
         // The empty string is expected value of the parameter of
         // requestSocket, not the app_name (there's no fallback, it checks
@@ -89,6 +101,13 @@ protected:
         server.setStatisticsSession(&statistics_session);
     }
 
+    ~AuthSrvTest() {
+        // Clear the message now; depending on the RTTI implementation,
+        // type information may be lost if the message is cleared
+        // automatically later, so as a precaution we do it now.
+        parse_message->clear(Message::PARSE);
+    }
+
     virtual void processMessage() {
         // If processMessage has been called before, parse_message needs
         // to be reset. If it hasn't, there's no harm in doing so
@@ -136,9 +155,30 @@ protected:
                     opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
     }
 
+    // Convenient shortcut of creating a simple request and having the
+    // server process it.
+    void createAndSendRequest(RRType req_type, Opcode opcode = Opcode::QUERY(),
+                              const Name& req_name = Name("example.com"),
+                              RRClass req_class = RRClass::IN(),
+                              int protocol = IPPROTO_UDP,
+                              const char* const remote_address =
+                              DEFAULT_REMOTE_ADDRESS,
+                              uint16_t remote_port = DEFAULT_REMOTE_PORT)
+    {
+        UnitTestUtil::createRequestMessage(request_message, opcode,
+                                           default_qid, req_name,
+                                           req_class, req_type);
+        createRequestPacket(request_message, protocol, NULL,
+                            remote_address, remote_port);
+        parse_message->clear(Message::PARSE);
+        server.processMessage(*io_message, *parse_message, *response_obuffer,
+                              &dnsserv);
+    }
+
     MockDNSService dnss_;
     MockSession statistics_session;
     MockXfroutClient xfrout;
+    MockSocketSessionForwarder ddns_forwarder;
     AuthSrv server;
     const RRClass rrclass;
     vector<uint8_t> response_data;
@@ -246,8 +286,8 @@ TEST_F(AuthSrvTest, iqueryViaDNSServer) {
 // Unsupported requests.  Should result in NOTIMP.
 TEST_F(AuthSrvTest, unsupportedRequest) {
     unsupportedRequest();
-    // unsupportedRequest tries 14 different opcodes
-    checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 14);
+    // unsupportedRequest tries 13 different opcodes
+    checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 13);
 }
 
 // Multiple questions.  Should result in FORMERR.
@@ -830,16 +870,23 @@ TEST_F(AuthSrvTest, updateConfigFail) {
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
-TEST_F(AuthSrvTest, updateWithInMemoryClient) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_updateWithInMemoryClient
+#else
+       updateWithInMemoryClient
+#endif
+    )
+{
     // Test configuring memory data source.  Detailed test cases are covered
     // in the configuration tests.  We only check the AuthSrv interface here.
 
     // By default memory data source isn't enabled
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     updateConfig(&server,
                  "{\"datasources\": [{\"type\": \"memory\"}]}", true);
     // after successful configuration, we should have one (with empty zoneset).
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 
     // The memory data source is empty, should return REFUSED rcode.
@@ -851,13 +898,20 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
-TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientNoDNSSEC
+#else
+       queryWithInMemoryClientNoDNSSEC
+#endif
+    )
+{
     // In this example, we do simple check that query is handled from the
     // query handler class, and confirm it returns no error and a non empty
     // answer section.  Detailed examination on the response content
     // for various types of queries are tested in the query tests.
     updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
@@ -869,12 +923,19 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
-TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientDNSSEC
+#else
+       queryWithInMemoryClientDNSSEC
+#endif
+    )
+{
     // Similar to the previous test, but the query has the DO bit on.
     // The response should contain RRSIGs, and should have more RRs than
     // the previous case.
     updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
 
     createDataFromFile("nsec3query_fromWire.wire");
@@ -886,7 +947,14 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 2, 3, 3);
 }
 
-TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_chQueryWithInMemoryClient
+#else
+       chQueryWithInMemoryClient
+#endif
+    )
+{
     // Configure memory data source for class IN
     updateConfig(&server, "{\"datasources\": "
                  "[{\"class\": \"IN\", \"type\": \"memory\"}]}", true);
@@ -1108,7 +1176,7 @@ TEST_F(AuthSrvTest, processNormalQuery_reuseRenderer2) {
 //
 namespace {
 
-/// A the possible methods to throw in, either in FakeInMemoryClient or
+/// The possible methods to throw in, either in FakeClient or
 /// FakeZoneFinder
 enum ThrowWhen {
     THROW_NEVER,
@@ -1132,10 +1200,10 @@ checkThrow(ThrowWhen method, ThrowWhen throw_at, bool isc_exception) {
     }
 }
 
-/// \brief proxy class for the ZoneFinder returned by the InMemoryClient
-///        proxied by FakeInMemoryClient
+/// \brief proxy class for the ZoneFinder returned by the Client
+///        proxied by FakeClient
 ///
-/// See the documentation for FakeInMemoryClient for more information,
+/// See the documentation for FakeClient for more information,
 /// all methods simply check whether they should throw, and if not, call
 /// their proxied equivalent.
 class FakeZoneFinder : public isc::datasrc::ZoneFinder {
@@ -1208,15 +1276,15 @@ private:
     ConstRRsetPtr fake_rrset_;
 };
 
-/// \brief Proxy InMemoryClient that can throw exceptions at specified times
+/// \brief Proxy FakeClient that can throw exceptions at specified times
 ///
-/// It is based on the memory client since that one is easy to override
-/// (with setInMemoryClient) with the current design of AuthSrv.
-class FakeInMemoryClient : public isc::datasrc::InMemoryClient {
+/// Currently it is used as an 'InMemoryClient' using setInMemoryClient,
+/// but it is in effect a general datasource client.
+class FakeClient : public isc::datasrc::DataSourceClient {
 public:
     /// \brief Create a proxy memory client
     ///
-    /// \param real_client The real in-memory client to proxy
+    /// \param real_client The real (in-memory) client to proxy
     /// \param throw_when if set to any value other than never, that is
     ///        the method that will throw an exception (either in this
     ///        class or the related FakeZoneFinder)
@@ -1224,10 +1292,10 @@ public:
     ///                      throw std::exception
     /// \param fake_rrset If non NULL, it will be used as an answer to
     /// find() for that name and type.
-    FakeInMemoryClient(AuthSrv::InMemoryClientPtr real_client,
-                       ThrowWhen throw_when, bool isc_exception,
-                       ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
-        real_client_(real_client),
+    FakeClient(isc::datasrc::DataSourceClientContainerPtr real_client,
+               ThrowWhen throw_when, bool isc_exception,
+               ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
+        real_client_ptr_(real_client),
         throw_when_(throw_when),
         isc_exception_(isc_exception),
         fake_rrset_(fake_rrset)
@@ -1242,7 +1310,8 @@ public:
     virtual FindResult
     findZone(const isc::dns::Name& name) const {
         checkThrow(THROW_AT_FIND_ZONE, throw_when_, isc_exception_);
-        const FindResult result = real_client_->findZone(name);
+        const FindResult result =
+            real_client_ptr_->getInstance().findZone(name);
         return (FindResult(result.code, isc::datasrc::ZoneFinderPtr(
                                         new FakeZoneFinder(result.zone_finder,
                                                            throw_when_,
@@ -1250,28 +1319,74 @@ public:
                                                            fake_rrset_))));
     }
 
+    isc::datasrc::ZoneUpdaterPtr
+    getUpdater(const isc::dns::Name&, bool, bool) const {
+        isc_throw(isc::NotImplemented,
+                  "Update attempt on in fake data source");
+    }
+    std::pair<isc::datasrc::ZoneJournalReader::Result,
+              isc::datasrc::ZoneJournalReaderPtr>
+    getJournalReader(const isc::dns::Name&, uint32_t, uint32_t) const {
+        isc_throw(isc::NotImplemented, "Journaling isn't supported for "
+                  "fake data source");
+    }
 private:
-    AuthSrv::InMemoryClientPtr real_client_;
+    const isc::datasrc::DataSourceClientContainerPtr real_client_ptr_;
     ThrowWhen throw_when_;
     bool isc_exception_;
     ConstRRsetPtr fake_rrset_;
 };
 
+class FakeContainer : public isc::datasrc::DataSourceClientContainer {
+public:
+    /// \brief Creates a fake container for the given in-memory client
+    ///
+    /// The initializer creates a fresh instance of a memory datasource,
+    /// which is ignored for the rest (but we do not allow 'null' containers
+    /// atm, and this is only needed in these tests, this may be changed
+    /// if we generalize the container class a bit more)
+    ///
+    /// It will also create a FakeClient, with the given arguments, which
+    /// is actually used when the instance is requested.
+    FakeContainer(isc::datasrc::DataSourceClientContainerPtr real_client,
+                  ThrowWhen throw_when, bool isc_exception,
+                  ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
+        DataSourceClientContainer("memory",
+                                  Element::fromJSON("{\"type\": \"memory\"}")),
+        client_(new FakeClient(real_client, throw_when, isc_exception,
+                               fake_rrset))
+    {}
+
+    isc::datasrc::DataSourceClient& getInstance() {
+        return (*client_);
+    }
+
+private:
+    const boost::scoped_ptr<isc::datasrc::DataSourceClient> client_;
+};
+
 } // end anonymous namespace for throwing proxy classes
 
 // Test for the tests
 //
 // Set the proxies to never throw, this should have the same result as
 // queryWithInMemoryClientNoDNSSEC, and serves to test the two proxy classes
-TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientProxy
+#else
+       queryWithInMemoryClientProxy
+#endif
+    )
+{
     // Set real inmem client to proxy
     updateConfig(&server, CONFIG_INMEMORY_EXAMPLE, true);
+    EXPECT_TRUE(server.hasInMemoryClient());
 
-    AuthSrv::InMemoryClientPtr fake_client(
-        new FakeInMemoryClient(server.getInMemoryClient(rrclass),
-                               THROW_NEVER, false));
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
-    server.setInMemoryClient(rrclass, fake_client);
+    isc::datasrc::DataSourceClientContainerPtr fake_client_container(
+        new FakeContainer(server.getInMemoryClientContainer(rrclass),
+                          THROW_NEVER, false));
+    server.setInMemoryClient(rrclass, fake_client_container);
 
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -1297,17 +1412,23 @@ setupThrow(AuthSrv* server, const char *config, ThrowWhen throw_when,
 
     // Set it to throw on findZone(), this should result in
     // SERVFAIL on any exception
-    AuthSrv::InMemoryClientPtr fake_client(
-        new FakeInMemoryClient(
-            server->getInMemoryClient(isc::dns::RRClass::IN()),
+    isc::datasrc::DataSourceClientContainerPtr fake_client_container(
+        new FakeContainer(
+            server->getInMemoryClientContainer(isc::dns::RRClass::IN()),
             throw_when, isc_exception, rrset));
 
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(),
-              server->getInMemoryClient(isc::dns::RRClass::IN()));
-    server->setInMemoryClient(isc::dns::RRClass::IN(), fake_client);
+    ASSERT_TRUE(server->hasInMemoryClient());
+    server->setInMemoryClient(isc::dns::RRClass::IN(), fake_client_container);
 }
 
-TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithThrowingProxyServfails
+#else
+       queryWithThrowingProxyServfails
+#endif
+    )
+{
     // Test the common cases, all of which should simply return SERVFAIL
     // Use THROW_NEVER as end marker
     ThrowWhen throws[] = { THROW_AT_FIND_ZONE,
@@ -1331,7 +1452,14 @@ TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
 
 // Throw isc::Exception in getClass(). (Currently?) getClass is not called
 // in the processMessage path, so this should result in a normal answer
-TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithInMemoryClientProxyGetClass
+#else
+       queryWithInMemoryClientProxyGetClass
+#endif
+    )
+{
     createDataFromFile("nsec3query_nodnssec_fromWire.wire");
     setupThrow(&server, CONFIG_INMEMORY_EXAMPLE, THROW_AT_GET_CLASS, true);
 
@@ -1344,7 +1472,14 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
 }
 
-TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
+TEST_F(AuthSrvTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_queryWithThrowingInToWire
+#else
+       queryWithThrowingInToWire
+#endif
+    )
+{
     // Set up a faked data source.  It will return an empty RRset for the
     // query.
     ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
@@ -1385,4 +1520,128 @@ TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
+//
+// DDNS related tests
+//
+
+// Helper subroutine to check if the given socket address has the expected
+// address and port.  It depends on specific output of getnameinfo() (while
+// there can be multiple textual representation of the same address) but
+// in practice it should be reliable.
+void
+checkAddrPort(const struct sockaddr& actual_sa,
+              const string& expected_addr, uint16_t expected_port)
+{
+    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+    const int error = getnameinfo(&actual_sa, getSALength(actual_sa), hbuf,
+                                  sizeof(hbuf), sbuf, sizeof(sbuf),
+                                  NI_NUMERICHOST | NI_NUMERICSERV);
+    if (error != 0) {
+        isc_throw(isc::Unexpected, "getnameinfo failed: " <<
+                  gai_strerror(error));
+    }
+    EXPECT_EQ(expected_addr, hbuf);
+    EXPECT_EQ(boost::lexical_cast<string>(expected_port), sbuf);
+}
+
+TEST_F(AuthSrvTest, DDNSForward) {
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+
+    // Repeat sending an update request 4 times, differing some network
+    // parameters: UDP/IPv4, TCP/IPv4, UDP/IPv6, TCP/IPv6, in this order.
+    // By doing that we can also confirm the forwarder connection will be
+    // established exactly once, and kept established.
+    for (size_t i = 0; i < 4; ++i) {
+        // Use different names for some different cases
+        const Name zone_name = Name(i < 2 ? "example.com" : "example.org");
+        const socklen_t family = (i < 2) ? AF_INET : AF_INET6;
+        const char* const remote_addr =
+            (family == AF_INET) ? "192.0.2.1" : "2001:db8::1";
+        const uint16_t remote_port =
+            (family == AF_INET) ? 53214 : 53216;
+        const int protocol = ((i % 2) == 0) ? IPPROTO_UDP : IPPROTO_TCP;
+
+        createAndSendRequest(RRType::SOA(), Opcode::UPDATE(), zone_name,
+                             RRClass::IN(), protocol, remote_addr,
+                             remote_port);
+        EXPECT_FALSE(dnsserv.hasAnswer());
+        EXPECT_TRUE(ddns_forwarder.isConnected());
+
+        // Examine the pushed data (note: currently "local end" has a dummy
+        // value equal to remote)
+        EXPECT_EQ(family, ddns_forwarder.getPushedFamily());
+        const int expected_type =
+            (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+        EXPECT_EQ(expected_type, ddns_forwarder.getPushedType());
+        EXPECT_EQ(protocol, ddns_forwarder.getPushedProtocol());
+        checkAddrPort(ddns_forwarder.getPushedRemoteend(),
+                      remote_addr, remote_port);
+        checkAddrPort(ddns_forwarder.getPushedLocalend(),
+                      remote_addr, remote_port);
+        EXPECT_EQ(io_message->getDataSize(),
+                  ddns_forwarder.getPushedData().size());
+        EXPECT_EQ(0, memcmp(io_message->getData(),
+                            &ddns_forwarder.getPushedData()[0],
+                            ddns_forwarder.getPushedData().size()));
+    }
+}
+
+TEST_F(AuthSrvTest, DDNSForwardConnectFail) {
+    // make connect attempt fail.  It should result in SERVFAIL.  Note that
+    // the question (zone) section should be cleared for opcode of update.
+    ddns_forwarder.disableConnect();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+
+    // Now make connect okay again.  Despite the previous failure the new
+    // connection should now be established.
+    ddns_forwarder.enableConnect();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_FALSE(dnsserv.hasAnswer());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+}
+
+TEST_F(AuthSrvTest, DDNSForwardPushFail) {
+    // Make first request succeed, which will establish the connection.
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+
+    // make connect attempt fail.  It should result in SERVFAIL.  The
+    // connection should be closed.  Use IPv6 address for varying log output.
+    ddns_forwarder.disablePush();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE(), Name("example.com"),
+                         RRClass::IN(), IPPROTO_UDP, "2001:db8::2");
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+                Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+
+    // Allow push again.  Connection will be reopened, and the request will
+    // be forwarded successfully.
+    ddns_forwarder.enablePush();
+    createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+    EXPECT_FALSE(dnsserv.hasAnswer());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+}
+
+TEST_F(AuthSrvTest, DDNSForwardClose) {
+    scoped_ptr<AuthSrv> tmp_server(new AuthSrv(true, xfrout, ddns_forwarder));
+    UnitTestUtil::createRequestMessage(request_message, Opcode::UPDATE(),
+                                       default_qid, Name("example.com"),
+                                       RRClass::IN(), RRType::SOA());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    tmp_server->processMessage(*io_message, *parse_message, *response_obuffer,
+                               &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
+    EXPECT_TRUE(ddns_forwarder.isConnected());
+
+    // Destroy the server.  The forwarder should close the connection.
+    tmp_server.reset();
+    EXPECT_FALSE(ddns_forwarder.isConnected());
+}
+
 }

+ 191 - 12
src/bin/auth/tests/command_unittest.cc

@@ -14,6 +14,8 @@
 
 #include <config.h>
 
+#include "datasrc_util.h"
+
 #include <auth/auth_srv.h>
 #include <auth/auth_config.h>
 #include <auth/command.h>
@@ -21,6 +23,7 @@
 #include <dns/name.h>
 #include <dns/rrclass.h>
 #include <dns/rrtype.h>
+#include <dns/rrttl.h>
 
 #include <cc/data.h>
 
@@ -30,6 +33,7 @@
 
 #include <asiolink/asiolink.h>
 
+#include <util/unittests/mock_socketsession.h>
 #include <testutils/mockups.h>
 
 #include <cassert>
@@ -49,13 +53,16 @@ using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::config;
+using namespace isc::util::unittests;
 using namespace isc::testutils;
+using namespace isc::auth::unittest;
 
 namespace {
+
 class AuthCommandTest : public ::testing::Test {
 protected:
     AuthCommandTest() :
-        server_(false, xfrout_),
+        server_(false, xfrout_, ddns_forwarder_),
         rcode_(-1),
         expect_rcode_(0),
         itimer_(server_.getIOService())
@@ -64,10 +71,11 @@ protected:
     }
     void checkAnswer(const int expected_code) {
         parseAnswer(rcode_, result_);
-        EXPECT_EQ(expected_code, rcode_);
+        EXPECT_EQ(expected_code, rcode_) << result_->str();
     }
     MockSession statistics_session_;
     MockXfroutClient xfrout_;
+    MockSocketSessionForwarder ddns_forwarder_;
     AuthSrv server_;
     ConstElementPtr result_;
     // The shutdown command parameter
@@ -191,9 +199,9 @@ zoneChecks(AuthSrv& server) {
 
 void
 configureZones(AuthSrv& server) {
-    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test1.zone.in "
+    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
                         TEST_DATA_BUILDDIR "/test1.zone.copied"));
-    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test2.zone.in "
+    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
                         TEST_DATA_BUILDDIR "/test2.zone.copied"));
     configureAuthServer(server, Element::fromJSON(
                             "{\"datasources\": "
@@ -229,13 +237,20 @@ newZoneChecks(AuthSrv& server) {
               find(Name("ns.test2.example"), RRType::AAAA())->code);
 }
 
-TEST_F(AuthCommandTest, loadZone) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZone
+#else
+       loadZone
+#endif
+    )
+{
     configureZones(server_);
 
-    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
                         "/test1-new.zone.in "
                         TEST_DATA_BUILDDIR "/test1.zone.copied"));
-    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
                         "/test2-new.zone.in "
                         TEST_DATA_BUILDDIR "/test2.zone.copied"));
 
@@ -246,10 +261,160 @@ TEST_F(AuthCommandTest, loadZone) {
     newZoneChecks(server_);
 }
 
-TEST_F(AuthCommandTest, loadBrokenZone) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZoneSQLite3
+#else
+       loadZoneSQLite3
+#endif
+    )
+{
+    const char* const SPEC_FILE = AUTH_OBJ_DIR "/auth.spec";
+
+    // Prepare the database first
+    const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
+    const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3";
+    stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+    createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss);
+
+    // Then store a config of the zone to the auth server
+    // This omits many config options of the auth server, but these are
+    // not read now.
+    isc::testutils::MockSession session;
+    // The session should not take care of anything or start anything, we
+    // need it only to hold the config we're going to put into it.
+    ModuleCCSession module_session(SPEC_FILE, session, NULL, NULL, false,
+                                  false);
+    // This describes the data source in the configuration
+    const ElementPtr
+        map(Element::fromJSON("{\"datasources\": ["
+                              "  {"
+                              "    \"type\": \"memory\","
+                              "    \"zones\": ["
+                              "      {"
+                              "        \"origin\": \"example.org\","
+                              "        \"file\": \"" + test_db + "\","
+                              "        \"filetype\": \"sqlite3\""
+                              "      }"
+                              "    ]"
+                              "  }"
+                              "],"
+                              " \"database_file\": \"" + test_db + "\""
+                              "}"));
+    module_session.setLocalConfig(map);
+    server_.setConfigSession(&module_session);
+
+    server_.updateConfig(map);
+
+    // Check that the A record at www.example.org does not exist
+    ASSERT_TRUE(server_.hasInMemoryClient());
+    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
+              findZone(Name("example.org")).zone_finder->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Add the record to the underlying sqlite database, by loading
+    // it as a separate datasource, and updating it
+    ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
+                                                "\"database_file\": \""
+                                                + test_db + "\"}");
+    DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
+    ZoneUpdaterPtr sql_updater =
+        sql_ds.getInstance().getUpdater(Name("example.org"), false);
+    RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(),
+                             RRType::A(), RRTTL(60)));
+    rrset->addRdata(rdata::createRdata(rrset->getType(),
+                                       rrset->getClass(),
+                                       "192.0.2.1"));
+    sql_updater->addRRset(*rrset);
+    sql_updater->commit();
+
+    // This new record is in the database now, but should not be in the
+    // memory-datasource yet, so check again
+    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
+              findZone(Name("example.org")).zone_finder->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Now send the command to reload it
+    result_ = execAuthServerCommand(server_, "loadzone",
+                                    Element::fromJSON(
+                                        "{\"origin\": \"example.org\"}"));
+    checkAnswer(0);
+
+    // And now it should be present too.
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+              findZone(Name("example.org")).zone_finder->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Some error cases. First, the zone has no configuration. (note .com here)
+    result_ = execAuthServerCommand(server_, "loadzone",
+        Element::fromJSON("{\"origin\": \"example.com\"}"));
+    checkAnswer(1);
+    // The previous zone is not hurt in any way
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+              findZone(Name("example.org")).zone_finder->
+              find(Name("example.org"), RRType::SOA())->code);
+
+    module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
+    result_ = execAuthServerCommand(server_, "loadzone",
+                                    Element::fromJSON(
+                                        "{\"origin\": \"example.org\"}"));
+    checkAnswer(1);
+
+    // The previous zone is not hurt in any way
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+              findZone(Name("example.org")).zone_finder->
+              find(Name("example.org"), RRType::SOA())->code);
+    // Configure an unreadable zone. Should fail, but leave the original zone
+    // data there
+    const ElementPtr
+        mapBad(Element::fromJSON("{\"datasources\": ["
+                                 "  {"
+                                 "    \"type\": \"memory\","
+                                 "    \"zones\": ["
+                                 "      {"
+                                 "        \"origin\": \"example.org\","
+                                 "        \"file\": \"" + bad_db + "\","
+                                 "        \"filetype\": \"sqlite3\""
+                                 "      }"
+                                 "    ]"
+                                 "  }"
+                                 "]}"));
+    module_session.setLocalConfig(mapBad);
+    result_ = execAuthServerCommand(server_, "loadzone",
+        Element::fromJSON("{\"origin\": \"example.com\"}"));
+    checkAnswer(1);
+    // The previous zone is not hurt in any way
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+              findZone(Name("example.org")).zone_finder->
+              find(Name("example.org"), RRType::SOA())->code);
+
+    // Broken configuration (not valid against the spec)
+    const ElementPtr
+        broken(Element::fromJSON("{\"datasources\": ["
+                                 "  {"
+                                 "    \"type\": \"memory\","
+                                 "    \"zones\": [[]]"
+                                 "  }"
+                                 "]}"));
+    module_session.setLocalConfig(broken);
+    checkAnswer(1);
+    // The previous zone is not hurt in any way
+    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
+              findZone(Name("example.org")).zone_finder->
+              find(Name("example.org"), RRType::SOA())->code);
+}
+
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadBrokenZone
+#else
+       loadBrokenZone
+#endif
+    )
+{
     configureZones(server_);
 
-    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
+    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
                         "/test1-broken.zone.in "
                         TEST_DATA_BUILDDIR "/test1.zone.copied"));
     result_ = execAuthServerCommand(server_, "loadzone",
@@ -259,11 +424,18 @@ TEST_F(AuthCommandTest, loadBrokenZone) {
     zoneChecks(server_);     // zone shouldn't be replaced
 }
 
-TEST_F(AuthCommandTest, loadUnreadableZone) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadUnreadableZone
+#else
+       loadUnreadableZone
+#endif
+    )
+{
     configureZones(server_);
 
     // install the zone file as unreadable
-    ASSERT_EQ(0, system(INSTALL_PROG " -m 000 " TEST_DATA_DIR
+    ASSERT_EQ(0, system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR
                         "/test1.zone.in "
                         TEST_DATA_BUILDDIR "/test1.zone.copied"));
     result_ = execAuthServerCommand(server_, "loadzone",
@@ -292,7 +464,14 @@ TEST_F(AuthCommandTest, loadSqlite3DataSrc) {
     checkAnswer(0);
 }
 
-TEST_F(AuthCommandTest, loadZoneInvalidParams) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZoneInvalidParams
+#else
+       loadZoneInvalidParams
+#endif
+    )
+{
     configureZones(server_);
 
     // null arg

+ 39 - 13
src/bin/auth/tests/common_unittest.cc

@@ -36,7 +36,7 @@ public:
         BOOST_FOREACH(const Environ &env, restoreEnviron) {
             if (env.second == NULL) {
                 EXPECT_EQ(0, unsetenv(env.first.c_str())) <<
-                    "Couldn't restore environment, results of other tests"
+                    "Couldn't restore environment, results of other tests "
                     "are uncertain";
             } else {
                 EXPECT_EQ(0, setenv(env.first.c_str(), env.second->c_str(),
@@ -60,37 +60,63 @@ protected:
             EXPECT_EQ(0, setenv(name.c_str(), value.c_str(), 1));
         }
     }
-    // Test getXfroutSocketPath under given environment
-    void testXfrout(const string& fromBuild, const string& localStateDir,
-                    const string& socketFile, const string& expected)
+    // Test getter functions for a socket file path under given environment
+    void testSocketPath(const string& fromBuild, const string& localStateDir,
+                        const string& socketFile, const string& env_name,
+                        const string& expected, string (*actual_fn)())
     {
         setEnv("B10_FROM_BUILD", fromBuild);
         setEnv("B10_FROM_SOURCE_LOCALSTATEDIR", localStateDir);
-        setEnv("BIND10_XFROUT_SOCKET_FILE", socketFile);
-        EXPECT_EQ(expected, getXfroutSocketPath());
+        setEnv(env_name, socketFile);
+        EXPECT_EQ(expected, actual_fn());
     }
 };
 
 // Test that when we have no special environment, we get the default from prefix
 TEST_F(Paths, xfroutNoEnv) {
-    testXfrout("", "", "", UNIX_SOCKET_FILE);
+    testSocketPath("", "", "", "BIND10_XFROUT_SOCKET_FILE",
+                   UNIX_XFROUT_SOCKET_FILE, getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsNoEnv) {
+    testSocketPath("", "", "", "BIND10_DDNS_SOCKET_FILE",
+                   UNIX_DDNS_SOCKET_FILE, getDDNSSocketPath);
 }
 
 // Override by B10_FROM_BUILD
 TEST_F(Paths, xfroutFromBuild) {
-    testXfrout("/from/build", "", "/wrong/path",
-               "/from/build/auth_xfrout_conn");
+    testSocketPath("/from/build", "", "/wrong/path",
+                   "BIND10_XFROUT_SOCKET_FILE", "/from/build/auth_xfrout_conn",
+                   getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsFromBuild) {
+    testSocketPath("/from/build", "", "/wrong/path", "BIND10_DDNS_SOCKET_FILE",
+                   "/from/build/ddns_socket", getDDNSSocketPath);
 }
 
 // Override by B10_FROM_SOURCE_LOCALSTATEDIR
 TEST_F(Paths, xfroutLocalStatedir) {
-    testXfrout("/wrong/path", "/state/dir", "/wrong/path",
-               "/state/dir/auth_xfrout_conn");
+    testSocketPath("/wrong/path", "/state/dir", "/wrong/path",
+                   "BIND10_XFROUT_SOCKET_FILE", "/state/dir/auth_xfrout_conn",
+                   getXfroutSocketPath);
 }
 
-// Override by BIND10_XFROUT_SOCKET_FILE explicitly
+TEST_F(Paths, ddnsLocalStatedir) {
+    testSocketPath("/wrong/path", "/state/dir", "/wrong/path",
+                   "BIND10_DDNS_SOCKET_FILE", "/state/dir/ddns_socket",
+                   getDDNSSocketPath);
+}
+
+// Override by BIND10_xxx_SOCKET_FILE explicitly
 TEST_F(Paths, xfroutFromEnv) {
-    testXfrout("", "", "/the/path/to/file", "/the/path/to/file");
+    testSocketPath("", "", "/the/path/to/file", "BIND10_XFROUT_SOCKET_FILE",
+                   "/the/path/to/file", getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsFromEnv) {
+    testSocketPath("", "", "/the/path/to/file", "BIND10_DDNS_SOCKET_FILE",
+                   "/the/path/to/file", getDDNSSocketPath);
 }
 
 }

+ 71 - 0
src/bin/auth/tests/config_syntax_unittest.cc

@@ -0,0 +1,71 @@
+// 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 <cc/data.h>
+#include <config/module_spec.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+const char* const SPEC_FILE = AUTH_OBJ_DIR "/auth.spec";
+
+class AuthConfigSyntaxTest : public ::testing::Test {
+protected:
+    AuthConfigSyntaxTest() : mspec_(moduleSpecFromFile(SPEC_FILE))
+    {}
+    ModuleSpec mspec_;
+};
+
+TEST_F(AuthConfigSyntaxTest, inmemoryDefaultFileType) {
+    // filetype is optional
+    EXPECT_TRUE(
+        mspec_.validateConfig(
+            Element::fromJSON(
+                "{\"listen_on\": [], \"datasources\": "
+                "  [{\"type\": \"memory\", \"class\": \"IN\", "
+                "    \"zones\": [{\"origin\": \"example.com\","
+                "                 \"file\": \""
+                TEST_DATA_DIR "/example.zone\"}]}]}"), true));
+}
+
+TEST_F(AuthConfigSyntaxTest, inmemorySQLite3Backend) {
+    // Specifying non-default in-memory filetype
+    EXPECT_TRUE(
+        mspec_.validateConfig(
+            Element::fromJSON(
+                "{\"datasources\": "
+                "  [{\"type\": \"memory\","
+                "    \"zones\": [{\"origin\": \"example.com\","
+                "                 \"file\": \""
+                TEST_DATA_DIR "/example.zone\","
+                "                 \"filetype\": \"sqlite3\"}]}]}"), false));
+}
+
+TEST_F(AuthConfigSyntaxTest, badInmemoryFileType) {
+    // filetype must be a string
+    EXPECT_FALSE(
+        mspec_.validateConfig(
+            Element::fromJSON(
+                "{\"datasources\": "
+                "  [{\"type\": \"memory\","
+                "    \"zones\": [{\"origin\": \"example.com\","
+                "                 \"file\": \""
+                TEST_DATA_DIR "/example.zone\","
+                "                 \"filetype\": 42}]}]}"), false));
+}
+}

+ 154 - 24
src/bin/auth/tests/config_unittest.cc

@@ -21,6 +21,7 @@
 
 #include <cc/data.h>
 
+#include <datasrc/data_source.h>
 #include <datasrc/memory_datasrc.h>
 
 #include <xfr/xfrout_client.h>
@@ -29,14 +30,22 @@
 #include <auth/auth_config.h>
 #include <auth/common.h>
 
+#include "datasrc_util.h"
+
+#include <util/unittests/mock_socketsession.h>
 #include <testutils/mockups.h>
 #include <testutils/portconfig.h>
 #include <testutils/socket_request.h>
 
+#include <sstream>
+
+using namespace std;
 using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::asiodns;
+using namespace isc::auth::unittest;
+using namespace isc::util::unittests;
 using namespace isc::testutils;
 
 namespace {
@@ -45,7 +54,7 @@ protected:
     AuthConfigTest() :
         dnss_(),
         rrclass(RRClass::IN()),
-        server(true, xfrout),
+        server(true, xfrout, ddns_forwarder),
         // The empty string is expected value of the parameter of
         // requestSocket, not the app_name (there's no fallback, it checks
         // the empty string is passed).
@@ -56,19 +65,27 @@ protected:
     MockDNSService dnss_;
     const RRClass rrclass;
     MockXfroutClient xfrout;
+    MockSocketSessionForwarder ddns_forwarder;
     AuthSrv server;
     isc::server_common::portconfig::AddressList address_store_;
 private:
     isc::testutils::TestSocketRequestor sock_requestor_;
 };
 
-TEST_F(AuthConfigTest, datasourceConfig) {
+TEST_F(AuthConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_datasourceConfig
+#else
+       datasourceConfig
+#endif
+    )
+{
     // By default, we don't have any in-memory data source.
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     configureAuthServer(server, Element::fromJSON(
                             "{\"datasources\": [{\"type\": \"memory\"}]}"));
     // after successful configuration, we should have one (with empty zoneset).
-    ASSERT_NE(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_TRUE(server.hasInMemoryClient());
     EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
@@ -89,7 +106,7 @@ TEST_F(AuthConfigTest, versionConfig) {
 }
 
 TEST_F(AuthConfigTest, exceptionGuarantee) {
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     // This configuration contains an invalid item, which will trigger
     // an exception.
     EXPECT_THROW(configureAuthServer(
@@ -99,7 +116,7 @@ TEST_F(AuthConfigTest, exceptionGuarantee) {
                          " \"no_such_config_var\": 1}")),
                  AuthConfigError);
     // The server state shouldn't change
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
 }
 
 TEST_F(AuthConfigTest, exceptionConversion) {
@@ -169,25 +186,46 @@ protected:
 TEST_F(MemoryDatasrcConfigTest, addZeroDataSrc) {
     parser->build(Element::fromJSON("[]"));
     parser->commit();
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
 }
 
-TEST_F(MemoryDatasrcConfigTest, addEmpty) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addEmpty
+#else
+       addEmpty
+#endif
+    )
+{
     // By default, we don't have any in-memory data source.
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
     parser->build(Element::fromJSON("[{\"type\": \"memory\"}]"));
     parser->commit();
     EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
-TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addZeroZone
+#else
+       addZeroZone
+#endif
+    )
+{
     parser->build(Element::fromJSON("[{\"type\": \"memory\","
                                     "  \"zones\": []}]"));
     parser->commit();
     EXPECT_EQ(0, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
-TEST_F(MemoryDatasrcConfigTest, addOneZone) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addOneZone
+#else
+       addOneZone
+#endif
+    )
+{
     EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
@@ -201,7 +239,70 @@ TEST_F(MemoryDatasrcConfigTest, addOneZone) {
         RRType::A())->code);
 }
 
-TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
+// This test uses dynamic load of a data source module, and won't work when
+// statically linked.
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addOneWithFiletypeSQLite3
+#else
+       addOneWithFiletypeSQLite3
+#endif
+    )
+{
+    const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
+    stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+    createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
+
+    // In-memory with an SQLite3 data source as the backend.
+    parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.org\","
+                      "               \"file\": \""
+                      + test_db +  "\","
+                      "               \"filetype\": \"sqlite3\"}]}]"));
+    parser->commit();
+    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
+
+    // Failure case: the specified zone doesn't exist in the DB file.
+    delete parser;
+    parser = createAuthConfigParser(server, "datasources");
+    EXPECT_THROW(parser->build(
+                     Element::fromJSON(
+                         "[{\"type\": \"memory\","
+                         "  \"zones\": [{\"origin\": \"example.com\","
+                         "               \"file\": \""
+                         + test_db +  "\","
+                         "               \"filetype\": \"sqlite3\"}]}]")),
+                 DataSourceError);
+}
+
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addOneWithFiletypeText
+#else
+       addOneWithFiletypeText
+#endif
+    )
+{
+    // Explicitly specifying "text" is okay.
+    parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.com\","
+                      "               \"file\": \""
+                      TEST_DATA_DIR "/example.zone\","
+                      "               \"filetype\": \"text\"}]}]"));
+    parser->commit();
+    EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_addMultiZones
+#else
+       addMultiZones
+#endif
+    )
+{
     EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
@@ -217,7 +318,14 @@ TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
     EXPECT_EQ(3, server.getInMemoryClient(rrclass)->getZoneCount());
 }
 
-TEST_F(MemoryDatasrcConfigTest, replace) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_replace
+#else
+       replace
+#endif
+    )
+{
     EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
@@ -248,7 +356,14 @@ TEST_F(MemoryDatasrcConfigTest, replace) {
                   Name("example.com")).code);
 }
 
-TEST_F(MemoryDatasrcConfigTest, exception) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_exception
+#else
+       exception
+#endif
+    )
+{
     // Load a zone
     EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
@@ -272,7 +387,8 @@ TEST_F(MemoryDatasrcConfigTest, exception) {
                       "/example.org.zone\"},"
                       "              {\"origin\": \"example.net\","
                       "               \"file\": \"" TEST_DATA_DIR
-                      "/nonexistent.zone\"}]}]")), isc::dns::MasterLoadError);
+                      "/nonexistent.zone\"}]}]")),
+                 isc::datasrc::DataSourceError);
     // As that one throwed exception, it is not expected from us to
     // commit it
 
@@ -283,7 +399,14 @@ TEST_F(MemoryDatasrcConfigTest, exception) {
                   Name("example.com")).code);
 }
 
-TEST_F(MemoryDatasrcConfigTest, remove) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_remove
+#else
+       remove
+#endif
+    )
+{
     EXPECT_NO_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example.com\","
@@ -296,7 +419,7 @@ TEST_F(MemoryDatasrcConfigTest, remove) {
     parser = createAuthConfigParser(server, "datasources"); 
     EXPECT_NO_THROW(parser->build(Element::fromJSON("[]")));
     EXPECT_NO_THROW(parser->commit());
-    EXPECT_EQ(AuthSrv::InMemoryClientPtr(), server.getInMemoryClient(rrclass));
+    EXPECT_FALSE(server.hasInMemoryClient());
 }
 
 TEST_F(MemoryDatasrcConfigTest, addDuplicateZones) {
@@ -309,7 +432,7 @@ TEST_F(MemoryDatasrcConfigTest, addDuplicateZones) {
                          "              {\"origin\": \"example.com\","
                          "               \"file\": \"" TEST_DATA_DIR
                          "/example.com.zone\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 }
 
 TEST_F(MemoryDatasrcConfigTest, addBadZone) {
@@ -318,35 +441,35 @@ TEST_F(MemoryDatasrcConfigTest, addBadZone) {
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // origin is missing
     EXPECT_THROW(parser->build(
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"file\": \"example.zone\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // file is missing
     EXPECT_THROW(parser->build(
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"origin\": \"example.com\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // missing zone file
     EXPECT_THROW(parser->build(
                      Element::fromJSON(
                          "[{\"type\": \"memory\","
                          "  \"zones\": [{\"origin\": \"example.com\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // bogus origin name
     EXPECT_THROW(parser->build(Element::fromJSON(
                       "[{\"type\": \"memory\","
                       "  \"zones\": [{\"origin\": \"example..com\","
                       "               \"file\": \"example.zone\"}]}]")),
-                 AuthConfigError);
+                 DataSourceError);
 
     // bogus RR class name
     EXPECT_THROW(parser->build(
@@ -367,7 +490,14 @@ TEST_F(MemoryDatasrcConfigTest, addBadZone) {
                  isc::InvalidParameter);
 }
 
-TEST_F(MemoryDatasrcConfigTest, badDatasrcType) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_badDatasrcType
+#else
+       badDatasrcType
+#endif
+    )
+{
     EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": \"badsrc\"}]")),
                  AuthConfigError);
     EXPECT_THROW(parser->build(Element::fromJSON("[{\"notype\": \"memory\"}]")),

+ 77 - 0
src/bin/auth/tests/datasrc_util.cc

@@ -0,0 +1,77 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/client.h>
+#include <datasrc/zone.h>
+#include <datasrc/factory.h>
+
+#include "datasrc_util.h"
+
+#include <boost/bind.hpp>
+
+#include <istream>
+
+#include <cstdlib>
+
+using namespace std;
+
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+
+namespace isc {
+namespace auth {
+namespace unittest {
+
+namespace {
+void
+addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
+    updater->addRRset(*rrset);
+}
+}
+
+void
+createSQLite3DB(RRClass zclass, const Name& zname,
+                const char* const db_file, istream& rr_stream)
+{
+    // We always begin with an empty template SQLite3 DB file and install
+    // the zone data from the zone file.
+    const char* const install_cmd_prefix = INSTALL_PROG " -c " TEST_DATA_DIR
+        "/rwtest.sqlite3 ";
+    const string install_cmd = string(install_cmd_prefix) + db_file;
+    if (system(install_cmd.c_str()) != 0) {
+        isc_throw(isc::Unexpected,
+                  "Error setting up; command failed: " << install_cmd);
+    }
+
+    DataSourceClientContainer container("sqlite3",
+                                        Element::fromJSON(
+                                            "{\"database_file\": \"" +
+                                            string(db_file) + "\"}"));
+    ZoneUpdaterPtr updater = container.getInstance().getUpdater(zname, true);
+    masterLoad(rr_stream, zname, zclass, boost::bind(addRRset, updater, _1));
+    updater->commit();
+}
+
+} // end of unittest
+} // end of auth
+} // end of isc

+ 58 - 0
src/bin/auth/tests/datasrc_util.h

@@ -0,0 +1,58 @@
+// 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 __AUTH_DATA_SOURCE_UTIL_H
+#define __AUTH_DATA_SOURCE_UTIL_H 1
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <istream>
+
+namespace isc {
+namespace auth {
+namespace unittest {
+
+/// \brief Create an SQLite3 database file for a given zone from a stream.
+///
+/// This function creates an SQLite3 DB file for the specified zone
+/// with specified content.  The zone will be created in the given
+/// SQLite3 database file.  The database file does not have to exist;
+/// this function will automatically create a new file for the test
+/// based on a template that only contains the necessary schema. If
+/// the given file already exists this function overrides the content
+/// (so basically the file must be an ephemeral one only for that test
+/// case).
+///
+/// The input stream must produce strings as the corresponding
+/// \c dns::masterLoad() function expects.
+///
+/// \param zclass The RR class of the zone
+/// \param zname The origin name of the zone
+/// \param db_file The SQLite3 data base file in which the zone data should be
+/// installed.
+/// \param rr_stream An input stream that produces zone data.
+void
+createSQLite3DB(dns::RRClass zclass, const dns::Name& zname,
+                const char* const db_file, std::istream& rr_stream);
+
+} // end of unittest
+} // end of auth
+} // end of isc
+
+#endif  // __AUTH_DATA_SOURCE_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:

BIN
src/bin/auth/tests/testdata/example.sqlite3


File diff suppressed because it is too large
+ 10 - 63
src/bin/bind10/bind10.8


+ 21 - 21
src/bin/bind10/bind10.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>March 1, 2012</date>
+    <date>April 12, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -52,6 +52,7 @@
       <arg><option>-u <replaceable>user</replaceable></option></arg>
       <arg><option>-v</option></arg>
       <arg><option>-w <replaceable>wait_time</replaceable></option></arg>
+      <arg><option>--clear-config</option></arg>
       <arg><option>--cmdctl-port</option> <replaceable>port</replaceable></arg>
       <arg><option>--config-file</option> <replaceable>config-filename</replaceable></arg>
       <arg><option>--data-path</option> <replaceable>directory</replaceable></arg>
@@ -106,6 +107,25 @@
 
       <varlistentry>
         <term>
+          <option>--clear-config</option>
+        </term>
+        <listitem>
+	  <para>
+	    This will create a backup of the existing configuration
+	    file, remove it and start
+	    <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+            with the default configuration.
+	    The name of the backup file can be found in the logs
+	    (<varname>CFGMGR_BACKED_UP_CONFIG_FILE</varname>).
+	    (It will append a number to the backup filename if a
+	    previous backup file exists.)
+
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
           <option>--cmdctl-port</option> <replaceable>port</replaceable>
         </term>
         <listitem>
@@ -244,10 +264,6 @@ TODO: configuration section
     <itemizedlist>
 
       <listitem>
-        <para> <varname>/Boss/components/b10-auth</varname> </para>
-      </listitem>
-
-      <listitem>
         <para> <varname>/Boss/components/b10-cmdctl</varname> </para>
       </listitem>
 
@@ -255,22 +271,6 @@ TODO: configuration section
         <para> <varname>/Boss/components/b10-stats</varname> </para>
       </listitem>
 
-      <listitem>
-        <para> <varname>/Boss/components/b10-stats-httpd</varname> </para>
-      </listitem>
-
-      <listitem>
-        <para> <varname>/Boss/components/b10-xfrin</varname> </para>
-      </listitem>
-
-      <listitem>
-        <para> <varname>/Boss/components/b10-xfrout</varname> </para>
-      </listitem>
-
-      <listitem>
-        <para> <varname>/Boss/components/b10-zonemgr</varname> </para>
-      </listitem>
-
     </itemizedlist>
 
     <para>

+ 4 - 5
src/bin/bind10/bind10_messages.mes

@@ -20,10 +20,6 @@ The boss process is starting up and will now check if the message bus
 daemon is already running. If so, it will not be able to start, as it
 needs a dedicated message bus.
 
-% BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the boss module specified
-statistics data which is invalid for the boss specification file.
-
 % BIND10_COMPONENT_FAILED component %1 (pid %2) failed: %3
 The process terminated, but the bind10 boss didn't expect it to, which means
 it must have failed.
@@ -86,6 +82,10 @@ the boss process will try to force them).
 A debug message. The configurator is about to perform one task of the plan it
 is currently executing on the named component.
 
+% BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified
+An error was encountered when the boss module specified
+statistics data which is invalid for the boss specification file.
+
 % BIND10_INVALID_USER invalid user: %1
 The boss process was started with the -u option, to drop root privileges
 and continue running as the specified user, but the user is unknown.
@@ -290,4 +290,3 @@ the configuration manager to start up.  The total length of time Boss
 will wait for the configuration manager before reporting an error is
 set with the command line --wait switch, which has a default value of
 ten seconds.
-

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

@@ -64,6 +64,7 @@ import posix
 import copy
 
 from bind10_config import LIBEXECPATH
+import bind10_config
 import isc.cc
 import isc.util.process
 import isc.net.parse
@@ -1122,6 +1123,28 @@ def unlink_pid_file(pid_file):
         if error.errno is not errno.ENOENT:
             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():
     global options
@@ -1201,6 +1224,7 @@ def main():
     finally:
         # Clean up the filesystem
         unlink_pid_file(options.pid_file)
+        remove_lock_files()
         if boss_of_bind is not None:
             boss_of_bind.remove_socket_srv()
     sys.exit(boss_of_bind.exitcode)

+ 0 - 8
src/bin/bind10/bob.spec

@@ -8,15 +8,7 @@
         "item_type": "named_set",
         "item_optional": false,
         "item_default": {
-          "b10-auth": { "special": "auth", "kind": "needed" },
-          "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
-          "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
-          "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
           "b10-stats": { "address": "Stats", "kind": "dispensable" },
-          "b10-stats-httpd": {
-            "address": "StatsHttpd",
-            "kind": "dispensable"
-          },
           "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
         },
         "named_set_item_spec": {

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

@@ -23,6 +23,7 @@ endif
 	chmod +x $(abs_builddir)/$$pytest ; \
 	$(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 \
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 		$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done

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

@@ -1475,6 +1475,41 @@ class SocketSrvTest(unittest.TestCase):
         self.assertEqual({}, self.__boss._unix_sockets)
         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__':
     # store os.environ for test_unchanged_environment
     original_os_environ = copy.deepcopy(os.environ)

+ 1 - 1
src/bin/bindctl/Makefile.am

@@ -8,7 +8,7 @@ EXTRA_DIST = $(man_MANS) bindctl.xml
 noinst_SCRIPTS = run_bindctl.sh
 
 python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py \
-		mycollections.py
+		mycollections.py command_sets.py
 pythondir = $(pyexecdir)/bindctl
 
 bindctldir = $(pkgdatadir)

+ 94 - 12
src/bin/bindctl/bindcmd.py

@@ -23,6 +23,7 @@ from cmd import Cmd
 from bindctl.exception import *
 from bindctl.moduleinfo import *
 from bindctl.cmdparse import BindCmdParse
+from bindctl import command_sets
 from xml.dom import minidom
 import isc
 import isc.cc.data
@@ -37,6 +38,7 @@ from hashlib import sha1
 import csv
 import pwd
 import getpass
+import copy
 
 try:
     from collections import OrderedDict
@@ -393,8 +395,9 @@ class BindCmdInterpreter(Cmd):
                 param_nr += 1
 
         # Convert parameter value according parameter spec file.
-        # Ignore check for commands belongs to module 'config'
-        if cmd.module != CONFIG_MODULE_NAME:
+        # Ignore check for commands belongs to module 'config' or 'execute
+        if cmd.module != CONFIG_MODULE_NAME and\
+           cmd.module != command_sets.EXECUTE_MODULE_NAME:
             for param_name in cmd.params:
                 param_spec = command_info.get_param_with_name(param_name).param_spec
                 try:
@@ -408,16 +411,9 @@ class BindCmdInterpreter(Cmd):
         if cmd.command == "help" or ("help" in cmd.params.keys()):
             self._handle_help(cmd)
         elif cmd.module == CONFIG_MODULE_NAME:
-            try:
-                self.apply_config_cmd(cmd)
-            except isc.cc.data.DataTypeError as dte:
-                print("Error: " + str(dte))
-            except isc.cc.data.DataNotFoundError as dnfe:
-                print("Error: " + str(dnfe))
-            except isc.cc.data.DataAlreadyPresentError as dape:
-                print("Error: " + str(dape))
-            except KeyError as ke:
-                print("Error: missing " + str(ke))
+            self.apply_config_cmd(cmd)
+        elif cmd.module == command_sets.EXECUTE_MODULE_NAME:
+            self.apply_execute_cmd(cmd)
         else:
             self.apply_cmd(cmd)
 
@@ -576,6 +572,14 @@ class BindCmdInterpreter(Cmd):
             self._print_correct_usage(err)
         except isc.cc.data.DataTypeError as err:
             print("Error! ", err)
+        except isc.cc.data.DataTypeError as dte:
+            print("Error: " + str(dte))
+        except isc.cc.data.DataNotFoundError as dnfe:
+            print("Error: " + str(dnfe))
+        except isc.cc.data.DataAlreadyPresentError as dape:
+            print("Error: " + str(dape))
+        except KeyError as ke:
+            print("Error: missing " + str(ke))
 
     def _print_correct_usage(self, ept):
         if isinstance(ept, CmdUnknownModuleSyntaxError):
@@ -728,6 +732,84 @@ class BindCmdInterpreter(Cmd):
 
         self.location = new_location
 
+    def apply_execute_cmd(self, command):
+        '''Handles the 'execute' command, which executes a number of
+           (preset) statements. The command set to execute is either
+           read from a file (e.g. 'execute file <file>'.) or one
+           of the sets as defined in command_sets.py'''
+        if command.command == 'file':
+            try:
+                with open(command.params['filename']) as command_file:
+                    commands = command_file.readlines()
+            except IOError as ioe:
+                print("Error: " + str(ioe))
+                return
+        elif command_sets.has_command_set(command.command):
+            commands = command_sets.get_commands(command.command)
+        else:
+            # Should not be reachable; parser should've caught this
+            raise Exception("Unknown execute command type " + command.command)
+
+        # We have our set of commands now, depending on whether 'show' was
+        # specified, show or execute them
+        if 'show' in command.params and command.params['show'] == 'show':
+            self.__show_execute_commands(commands)
+        else:
+            self.__apply_execute_commands(commands)
+
+    def __show_execute_commands(self, commands):
+        '''Prints the command list without executing them'''
+        for line in commands:
+            print(line.strip())
+
+    def __apply_execute_commands(self, commands):
+        '''Applies the configuration commands from the given iterator.
+           This is the method that catches, comments, echo statements, and
+           other directives. All commands not filtered by this method are
+           interpreted as if they are directly entered in an active session.
+           Lines starting with any of the following characters are not
+           passed directly:
+           # - These are comments
+           ! - These are directives
+               !echo: print the rest of the line
+               !verbose on/off: print the commands themselves too
+               Unknown directives are ignored (with a warning)
+           The execution is stopped if there are any errors.
+        '''
+        verbose = False
+        try:
+            for line in commands:
+                line = line.strip()
+                if verbose:
+                    print(line)
+                if line.startswith('#') or len(line) == 0:
+                    continue
+                elif line.startswith('!'):
+                    if re.match('^!echo ', line, re.I) and len(line) > 6:
+                        print(line[6:])
+                    elif re.match('^!verbose\s+on\s*$', line, re.I):
+                        verbose = True
+                    elif re.match('^!verbose\s+off$', line, re.I):
+                        verbose = False
+                    else:
+                        print("Warning: ignoring unknown directive: " + line)
+                else:
+                    cmd = BindCmdParse(line)
+                    self._validate_cmd(cmd)
+                    self._handle_cmd(cmd)
+        except (isc.config.ModuleCCSessionError,
+                IOError, http.client.HTTPException,
+                BindCtlException, isc.cc.data.DataTypeError,
+                isc.cc.data.DataNotFoundError,
+                isc.cc.data.DataAlreadyPresentError,
+                KeyError) as err:
+            print('Error: ', err)
+            print()
+            print('Depending on the contents of the script, and which')
+            print('commands it has called, there can be committed and')
+            print('local changes. It is advised to check your settings,')
+            print('and revert local changes with "config revert".')
+
     def apply_cmd(self, cmd):
         '''Handles a general module command'''
         url = '/' + cmd.module + '/' + cmd.command

+ 2 - 0
src/bin/bindctl/bindctl_main.py.in

@@ -22,6 +22,7 @@ import sys; sys.path.append ('@@PYTHONPATH@@')
 
 from bindctl.moduleinfo import *
 from bindctl.bindcmd import *
+from bindctl import command_sets
 import pprint
 from optparse import OptionParser, OptionValueError
 import isc.util.process
@@ -146,5 +147,6 @@ if __name__ == '__main__':
     tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain,
                               csv_file_dir=options.csv_file_dir)
     prepare_config_commands(tool)
+    command_sets.prepare_execute_commands(tool)
     result = tool.run()
     sys.exit(result)

+ 94 - 0
src/bin/bindctl/command_sets.py

@@ -0,0 +1,94 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This file provides a built-in set of 'execute' commands, for common
+# functions, such as adding an initial auth server.
+# By calling the function prepare_execute_commands, the
+# commands in the command_sets map are added to the virtual
+# component called 'execute'. This is done in bindctl_main.
+
+from bindctl.moduleinfo import *
+# The name of the 'virtual' command set execution module in bindctl
+EXECUTE_MODULE_NAME = 'execute'
+
+# This is a map of command names to lists
+# Each element in the set should itself be a dict containing:
+# 'description': A string with a description of the command set
+# 'commands': A list of bindctl commands
+command_sets = {
+    'init_authoritative_server': {
+        'description':
+            'Configure and run a basic Authoritative server, with default '+
+            'SQLite3 backend, and xfrin and xfrout functionality',
+        'commands':
+            [
+            '!echo adding Authoritative server component',
+            'config add /Boss/components b10-auth',
+            'config set /Boss/components/b10-auth/kind needed',
+            'config set /Boss/components/b10-auth/special auth',
+            '!echo adding Xfrin component',
+            'config add /Boss/components b10-xfrin',
+            'config set /Boss/components/b10-xfrin/address Xfrin',
+            'config set /Boss/components/b10-xfrin/kind dispensable',
+            '!echo adding Xfrout component',
+            'config add /Boss/components b10-xfrout',
+            'config set /Boss/components/b10-xfrout/address Xfrout',
+            'config set /Boss/components/b10-xfrout/kind dispensable',
+            '!echo adding Zone Manager component',
+            'config add /Boss/components b10-zonemgr',
+            'config set /Boss/components/b10-zonemgr/address Zonemgr',
+            'config set /Boss/components/b10-zonemgr/kind dispensable',
+            '!echo Components added. Please enter "config commit" to',
+            '!echo finalize initial setup and run the components.'
+            ]
+    }
+}
+
+def has_command_set(name):
+    return name in command_sets
+
+def get_commands(name):
+    return command_sets[name]['commands']
+
+def get_description(name):
+    return command_sets[name]['description']
+
+# For each
+def prepare_execute_commands(tool):
+    """This function is called by bindctl_main, and sets up the commands
+       defined here for use in bindctl."""
+    # common parameter
+    param_show = ParamInfo(name="show", type="string", optional=True,
+        desc="Show the list of commands without executing them")
+
+    # The command module
+    module = ModuleInfo(name=EXECUTE_MODULE_NAME,
+                        desc="Execute a given set of commands")
+
+    # Command to execute a file
+    cmd = CommandInfo(name="file", desc="Read commands from file")
+    param = ParamInfo(name="filename", type="string", optional=False,
+                      desc="File to read the set of commands from.")
+    cmd.add_param(param)
+    cmd.add_param(param_show)
+    module.add_command(cmd)
+
+    # and loop through all command sets defined above
+    for name in command_sets:
+        cmd = CommandInfo(name=name, desc=get_description(name))
+        cmd.add_param(param_show)
+        module.add_command(cmd)
+
+    tool.add_module_info(module)

+ 12 - 5
src/bin/cfgmgr/b10-cfgmgr.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-cfgmgr
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: March 10, 2010
+.\"      Date: April 12, 2010
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"
-.TH "B10\-CFGMGR" "8" "March 10, 2010" "BIND10" "BIND10"
+.TH "B10\-CFGMGR" "8" "April 12, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
 b10-cfgmgr \- Configuration manager
 .SH "SYNOPSIS"
 .HP \w'\fBb10\-cfgmgr\fR\ 'u
-\fBb10\-cfgmgr\fR [\fB\-c\fR\fB\fIconfig\-filename\fR\fR] [\fB\-p\fR\fB\fIdata_path\fR\fR]
+\fBb10\-cfgmgr\fR [\fB\-c\ \fR\fB\fIconfig\-filename\fR\fR] [\fB\-p\ \fR\fB\fIdata_path\fR\fR] [\fB\-\-clear\-config\fR] [\fB\-\-config\-filename\ \fR\fB\fIconfig\-filename\fR\fR] [\fB\-\-data\-path\ \fR\fB\fIdata_path\fR\fR]
 .SH "DESCRIPTION"
 .PP
 The
@@ -50,14 +50,21 @@ When it exits, it saves its current configuration to
 .PP
 The arguments are as follows:
 .PP
-\fB\-c\fR\fIconfig\-filename\fR, \fB\-\-config\-filename\fR \fIconfig\-filename\fR
+\fB\-\-clear\-config\fR
+.RS 4
+This will create a backup of the existing configuration file, remove it, and
+b10\-cfgmgr(8)
+will use the default configurations\&. The name of the backup file can be found in the logs (\fICFGMGR_BACKED_UP_CONFIG_FILE\fR)\&. (It will append a number to the backup filename if a previous backup file exists\&.)
+.RE
+.PP
+\fB\-c\fR \fIconfig\-filename\fR, \fB\-\-config\-filename\fR \fIconfig\-filename\fR
 .RS 4
 The configuration database filename to use\&. Can be either absolute or relative to data path\&.
 .sp
 Defaults to b10\-config\&.db
 .RE
 .PP
-\fB\-p\fR\fIdata\-path\fR, \fB\-\-data\-path\fR \fIdata\-path\fR
+\fB\-p\fR \fIdata\-path\fR, \fB\-\-data\-path\fR \fIdata\-path\fR
 .RS 4
 The path where BIND 10 looks for files\&. The configuration file is looked for here, if it is relative\&. If it is absolute, the path is ignored\&.
 .RE

+ 29 - 4
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -44,11 +44,11 @@ def parse_options(args=sys.argv[1:], Parser=OptionParser):
     parser = Parser()
     parser.add_option("-p", "--data-path", dest="data_path",
                       help="Directory to search for configuration files " +
-                      "(default=" + DATA_PATH + ")", default=DATA_PATH)
+                      "(default=" + DATA_PATH + ")", default=None)
     parser.add_option("-c", "--config-filename", dest="config_file",
                       help="Configuration database filename " +
                       "(default=" + DEFAULT_CONFIG_FILE + ")",
-                      default=DEFAULT_CONFIG_FILE)
+                      default=None)
     parser.add_option("--clear-config", action="store_true",
                       dest="clear_config", default=False,
                       help="Back up the configuration file and start with " +
@@ -85,12 +85,37 @@ def load_plugins(path, cm):
         # Restore the search path
         sys.path = sys.path[1:]
 
+
+def determine_path_and_file(data_path_option, config_file_option):
+    """Given the data path and config file as specified on the command line
+       (or not specified, as may be the case), determine the full path and
+       file to use when starting the config manager;
+       - if neither are given, use defaults
+       - if both are given, use both
+       - if only data path is given, use default file in that path
+       - if only file is given, use cwd() + file (if file happens to
+         be an absolute file name, path will be ignored)
+       Arguments are either a string, or None.
+       Returns a tuple containing (result_path, result_file).
+    """
+    data_path = data_path_option
+    config_file = config_file_option
+    if config_file is None:
+        config_file = DEFAULT_CONFIG_FILE
+        if data_path is None:
+            data_path = DATA_PATH
+    else:
+        if data_path is None:
+            data_path = os.getcwd()
+    return (data_path, config_file)
+
 def main():
     options = parse_options()
     global cm
     try:
-        cm = ConfigManager(options.data_path, options.config_file,
-                           None, options.clear_config)
+        (data_path, config_file) = determine_path_and_file(options.data_path,
+                                                           options.config_file)
+        cm = ConfigManager(data_path, config_file, None, options.clear_config)
         signal.signal(signal.SIGINT, signal_handler)
         signal.signal(signal.SIGTERM, signal_handler)
         cm.read_config()

+ 26 - 5
src/bin/cfgmgr/b10-cfgmgr.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>March 10, 2010</date>
+    <date>April 12, 2010</date>
   </refentryinfo>
 
   <refmeta>
@@ -44,8 +44,11 @@
   <refsynopsisdiv>
     <cmdsynopsis>
       <command>b10-cfgmgr</command>
-      <arg><option>-c<replaceable>config-filename</replaceable></option></arg>
-      <arg><option>-p<replaceable>data_path</replaceable></option></arg>
+      <arg><option>-c <replaceable>config-filename</replaceable></option></arg>
+      <arg><option>-p <replaceable>data_path</replaceable></option></arg>
+      <arg><option>--clear-config</option></arg>
+      <arg><option>--config-filename <replaceable>config-filename</replaceable></option></arg>
+      <arg><option>--data-path <replaceable>data_path</replaceable></option></arg>
     </cmdsynopsis>
   </refsynopsisdiv>
 
@@ -95,7 +98,25 @@
     <variablelist>
       <varlistentry>
         <term>
-          <option>-c</option><replaceable>config-filename</replaceable>,
+          <option>--clear-config</option>
+        </term>
+        <listitem>
+          <para>
+            This will create a backup of the existing configuration
+            file, remove it, and
+            <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+            will use the default configurations.
+            The name of the backup file can be found in the logs
+            (<varname>CFGMGR_BACKED_UP_CONFIG_FILE</varname>).
+            (It will append a number to the backup filename if a
+            previous backup file exists.)
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>-c</option> <replaceable>config-filename</replaceable>,
           <option>--config-filename</option> <replaceable>config-filename</replaceable>
         </term>
         <listitem>
@@ -107,7 +128,7 @@
 
       <varlistentry>
         <term>
-          <option>-p</option><replaceable>data-path</replaceable>,
+          <option>-p</option> <replaceable>data-path</replaceable>,
           <option>--data-path</option> <replaceable>data-path</replaceable>
         </term>
         <listitem>

+ 17 - 7
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in

@@ -141,8 +141,8 @@ class TestParseArgs(unittest.TestCase):
         # Pass it empty array, not our arguments
         b = __import__("b10-cfgmgr")
         parsed = b.parse_options([], TestOptParser)
-        self.assertEqual(b.DATA_PATH, parsed.data_path)
-        self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+        self.assertEqual(None, parsed.data_path)
+        self.assertEqual(None, parsed.config_file)
 
     def test_wrong_args(self):
         """
@@ -168,10 +168,10 @@ class TestParseArgs(unittest.TestCase):
         b = __import__("b10-cfgmgr")
         parsed = b.parse_options(['--data-path=/path'], TestOptParser)
         self.assertEqual('/path', parsed.data_path)
-        self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+        self.assertEqual(None, parsed.config_file)
         parsed = b.parse_options(['-p', '/path'], TestOptParser)
         self.assertEqual('/path', parsed.data_path)
-        self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+        self.assertEqual(None, parsed.config_file)
         self.assertRaises(OptsError, b.parse_options, ['-p'], TestOptParser)
         self.assertRaises(OptsError, b.parse_options, ['--data-path'],
                           TestOptParser)
@@ -183,22 +183,32 @@ class TestParseArgs(unittest.TestCase):
         b = __import__("b10-cfgmgr")
         parsed = b.parse_options(['--config-filename=filename'],
                                  TestOptParser)
-        self.assertEqual(b.DATA_PATH, parsed.data_path)
+        self.assertEqual(None, parsed.data_path)
         self.assertEqual("filename", parsed.config_file)
         parsed = b.parse_options(['-c', 'filename'], TestOptParser)
-        self.assertEqual(b.DATA_PATH, parsed.data_path)
+        self.assertEqual(None, parsed.data_path)
         self.assertEqual("filename", parsed.config_file)
         self.assertRaises(OptsError, b.parse_options, ['-c'], TestOptParser)
         self.assertRaises(OptsError, b.parse_options, ['--config-filename'],
                           TestOptParser)
 
+    def test_determine_path_and_file(self):
+        b = __import__("b10-cfgmgr")
+        self.assertEqual((b.DATA_PATH, b.DEFAULT_CONFIG_FILE),
+                         b.determine_path_and_file(None, None))
+        self.assertEqual(("/foo", b.DEFAULT_CONFIG_FILE),
+                         b.determine_path_and_file("/foo", None))
+        self.assertEqual((os.getcwd(), "file.config"),
+                         b.determine_path_and_file(None, "file.config"))
+        self.assertEqual(("/foo", "bar"),
+                         b.determine_path_and_file("/foo", "bar"))
+
     def test_clear_config(self):
         b = __import__("b10-cfgmgr")
         parsed = b.parse_options([], TestOptParser)
         self.assertFalse(parsed.clear_config)
         parsed = b.parse_options(['--clear-config'], TestOptParser)
         self.assertTrue(parsed.clear_config)
-        
 
 if __name__ == '__main__':
     unittest.main()

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

@@ -22,5 +22,6 @@ endif
 	PYTHONPATH=$(COMMON_PYTHON_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 \
+	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

+ 2 - 5
src/bin/dbutil/dbutil.py.in

@@ -196,7 +196,7 @@ UPGRADES = [
     }
 
 # To extend this, leave the above statements in place and add another
-# dictionary to the list.  The "from" version should be (2, 0), the "to" 
+# dictionary to the list.  The "from" version should be (2, 0), the "to"
 # version whatever the version the update is to, and the SQL statements are
 # the statements required to perform the upgrade.  This way, the upgrade
 # program will be able to upgrade both a V1.0 and a V2.0 database.
@@ -378,10 +378,7 @@ def get_latest_version():
 
     This is the 'to' version held in the last element of the upgrades list
     """
-    # Temporarily hardcoded to return 1 as the schema version, until
-    # #324 is merged.
-    #return UPGRADES[-1]['to']
-    return (1, 0)
+    return UPGRADES[-1]['to']
 
 
 def get_version(db):

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

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

+ 21 - 26
src/bin/dbutil/tests/dbutil_test.sh.in

@@ -359,22 +359,19 @@ check_version $testdata/old_v1.sqlite3 "V1.0"
 check_no_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 
-# Temporarily disabled until #324 is merged
-#echo "5.2. Database is an old V1 database - upgrade"
-#upgrade_ok_test $testdata/old_v1.sqlite3 $backupfile
-#rm -f $tempfile $backupfile
+echo "5.2. Database is an old V1 database - upgrade"
+upgrade_ok_test $testdata/old_v1.sqlite3 $backupfile
+rm -f $tempfile $backupfile
 
 
-# Temporarily disabled until #324 is merged
-#echo "6.1. Database is new V1 database - check"
-#check_version $testdata/new_v1.sqlite3 "V1.0"
-#check_no_backup $tempfile $backupfile
-#rm -f $tempfile $backupfile
+echo "6.1. Database is new V1 database - check"
+check_version $testdata/new_v1.sqlite3 "V1.0"
+check_no_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
 
-# Temporarily disabled until #324 is merged
-#echo "6.2. Database is a new V1 database - upgrade"
-#upgrade_ok_test $testdata/new_v1.sqlite3 $backupfile
-#rm -f $tempfile $backupfile
+echo "6.2. Database is a new V1 database - upgrade"
+upgrade_ok_test $testdata/new_v1.sqlite3 $backupfile
+rm -f $tempfile $backupfile
 
 
 echo "7.1. Database is V2.0 database - check"
@@ -405,10 +402,9 @@ upgrade_fail_test $testdata/too_many_version.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-# Temporarily disabled until #324 is merged
-#echo "10.0. Upgrade corrupt database"
-#upgrade_fail_test $testdata/corrupt.sqlite3 $backupfile
-#rm -f $tempfile $backupfile
+echo "10.0. Upgrade corrupt database"
+upgrade_fail_test $testdata/corrupt.sqlite3 $backupfile
+rm -f $tempfile $backupfile
 
 
 echo "11. Record count test"
@@ -447,15 +443,14 @@ copy_file $testdata/old_v1.sqlite3 $tempfile
 passzero $?
 rm -f $tempfile $backupfile
 
-# Temporarily disabled until #324 is merged
-#echo "13.3 Interactive prompt - yes"
-#copy_file $testdata/old_v1.sqlite3 $tempfile
-#../run_dbutil.sh --upgrade $tempfile << .
-#Yes
-#.
-#passzero $?
-#check_version $tempfile "V2.0"
-#rm -f $tempfile $backupfile
+echo "13.3 Interactive prompt - yes"
+copy_file $testdata/old_v1.sqlite3 $tempfile
+../run_dbutil.sh --upgrade $tempfile << .
+Yes
+.
+passzero $?
+check_version $tempfile "V2.0"
+rm -f $tempfile $backupfile
 
 echo "13.4 Interactive prompt - no"
 copy_file $testdata/old_v1.sqlite3 $tempfile

+ 439 - 27
src/bin/ddns/ddns.py.in

@@ -18,13 +18,24 @@
 
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import isc
+from isc.acl.dns import REQUEST_LOADER
 import bind10_config
 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.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.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 time
 import errno
 
 from isc.log_messages.ddns_messages import *
@@ -39,18 +50,39 @@ isc.log.init("b10-ddns")
 logger = isc.log.Logger("ddns")
 TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
 
-DATA_PATH = bind10_config.DATA_PATH
-SOCKET_FILE = DATA_PATH + '/ddns_socket'
+# 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
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
 if "B10_FROM_SOURCE" in os.environ:
-    DATA_PATH = os.environ['B10_FROM_SOURCE'] + "/src/bin/ddns"
+    SPECFILE_PATH = os.environ["B10_FROM_SOURCE"] + "/src/bin/ddns"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR)
+    SPECFILE_PATH = SPECFILE_PATH.replace("${prefix}", PREFIX)
+
 if "B10_FROM_BUILD" 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:
-        SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/ddns_socket"
-SPECFILE_LOCATION = DATA_PATH + "/ddns.spec"
+        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()
 
@@ -85,7 +117,55 @@ def clear_socket():
     if os.path.exists(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:
+    # 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):
         '''
         Initialize the DDNS Server.
@@ -102,8 +182,32 @@ class DDNSServer:
                                                   self.config_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._zone_config = self.__update_zone_config(
+            self._cc.get_default_value('zones'))
         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
         # List of the session receivers where we get the requests
         self._socksession_receivers = {}
@@ -112,12 +216,54 @@ class DDNSServer:
         self._listen_socket.bind(SOCKET_FILE)
         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):
         '''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):
         '''
@@ -133,6 +279,88 @@ class DDNSServer:
             answer = create_answer(1, "Unknown command: " + str(cmd))
         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):
         '''Initiate a shutdown sequence.
 
@@ -163,10 +391,10 @@ class DDNSServer:
         Accept another connection and create the session receiver.
         """
         try:
-            sock = self._listen_socket.accept()
+            (sock, remote_addr) = self._listen_socket.accept()
             fileno = sock.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)
             self._socksession_receivers[fileno] = (sock, receiver)
         except (socket.error, isc.util.cio.socketsession.SocketSessionError) \
@@ -175,7 +403,30 @@ class DDNSServer:
             # continue with the rest
             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
         methods are either subroutines of this method or methods doing the
@@ -185,27 +436,179 @@ class DDNSServer:
         It is called with the request being session as received from
         SocketSessionReceiver, i.e. tuple
         (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):
-        """
-        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)
-        (socket, receiver) = self._socksession_receivers[fileno]
+        (session_socket, receiver) = self._socksession_receivers[fileno]
         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:
             # No matter why this failed, the connection is in unknown, possibly
             # broken state. So, we close the socket and remove the receiver.
             del self._socksession_receivers[fileno]
-            socket.close()
+            session_socket.close()
             logger.warn(DDNS_DROP_CONN, fileno, se)
+            return False
 
     def run(self):
         '''
@@ -226,8 +629,8 @@ class DDNSServer:
             try:
                 (reads, writes, exceptions) = \
                     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:
                 # In case it is just interrupted, we continue like nothing
                 # happened
@@ -242,6 +645,15 @@ class DDNSServer:
                     self.accept()
                 else:
                     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()
         logger.info(DDNS_STOPPED)
 
@@ -300,7 +712,7 @@ def main(ddns_server=None):
         logger.info(DDNS_STOPPED_BY_KEYBOARD)
     except SessionError as 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))
     except DDNSConfigError as e:
         logger.error(DDNS_CONFIG_ERROR, str(e))

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

@@ -4,22 +4,36 @@
     "config_data": [
       {
         "item_name": "zones",
-        "item_type": "named_set",
+        "item_type": "list",
         "item_optional": false,
-        "item_default": {},
-        "named_set_item_spec": {
+        "item_default": [],
+        "list_item_spec": {
           "item_name": "entry",
           "item_type": "map",
           "item_optional": true,
           "item_default": {
-            "update_acl": [{"action": "ACCEPT", "from": "127.0.0.1"},
-                           {"action": "ACCEPT", "from": "::1"}]
+	    "origin": "",
+	    "class": "IN",
+            "update_acl": []
           },
           "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_type": "list",
               "item_optional": false,
+	      "item_default": [],
               "list_item_spec": {
                 "item_name": "acl_element",
                 "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
 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
 There was a problem reading from the command and control channel. The
 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
 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
 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
@@ -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
 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
 There was a problem in the lower level module handling configuration and
 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
 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
 The ddns process received a shutdown command from the command channel
 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
 The ddns process has successfully started and is now ready to receive commands
 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
 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
@@ -88,3 +204,26 @@ process will now shut down.
 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
 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) \
 	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/ \
+	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

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


+ 9 - 5
src/bin/dhcp4/Makefile.am

@@ -18,10 +18,10 @@ man_MANS = b10-dhcp4.8
 EXTRA_DIST = $(man_MANS) b10-dhcp4.xml dhcp4.spec
 
 if ENABLE_MAN
-
 b10-dhcp4.8: b10-dhcp4.xml
-	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-dhcp4.xml
-
+	xsltproc --novalid --xinclude --nonet -o $@ \
+        http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \
+	$(srcdir)/b10-dhcp4.xml
 endif
 
 spec_config.h: spec_config.h.pre
@@ -32,12 +32,16 @@ pkglibexec_PROGRAMS = b10-dhcp4
 
 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/exceptions/libexceptions.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/liblog.la
 
-# TODO: config.h.in is wrong because doesn't honor pkgdatadir
-# and can't use @datadir@ because doesn't expand default ${prefix}
 b10_dhcp4dir = $(pkgdatadir)
 b10_dhcp4_DATA = dhcp4.spec

+ 26 - 33
src/bin/dhcp4/dhcp4_srv.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -59,10 +59,9 @@ Dhcpv4Srv::~Dhcpv4Srv() {
 bool
 Dhcpv4Srv::run() {
     while (!shutdown_) {
-        boost::shared_ptr<Pkt4> query; // client's message
-        boost::shared_ptr<Pkt4> rsp;   // server's response
-
-        query = IfaceMgr::instance().receive4();
+        // client's message and server's response
+        Pkt4Ptr query = IfaceMgr::instance().receive4();
+        Pkt4Ptr rsp;
 
         if (query) {
             try {
@@ -141,14 +140,13 @@ Dhcpv4Srv::setServerID() {
 #if 0
     // uncomment this once ticket 1350 is merged.
     IOAddress srvId("127.0.0.1");
-    serverid_ = boost::shared_ptr<Option>(
+    serverid_ = OptionPtr(
       new Option4AddrLst(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, srvId));
 #endif
 }
 
 
-void Dhcpv4Srv::copyDefaultFields(const boost::shared_ptr<Pkt4>& question,
-                                  boost::shared_ptr<Pkt4>& answer) {
+void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     answer->setIface(question->getIface());
     answer->setIndex(question->getIndex());
     answer->setCiaddr(question->getCiaddr());
@@ -174,17 +172,17 @@ void Dhcpv4Srv::copyDefaultFields(const boost::shared_ptr<Pkt4>& question,
 
 }
 
-void Dhcpv4Srv::appendDefaultOptions(boost::shared_ptr<Pkt4>& msg, uint8_t msg_type) {
-    boost::shared_ptr<Option> opt;
+void Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
+    OptionPtr opt;
 
     // add Message Type Option (type 53)
     std::vector<uint8_t> tmp;
     tmp.push_back(static_cast<uint8_t>(msg_type));
-    opt = boost::shared_ptr<Option>(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
+    opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
     msg->addOption(opt);
 
     // DHCP Server Identifier (type 54)
-    opt = boost::shared_ptr<Option>
+    opt = OptionPtr
         (new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, IOAddress(HARDCODED_SERVER_ID)));
     msg->addOption(opt);
 
@@ -192,47 +190,43 @@ void Dhcpv4Srv::appendDefaultOptions(boost::shared_ptr<Pkt4>& msg, uint8_t msg_t
 }
 
 
-void Dhcpv4Srv::appendRequestedOptions(boost::shared_ptr<Pkt4>& msg) {
-    boost::shared_ptr<Option> opt;
+void Dhcpv4Srv::appendRequestedOptions(Pkt4Ptr& msg) {
+    OptionPtr opt;
 
     // Domain name (type 15)
     vector<uint8_t> domain(HARDCODED_DOMAIN_NAME.begin(), HARDCODED_DOMAIN_NAME.end());
-    opt = boost::shared_ptr<Option>(new Option(Option::V4, DHO_DOMAIN_NAME, domain));
+    opt = OptionPtr(new Option(Option::V4, DHO_DOMAIN_NAME, domain));
     msg->addOption(opt);
     // TODO: Add Option_String class
 
     // DNS servers (type 6)
-    opt = boost::shared_ptr<Option>
-        (new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress(HARDCODED_DNS_SERVER)));
+    opt = OptionPtr(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress(HARDCODED_DNS_SERVER)));
     msg->addOption(opt);
 }
 
-void Dhcpv4Srv::tryAssignLease(boost::shared_ptr<Pkt4>& msg) {
-    boost::shared_ptr<Option> opt;
+void Dhcpv4Srv::tryAssignLease(Pkt4Ptr& msg) {
+    OptionPtr opt;
 
     // TODO: Implement actual lease assignment here
     msg->setYiaddr(IOAddress(HARDCODED_LEASE));
 
     // IP Address Lease time (type 51)
-    opt = boost::shared_ptr<Option>(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
+    opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
     opt->setUint32(HARDCODED_LEASE_TIME);
     msg->addOption(opt);
     // TODO: create Option_IntArray that holds list of integers, similar to Option4_AddrLst
 
     // Subnet mask (type 1)
-    opt = boost::shared_ptr<Option>
-        (new Option4AddrLst(DHO_SUBNET_MASK, IOAddress(HARDCODED_NETMASK)));
+    opt = OptionPtr(new Option4AddrLst(DHO_SUBNET_MASK, IOAddress(HARDCODED_NETMASK)));
     msg->addOption(opt);
 
     // Router (type 3)
-    opt = boost::shared_ptr<Option>
-        (new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
+    opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
     msg->addOption(opt);
 }
 
-boost::shared_ptr<Pkt4>
-Dhcpv4Srv::processDiscover(boost::shared_ptr<Pkt4>& discover) {
-    boost::shared_ptr<Pkt4> offer = boost::shared_ptr<Pkt4>
+Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
+    Pkt4Ptr offer = Pkt4Ptr
         (new Pkt4(DHCPOFFER, discover->getTransid()));
 
     copyDefaultFields(discover, offer);
@@ -244,9 +238,8 @@ Dhcpv4Srv::processDiscover(boost::shared_ptr<Pkt4>& discover) {
     return (offer);
 }
 
-boost::shared_ptr<Pkt4>
-Dhcpv4Srv::processRequest(boost::shared_ptr<Pkt4>& request) {
-    boost::shared_ptr<Pkt4> ack = boost::shared_ptr<Pkt4>
+Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
+    Pkt4Ptr ack = Pkt4Ptr
         (new Pkt4(DHCPACK, request->getTransid()));
 
     copyDefaultFields(request, ack);
@@ -258,17 +251,17 @@ Dhcpv4Srv::processRequest(boost::shared_ptr<Pkt4>& request) {
     return (ack);
 }
 
-void Dhcpv4Srv::processRelease(boost::shared_ptr<Pkt4>& release) {
+void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
     /// TODO: Implement this.
     cout << "Received RELEASE on " << release->getIface() << " interface." << endl;
 }
 
-void Dhcpv4Srv::processDecline(boost::shared_ptr<Pkt4>& decline) {
+void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
     /// TODO: Implement this.
     cout << "Received DECLINE on " << decline->getIface() << " interface." << endl;
 }
 
-boost::shared_ptr<Pkt4> Dhcpv4Srv::processInform(boost::shared_ptr<Pkt4>& inform) {
+Pkt4Ptr Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
     /// TODO: Currently implemented echo mode. Implement this for real
     return (inform);
 }

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,6 @@
 #ifndef DHCPV4_SRV_H
 #define DHCPV4_SRV_H
 
-#include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 #include <dhcp/dhcp4.h>
 #include <dhcp/pkt4.h>
@@ -68,11 +67,10 @@ protected:
     /// should be served. In particular, a lease is selected and sent
     /// as an offer to a client if it should be served.
     ///
-    /// @param solicit DISCOVER message received from client
+    /// @param discover DISCOVER message received from client
     ///
     /// @return OFFER message or NULL
-    boost::shared_ptr<Pkt4>
-    processDiscover(boost::shared_ptr<Pkt4>& discover);
+    Pkt4Ptr processDiscover(Pkt4Ptr& discover);
 
     /// @brief Processes incoming REQUEST and returns REPLY response.
     ///
@@ -86,7 +84,7 @@ protected:
     /// @param request a message received from client
     ///
     /// @return ACK or NACK message
-    boost::shared_ptr<Pkt4> processRequest(boost::shared_ptr<Pkt4>& request);
+    Pkt4Ptr processRequest(Pkt4Ptr& request);
 
     /// @brief Stub function that will handle incoming RELEASE messages.
     ///
@@ -94,17 +92,17 @@ protected:
     /// this function does not return anything.
     ///
     /// @param release message received from client
-    void processRelease(boost::shared_ptr<Pkt4>& release);
+    void processRelease(Pkt4Ptr& release);
 
     /// @brief Stub function that will handle incoming DHCPDECLINE messages.
     ///
     /// @param decline message received from client
-    void processDecline(boost::shared_ptr<Pkt4>& decline);
+    void processDecline(Pkt4Ptr& decline);
 
     /// @brief Stub function that will handle incoming INFORM messages.
     ///
-    /// @param infRequest message received from client
-    boost::shared_ptr<Pkt4> processInform(boost::shared_ptr<Pkt4>& inform);
+    /// @param inform message received from client
+    Pkt4Ptr processInform(Pkt4Ptr& inform);
 
     /// @brief Copies default parameters from client's to server's message
     ///
@@ -113,9 +111,7 @@ protected:
     ///
     /// @param question any message sent by client
     /// @param answer any message server is going to send as response
-    void copyDefaultFields(const boost::shared_ptr<Pkt4>& question,
-                           boost::shared_ptr<Pkt4>& answer);
-
+    void copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer);
 
     /// @brief Appends options requested by client.
     ///
@@ -123,8 +119,7 @@ protected:
     /// (sent in PRL) or are enforced by server.
     ///
     /// @param msg outgoing message (options will be added here)
-    void appendRequestedOptions(boost::shared_ptr<Pkt4>& msg);
-
+    void appendRequestedOptions(Pkt4Ptr& msg);
 
     /// @brief Assigns a lease and appends corresponding options
     ///
@@ -136,20 +131,18 @@ protected:
     /// used fixed, hardcoded lease.
     ///
     /// @param msg OFFER or ACK message (lease options will be added here)
-    void tryAssignLease(boost::shared_ptr<Pkt4>& msg);
-
+    void tryAssignLease(Pkt4Ptr& msg);
 
     /// @brief Appends default options to a message
     ///
     /// @param msg message object (options will be added to it)
     /// @param msg_type specifies message type
-    void appendDefaultOptions(boost::shared_ptr<Pkt4>& msg, uint8_t msg_type);
+    void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
 
     /// @brief Returns server-intentifier option
     ///
     /// @return server-id option
-    boost::shared_ptr<isc::dhcp::Option>
-    getServerID() { return serverid_; }
+    OptionPtr getServerID() { return serverid_; }
 
     /// @brief Sets server-identifier.
     ///
@@ -163,7 +156,7 @@ protected:
     void setServerID();
 
     /// server DUID (to be sent in server-identifier option)
-    boost::shared_ptr<isc::dhcp::Option> serverid_;
+    OptionPtr serverid_;
 
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.

+ 19 - 6
src/bin/dhcp4/main.cc

@@ -37,6 +37,7 @@
 
 #include <dhcp4/spec_config.h>
 #include <dhcp4/dhcp4_srv.h>
+#include <dhcp/dhcp4.h>
 
 using namespace std;
 using namespace isc::util;
@@ -53,33 +54,45 @@ usage() {
     cerr << "Usage:  b10-dhcp4 [-v]"
          << endl;
     cerr << "\t-v: verbose output" << endl;
-    exit(1);
+    cerr << "\t-p number: specify non-standard port number 1-65535 (useful for testing only)" << endl;
+    exit(EXIT_FAILURE);
 }
 } // end of anonymous namespace
 
 int
 main(int argc, char* argv[]) {
     int ch;
+    int port_number = DHCP4_SERVER_PORT; // The default. any other values are
+                                         // useful for testing only.
 
-    while ((ch = getopt(argc, argv, ":v")) != -1) {
+    while ((ch = getopt(argc, argv, "vp:")) != -1) {
         switch (ch) {
         case 'v':
             verbose_mode = true;
             isc::log::denabled = true;
             break;
+        case 'p':
+            port_number = strtol(optarg, NULL, 10);
+            if (port_number == 0) {
+                cerr << "Failed to parse port number: [" << optarg
+                     << "], 1-65535 allowed." << endl;
+                usage();
+            }
+            break;
         case ':':
         default:
             usage();
         }
     }
 
-    cout << "My pid=" << getpid() << endl;
+    cout << "My pid=" << getpid() << ", binding to port " << port_number
+         << ", verbose " << (verbose_mode?"yes":"no") << endl;
 
     if (argc - optind > 0) {
         usage();
     }
 
-    int ret = 0;
+    int ret = EXIT_SUCCESS;
 
     // TODO remainder of auth to dhcp4 code copy. We need to enable this in
     //      dhcp4 eventually
@@ -99,13 +112,13 @@ main(int argc, char* argv[]) {
 
         cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
 
-        Dhcpv4Srv* srv = new Dhcpv4Srv();
+        Dhcpv4Srv* srv = new Dhcpv4Srv(port_number);
 
         srv->run();
 
     } catch (const std::exception& ex) {
         cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
-        ret = 1;
+        ret = EXIT_FAILURE;
     }
 
     return (ret);

+ 21 - 2
src/bin/dhcp4/tests/Makefile.am

@@ -1,12 +1,25 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
+PYTESTS = dhcp4_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# Explicitly specify paths to dynamic libraries required by loadable python
+# modules. That is required on Mac OS systems. Otherwise we will get exception
+# about python not being able to load liblog library.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+	$(LIBRARY_PATH_PLACEHOLDER) \
+		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+	done
+
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin
@@ -34,6 +47,12 @@ dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc
 dhcp4_unittests_SOURCES += dhcp4_unittests.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_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp4_unittests_LDADD = $(GTEST_LDADD)

+ 170 - 0
src/bin/dhcp4/tests/dhcp4_test.py

@@ -0,0 +1,170 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from bind10_src import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+
+import unittest
+import sys
+import os
+import signal
+import socket
+from isc.net.addr import IPAddr
+import time
+import isc
+import fcntl
+
+class TestDhcpv4Daemon(unittest.TestCase):
+    def setUp(self):
+        # don't redirect stdout/stderr here as we want to print out things
+        # during the test
+        pass
+
+    def tearDown(self):
+        pass
+
+    def runDhcp4(self, params, wait=1):
+        """
+        This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
+        """
+        ## @todo: Convert this into generic method and reuse it in dhcp6
+
+        print("Running command: %s" % (" ".join(params)))
+
+        # redirect stdout to a pipe so we can check that our
+        # process spawning is doing the right thing with stdout
+        self.stdout_old = os.dup(sys.stdout.fileno())
+        self.stdout_pipes = os.pipe()
+        os.dup2(self.stdout_pipes[1], sys.stdout.fileno())
+        os.close(self.stdout_pipes[1])
+
+        # do the same trick for stderr:
+        self.stderr_old = os.dup(sys.stderr.fileno())
+        self.stderr_pipes = os.pipe()
+        os.dup2(self.stderr_pipes[1], sys.stderr.fileno())
+        os.close(self.stderr_pipes[1])
+
+        # note that we use dup2() to restore the original stdout
+        # to the main program ASAP in each test... this prevents
+        # hangs reading from the child process (as the pipe is only
+        # open in the child), and also insures nice pretty output
+
+        pi = ProcessInfo('Test Process', params)
+        pi.spawn()
+        time.sleep(wait)
+        os.dup2(self.stdout_old, sys.stdout.fileno())
+        os.dup2(self.stderr_old, sys.stderr.fileno())
+        self.assertNotEqual(pi.process, None)
+        self.assertTrue(type(pi.pid) is int)
+
+        # Set non-blocking read on pipes. Process may not print anything
+        # on specific output and the we would hang without this.
+        fd = self.stdout_pipes[0]
+        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+        fd = self.stderr_pipes[0]
+        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+        # There's potential problem if b10-dhcp4 prints out more
+        # than 4k of text
+        try:
+            output = os.read(self.stdout_pipes[0], 4096)
+        except OSError:
+            print("No data available from stdout")
+            output = ""
+
+        # read can return None. Make sure we have a string
+        if (output is None):
+            output = ""
+
+        try:
+            error = os.read(self.stderr_pipes[0], 4096)
+        except OSError:
+            print("No data available on stderr")
+            error = ""
+
+        # read can return None. Make sure we have a string
+        if (error is None):
+            error = ""
+
+
+        try:
+            if (not pi.process.poll()):
+                # let's be nice at first...
+                pi.process.terminate()
+        except OSError:
+            print("Ignoring failed kill attempt. Process is dead already.")
+
+        # call this to get returncode, process should be dead by now
+        rc = pi.process.wait()
+
+        # Clean up our stdout/stderr munging.
+        os.dup2(self.stdout_old, sys.stdout.fileno())
+        os.close(self.stdout_pipes[0])
+
+        os.dup2(self.stderr_old, sys.stderr.fileno())
+        os.close(self.stderr_pipes[0])
+
+        print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
+               % (rc, len(output), len(error)) )
+
+        return (rc, output, error)
+
+    def test_alive(self):
+        print("Note: Purpose of some of the tests is to check if DHCPv4 server can be started,")
+        print("      not that is can bind sockets correctly. Please ignore binding errors.")
+
+        (returncode, output, error) = self.runDhcp4(["../b10-dhcp4", "-v"])
+
+        self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
+
+    def test_portnumber_0(self):
+        print("Check that specifying port number 0 is not allowed.")
+
+        (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '0'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+    def test_portnumber_missing(self):
+        print("Check that -p option requires a parameter.")
+
+        (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("option requires an argument"), 1)
+
+    def test_portnumber_nonroot(self):
+        print("Check that specifying unprivileged port number will work.")
+
+        (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '10057'])
+
+        # When invalid port number is specified, return code must not be success
+        # TODO: Temporarily commented out as socket binding on systems that do not have
+        #       interface detection implemented currently fails.
+        # self.assertTrue(returncode == 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
+
+if __name__ == '__main__':
+    unittest.main()

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

@@ -2,9 +2,8 @@ SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
-AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
- AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -14,16 +13,17 @@ endif
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 
-CLEANFILES = *.gcno *.gcda spec_config.h
+CLEANFILES = spec_config.h
 
 man_MANS = b10-dhcp6.8
-EXTRA_DIST = $(man_MANS) b10-dhcp6.xml dhcp6.spec interfaces.txt
+EXTRA_DIST = $(man_MANS) b10-dhcp6.xml dhcp6.spec
 
 if ENABLE_MAN
 
 b10-dhcp6.8: b10-dhcp6.xml
-	xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-dhcp6.xml
-
+	xsltproc --novalid --xinclude --nonet -o $@ \
+        http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \
+        $(srcdir)/b10-dhcp6.xml
 endif
 
 spec_config.h: spec_config.h.pre
@@ -34,12 +34,16 @@ pkglibexec_PROGRAMS = b10-dhcp6
 
 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/asiolink/libasiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/liblog.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
 
-# TODO: config.h.in is wrong because doesn't honor pkgdatadir
-# and can't use @datadir@ because doesn't expand default ${prefix}
 b10_dhcp6dir = $(pkgdatadir)
-b10_dhcp6_DATA = dhcp6.spec interfaces.txt
+b10_dhcp6_DATA = dhcp6.spec

+ 127 - 79
src/bin/dhcp6/dhcp6_srv.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <stdlib.h>
+#include <time.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/iface_mgr.h>
@@ -21,11 +23,14 @@
 #include <dhcp/option6_addrlst.h>
 #include <asiolink/io_address.h>
 #include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+#include <util/range_utilities.h>
 
 using namespace std;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
+using namespace isc::util;
 
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const uint32_t HARDCODED_T1 = 1500; // in seconds
@@ -35,7 +40,7 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
-    cout << "Initialization" << endl;
+    cout << "Initialization: opening sockets on port " << port << endl;
 
     // first call to instance() will create IfaceMgr (it's a singleton)
     // it may throw something if things go wrong
@@ -67,13 +72,12 @@ Dhcpv6Srv::~Dhcpv6Srv() {
     IfaceMgr::instance().closeSockets();
 }
 
-bool
-Dhcpv6Srv::run() {
+bool Dhcpv6Srv::run() {
     while (!shutdown) {
-        boost::shared_ptr<Pkt6> query; // client's message
-        boost::shared_ptr<Pkt6> rsp;   // server's response
 
-        query = IfaceMgr::instance().receive6();
+        // client's message and server's response
+        Pkt6Ptr query = IfaceMgr::instance().receive6();
+        Pkt6Ptr rsp;
 
         if (query) {
             if (!query->unpack()) {
@@ -110,16 +114,16 @@ Dhcpv6Srv::run() {
                      << query->getType() << endl;
             }
 
-            cout << "Received " << query->data_len_ << " bytes packet type="
+            cout << "Received " << query->getBuffer().getLength() << " bytes packet type="
                  << query->getType() << endl;
             cout << query->toText();
             if (rsp) {
-                rsp->remote_addr_ = query->remote_addr_;
-                rsp->local_addr_ = query->local_addr_;
-                rsp->remote_port_ = DHCP6_CLIENT_PORT;
-                rsp->local_port_ = DHCP6_SERVER_PORT;
-                rsp->ifindex_ = query->ifindex_;
-                rsp->iface_ = query->iface_;
+                rsp->setRemoteAddr(query->getRemoteAddr());
+                rsp->setLocalAddr(query->getLocalAddr());
+                rsp->setRemotePort(DHCP6_CLIENT_PORT);
+                rsp->setLocalPort(DHCP6_SERVER_PORT);
+                rsp->setIndex(query->getIndex());
+                rsp->setIface(query->getIface());
                 cout << "Replying with:" << rsp->getType() << endl;
                 cout << rsp->toText();
                 cout << "----" << endl;
@@ -138,28 +142,93 @@ Dhcpv6Srv::run() {
     return (true);
 }
 
-void
-Dhcpv6Srv::setServerID() {
-    /// TODO implement this for real once interface detection is done.
-    /// Use hardcoded server-id for now
-
-    boost::shared_array<uint8_t> srvid(new uint8_t[14]);
-    srvid[0] = 0;
-    srvid[1] = 1; // DUID type 1 = DUID-LLT (see section 9.2 of RFC3315)
-    srvid[2] = 0;
-    srvid[3] = 6; // HW type = ethernet (I think. I'm typing this from my head
-                  // in hotel, without Internet connection)
-    for (int i=4; i<14; i++) {
-        srvid[i]=i-4;
+void Dhcpv6Srv::setServerID() {
+
+    /// @todo: DUID should be generated once and then stored, rather
+    /// than generated each time
+
+    /// @todo: This code implements support for DUID-LLT (the recommended one).
+    /// We should eventually add support for other DUID types: DUID-LL, DUID-EN
+    /// and DUID-UUID
+
+    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+    // let's find suitable interface
+    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+         iface != ifaces.end(); ++iface) {
+        // All the following checks could be merged into one multi-condition
+        // statement, but let's keep them separated as perhaps one day
+        // we will grow knobs to selectively turn them on or off. Also,
+        // this code is used only *once* during first start on a new machine
+        // and then server-id is stored. (or at least it will be once
+        // DUID storage is implemente
+
+        // I wish there was a this_is_a_real_physical_interface flag...
+
+        // MAC address should be at least 6 bytes. Although there is no such
+        // requirement in any RFC, all decent physical interfaces (Ethernet,
+        // WiFi, Infiniband, etc.) have 6 bytes long MAC address. We want to
+        // base our DUID on real hardware address, rather than virtual
+        // interface that pretends that underlying IP address is its MAC.
+        if (iface->getMacLen() < MIN_MAC_LEN) {
+            continue;
+        }
+
+        // let's don't use loopback
+        if (iface->flag_loopback_) {
+            continue;
+        }
+
+        // let's skip downed interfaces. It is better to use working ones.
+        if (!iface->flag_up_) {
+            continue;
+        }
+
+        // some interfaces (like lo on Linux) report 6-bytes long
+        // MAC adress 00:00:00:00:00:00. Let's not use such weird interfaces
+        // to generate DUID.
+        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
+            continue;
+        }
+
+        // Ok, we have useful MAC. Let's generate DUID-LLT based on
+        // it. See RFC3315, Section 9.2 for details.
+
+        // DUID uses seconds since midnight of 01-01-2000, time() returns
+        // seconds since 01-01-1970. DUID_TIME_EPOCH substution corrects that.
+        time_t seconds = time(NULL);
+        seconds -= DUID_TIME_EPOCH;
+
+        OptionBuffer srvid(8 + iface->getMacLen());
+        writeUint16(DUID_LLT, &srvid[0]);
+        writeUint16(HWTYPE_ETHERNET, &srvid[2]);
+        writeUint32(static_cast<uint32_t>(seconds), &srvid[4]);
+        memcpy(&srvid[0]+8, iface->getMac(), iface->getMacLen());
+
+        serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
+                                         srvid.begin(), srvid.end()));
+        return;
     }
-    serverid_ = boost::shared_ptr<Option>(new Option(Option::V6,
-                                                     D6O_SERVERID,
-                                                     srvid,
-                                                     0, 14));
+
+    // if we reached here, there are no suitable interfaces found.
+    // Either interface detection is not supported on this platform or
+    // this is really weird box. Let's use DUID-EN instead.
+    // See Section 9.3 of RFC3315 for details.
+
+    OptionBuffer srvid(12);
+    writeUint16(DUID_EN, &srvid[0]);
+    writeUint32(ENTERPRISE_ID_ISC, &srvid[2]);
+
+    // Length of the identifier is company specific. I hereby declare
+    // ISC "standard" of 6 bytes long pseudo-random numbers.
+    srandom(time(NULL));
+    fillRandom(&srvid[6],&srvid[12]);
+
+    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
+                                     srvid.begin(), srvid.end()));
 }
 
-void Dhcpv6Srv::copyDefaultOptions(const boost::shared_ptr<Pkt6>& question,
-                                   boost::shared_ptr<Pkt6>& answer) {
+void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     // add client-id
     boost::shared_ptr<Option> clientid = question->getOption(D6O_CLIENTID);
     if (clientid) {
@@ -169,8 +238,7 @@ void Dhcpv6Srv::copyDefaultOptions(const boost::shared_ptr<Pkt6>& question,
     // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
 }
 
-void Dhcpv6Srv::appendDefaultOptions(const boost::shared_ptr<Pkt6>& /*question*/,
-                                   boost::shared_ptr<Pkt6>& answer) {
+void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
     // TODO: question is currently unused, but we need it at least to know
     // message type we are answering
 
@@ -179,8 +247,7 @@ void Dhcpv6Srv::appendDefaultOptions(const boost::shared_ptr<Pkt6>& /*question*/
 }
 
 
-void Dhcpv6Srv::appendRequestedOptions(const boost::shared_ptr<Pkt6>& /*question*/,
-                                       boost::shared_ptr<Pkt6>& answer) {
+void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
     // TODO: question is currently unused, but we need to extract ORO from it
     // and act on its content. Now we just send DNS-SERVERS option.
 
@@ -190,8 +257,7 @@ void Dhcpv6Srv::appendRequestedOptions(const boost::shared_ptr<Pkt6>& /*question
     answer->addOption(dnsservers);
 }
 
-void Dhcpv6Srv::assignLeases(const boost::shared_ptr<Pkt6>& question,
-                             boost::shared_ptr<Pkt6>& answer) {
+void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     /// TODO Rewrite this once LeaseManager is implemented.
 
     // answer client's IA (this is mostly a dummy,
@@ -217,11 +283,8 @@ void Dhcpv6Srv::assignLeases(const boost::shared_ptr<Pkt6>& question,
     }
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processSolicit(const boost::shared_ptr<Pkt6>& solicit) {
-
-    boost::shared_ptr<Pkt6> advertise(new Pkt6(DHCPV6_ADVERTISE,
-                                               solicit->getTransid()));
+Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
+    Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
 
     copyDefaultOptions(solicit, advertise);
     appendDefaultOptions(solicit, advertise);
@@ -232,11 +295,8 @@ Dhcpv6Srv::processSolicit(const boost::shared_ptr<Pkt6>& solicit) {
     return (advertise);
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processRequest(const boost::shared_ptr<Pkt6>& request) {
-
-    boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
-                                           request->getTransid()));
+Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
 
     copyDefaultOptions(request, reply);
     appendDefaultOptions(request, reply);
@@ -247,50 +307,38 @@ Dhcpv6Srv::processRequest(const boost::shared_ptr<Pkt6>& request) {
     return (reply);
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processRenew(const boost::shared_ptr<Pkt6>& renew) {
-    boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
-                                           renew->getTransid(),
-                                           Pkt6::UDP));
+Pkt6Ptr Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
+    /// @todo: Implement this
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
     return reply;
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processRebind(const boost::shared_ptr<Pkt6>& rebind) {
-    boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
-                                           rebind->getTransid(),
-                                           Pkt6::UDP));
+Pkt6Ptr Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
+    /// @todo: Implement this
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
     return reply;
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processConfirm(const boost::shared_ptr<Pkt6>& confirm) {
-    boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
-                                           confirm->getTransid(),
-                                           Pkt6::UDP));
+Pkt6Ptr Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
+    /// @todo: Implement this
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, confirm->getTransid()));
     return reply;
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processRelease(const boost::shared_ptr<Pkt6>& release) {
-    boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
-                                           release->getTransid(),
-                                           Pkt6::UDP));
+Pkt6Ptr Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
+    /// @todo: Implement this
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
     return reply;
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processDecline(const boost::shared_ptr<Pkt6>& decline) {
-    boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
-                                           decline->getTransid(),
-                                           Pkt6::UDP));
+Pkt6Ptr Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) {
+    /// @todo: Implement this
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, decline->getTransid()));
     return reply;
 }
 
-boost::shared_ptr<Pkt6>
-Dhcpv6Srv::processInfRequest(const boost::shared_ptr<Pkt6>& infRequest) {
-    boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
-                                           infRequest->getTransid(),
-                                           Pkt6::UDP));
+Pkt6Ptr Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
+    /// @todo: Implement this
+    Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, infRequest->getTransid()));
     return reply;
 }

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,6 @@
 #ifndef DHCPV6_SRV_H
 #define DHCPV6_SRV_H
 
-#include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 #include <dhcp/dhcp6.h>
 #include <dhcp/pkt6.h>
@@ -36,6 +35,10 @@ namespace dhcp {
 class Dhcpv6Srv : public boost::noncopyable {
 
 public:
+
+    /// @brief Minimum length of a MAC address to be used in DUID generation.
+    static const size_t MIN_MAC_LEN = 6;
+
     /// @brief Default constructor.
     ///
     /// Instantiates necessary services, required to run DHCPv6 server.
@@ -52,8 +55,7 @@ public:
     /// @brief Returns server-intentifier option
     ///
     /// @return server-id option
-    boost::shared_ptr<isc::dhcp::Option>
-    getServerID() { return serverid_; }
+    OptionPtr getServerID() { return serverid_; }
 
     /// @brief Main server processing loop.
     ///
@@ -80,8 +82,7 @@ protected:
     /// @param solicit SOLICIT message received from client
     ///
     /// @return ADVERTISE, REPLY message or NULL
-    boost::shared_ptr<Pkt6>
-    processSolicit(const boost::shared_ptr<Pkt6>& solicit);
+    Pkt6Ptr processSolicit(const Pkt6Ptr& solicit);
 
     /// @brief Processes incoming REQUEST and returns REPLY response.
     ///
@@ -94,44 +95,37 @@ protected:
     /// @param request a message received from client
     ///
     /// @return REPLY message or NULL
-    boost::shared_ptr<Pkt6>
-    processRequest(const boost::shared_ptr<Pkt6>& request);
+    Pkt6Ptr processRequest(const Pkt6Ptr& request);
 
     /// @brief Stub function that will handle incoming RENEW messages.
     ///
     /// @param renew message received from client
-    boost::shared_ptr<Pkt6>
-    processRenew(const boost::shared_ptr<Pkt6>& renew);
+    Pkt6Ptr processRenew(const Pkt6Ptr& renew);
 
     /// @brief Stub function that will handle incoming REBIND messages.
     ///
     /// @param rebind message received from client
-    boost::shared_ptr<Pkt6>
-    processRebind(const boost::shared_ptr<Pkt6>& rebind);
+    Pkt6Ptr processRebind(const Pkt6Ptr& rebind);
 
     /// @brief Stub function that will handle incoming CONFIRM messages.
     ///
     /// @param confirm message received from client
-    boost::shared_ptr<Pkt6>
-    processConfirm(const boost::shared_ptr<Pkt6>& confirm);
+    Pkt6Ptr processConfirm(const Pkt6Ptr& confirm);
 
     /// @brief Stub function that will handle incoming RELEASE messages.
     ///
     /// @param release message received from client
-    boost::shared_ptr<Pkt6>
-    processRelease(const boost::shared_ptr<Pkt6>& release);
+    Pkt6Ptr processRelease(const Pkt6Ptr& release);
 
     /// @brief Stub function that will handle incoming DECLINE messages.
     ///
     /// @param decline message received from client
-    boost::shared_ptr<Pkt6>
-    processDecline(const boost::shared_ptr<Pkt6>& decline);
+    Pkt6Ptr processDecline(const Pkt6Ptr& decline);
 
     /// @brief Stub function that will handle incoming INF-REQUEST messages.
     ///
     /// @param infRequest message received from client
-    boost::shared_ptr<Pkt6>
-    processInfRequest(const boost::shared_ptr<Pkt6>& infRequest);
+    Pkt6Ptr processInfRequest(const Pkt6Ptr& infRequest);
 
     /// @brief Copies required options from client message to server answer
     ///
@@ -141,8 +135,7 @@ protected:
     ///
     /// @param question client's message (options will be copied from here)
     /// @param answer server's message (options will be copied here)
-    void copyDefaultOptions(const boost::shared_ptr<Pkt6>& question,
-                            boost::shared_ptr<Pkt6>& answer);
+    void copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
 
     /// @brief Appends default options to server's answer.
     ///
@@ -152,8 +145,7 @@ protected:
     ///
     /// @param question client's message
     /// @param answer server's message (options will be added here)
-    void appendDefaultOptions(const boost::shared_ptr<Pkt6>& question,
-                              boost::shared_ptr<Pkt6>& answer);
+    void appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
 
     /// @brief Appends requested options to server's answer.
     ///
@@ -163,8 +155,7 @@ protected:
     ///
     /// @param question client's message
     /// @param answer server's message (options will be added here)
-    void appendRequestedOptions(const boost::shared_ptr<Pkt6>& question,
-                                boost::shared_ptr<Pkt6>& answer);
+    void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
 
     /// @brief Assigns leases.
     ///
@@ -174,8 +165,7 @@ protected:
     ///
     /// @param question client's message (with requested IA_NA)
     /// @param answer server's message (IA_NA options will be added here)
-    void assignLeases(const boost::shared_ptr<Pkt6>& question,
-                      boost::shared_ptr<Pkt6>& answer);
+    void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
 
     /// @brief Sets server-identifier.
     ///

+ 0 - 10
src/bin/dhcp6/interfaces.txt

@@ -1,10 +0,0 @@
-eth0 fe80::21e:8cff:fe9b:7349
-
-#
-# only first line is read.
-# please use following format:
-# interface-name link-local-ipv6-address
-#
-# This file will become obsolete once proper interface detection 
-# is implemented.
-#

+ 17 - 6
src/bin/dhcp6/main.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -53,20 +53,31 @@ usage() {
     cerr << "Usage:  b10-dhcp6 [-v]"
          << endl;
     cerr << "\t-v: verbose output" << endl;
-    exit(1);
+    cerr << "\t-p number: specify non-standard port number 1-65535 (useful for testing only)" << endl;
+    exit(EXIT_FAILURE);
 }
 } // end of anonymous namespace
 
 int
 main(int argc, char* argv[]) {
     int ch;
+    int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
+                                         // useful for testing only.
 
-    while ((ch = getopt(argc, argv, ":v")) != -1) {
+    while ((ch = getopt(argc, argv, "vp:")) != -1) {
         switch (ch) {
         case 'v':
             verbose_mode = true;
             isc::log::denabled = true;
             break;
+        case 'p':
+            port_number = strtol(optarg, NULL, 10);
+            if (port_number == 0) {
+                cerr << "Failed to parse port number: [" << optarg
+                     << "], 1-65535 allowed." << endl;
+                usage();
+            }
+            break;
         case ':':
         default:
             usage();
@@ -79,7 +90,7 @@ main(int argc, char* argv[]) {
         usage();
     }
 
-    int ret = 0;
+    int ret = EXIT_SUCCESS;
 
     // TODO remainder of auth to dhcp6 code copy. We need to enable this in
     //      dhcp6 eventually
@@ -99,13 +110,13 @@ main(int argc, char* argv[]) {
 
         cout << "[b10-dhcp6] Initiating DHCPv6 operation." << endl;
 
-        Dhcpv6Srv* srv = new Dhcpv6Srv();
+        Dhcpv6Srv* srv = new Dhcpv6Srv(port_number);
 
         srv->run();
 
     } catch (const std::exception& ex) {
         cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
-        ret = 1;
+        ret = EXIT_FAILURE;
     }
 
     return (ret);

+ 11 - 9
src/bin/dhcp6/tests/Makefile.am

@@ -1,11 +1,10 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-#PYTESTS = args_test.py bind10_test.py
-# NOTE: this has a generated test found in the builddir
 PYTESTS = dhcp6_test.py
 EXTRA_DIST = $(PYTESTS)
 
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
+# Explicitly specify paths to dynamic libraries required by loadable python
+# modules. That is required on Mac OS systems. Otherwise we will get exception
+# about python not being able to load liblog library.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
@@ -15,9 +14,8 @@ endif
 check-local:
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
-	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
+	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 
@@ -26,8 +24,6 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
-AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
 CLEANFILES = $(builddir)/interfaces.txt
@@ -47,14 +43,20 @@ dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += dhcp6_unittests.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_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
-dhcp6_unittests_LDADD += $(SQLITE_LIBS)
 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/log/liblog.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
 endif
 
 noinst_PROGRAMS = $(TESTS)

+ 94 - 26
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -20,18 +20,21 @@
 #include <arpa/inet.h>
 #include <gtest/gtest.h>
 
-#include "dhcp/dhcp6.h"
-#include "dhcp6/dhcp6_srv.h"
-#include "dhcp/option6_ia.h"
+#include <dhcp/dhcp6.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <util/buffer.h>
+#include <util/range_utilities.h>
+#include <boost/scoped_ptr.hpp>
 
 using namespace std;
 using namespace isc;
 using namespace isc::dhcp;
+using namespace isc::util;
 
 // namespace has to be named, because friends are defined in Dhcpv6Srv class
 // Maybe it should be isc::test?
 namespace {
-const char* const INTERFACE_FILE = "interfaces.txt";
 
 class NakedDhcpv6Srv: public Dhcpv6Srv {
     // "naked" Interface Manager, exposes internal fields
@@ -50,18 +53,10 @@ public:
 
 class Dhcpv6SrvTest : public ::testing::Test {
 public:
+    // these are empty for now, but let's keep them around
     Dhcpv6SrvTest() {
-        unlink(INTERFACE_FILE);
-        fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
-        if (if_nametoindex("lo") > 0) {
-            fakeifaces << "lo ::1";
-        } else if (if_nametoindex("lo0") > 0) {
-            fakeifaces << "lo0 ::1";
-        }
-        fakeifaces.close();
     }
     ~Dhcpv6SrvTest() {
-        unlink(INTERFACE_FILE);
     };
 };
 
@@ -79,18 +74,93 @@ TEST_F(Dhcpv6SrvTest, basic) {
     delete srv;
 }
 
+TEST_F(Dhcpv6SrvTest, DUID) {
+    // tests that DUID is generated properly
+
+    boost::scoped_ptr<Dhcpv6Srv> srv;
+    ASSERT_NO_THROW( {
+        srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
+    });
+
+    OptionPtr srvid = srv->getServerID();
+    ASSERT_TRUE(srvid);
+
+    EXPECT_EQ(D6O_SERVERID, srvid->getType());
+
+    OutputBuffer buf(32);
+    srvid->pack(buf);
+
+    // length of the actual DUID
+    size_t len = buf.getLength() - srvid->getHeaderLen();
+
+    InputBuffer data(buf.getData(), buf.getLength());
+
+    // ignore first four bytes (standard DHCPv6 header)
+    data.readUint32();
+
+    uint16_t duid_type = data.readUint16();
+    cout << "Duid-type=" << duid_type << endl;
+    switch(duid_type) {
+    case DUID_LLT: {
+        // DUID must contain at least 6 bytes long MAC
+        // + 8 bytes of fixed header
+        EXPECT_GE(14, len);
+
+        uint16_t hw_type = data.readUint16();
+        // there's no real way to find out "correct"
+        // hardware type
+        EXPECT_GT(hw_type, 0);
+
+        // check that timer is counted since 1.1.2000,
+        // not from 1.1.1970.
+        uint32_t seconds = data.readUint32();
+        EXPECT_LE(seconds, DUID_TIME_EPOCH);
+        // this test will start failing after 2030.
+        // Hopefully we will be at BIND12 by then.
+
+        // MAC must not be zeros
+        vector<uint8_t> mac(len-8);
+        vector<uint8_t> zeros(len-8, 0);
+        data.readVector(mac, len-8);
+        EXPECT_TRUE(mac != zeros);
+        break;
+    }
+    case DUID_EN: {
+        // there's not much we can check. Just simple
+        // check if it is not all zeros
+        vector<uint8_t> content(len-2);
+        data.readVector(content, len-2);
+        EXPECT_FALSE(isRangeZero(content.begin(), content.end()));
+        break;
+    }
+    case DUID_LL: {
+        // not supported yet
+        cout << "Test not implemented for DUID-LL." << endl;
+
+        // No failure here. There's really no way for test LL DUID. It doesn't
+        // even make sense to check if that Link Layer is actually present on
+        // a physical interface. RFC3315 says a server should write its DUID
+        // and keep it despite hardware changes.
+        break;
+    }
+    case DUID_UUID: // not supported yet
+    default:
+        ADD_FAILURE() << "Not supported duid type=" << duid_type << endl;
+        break;
+    }
+}
+
 TEST_F(Dhcpv6SrvTest, Solicit_basic) {
-    NakedDhcpv6Srv* srv = NULL;
-    ASSERT_NO_THROW( srv = new NakedDhcpv6Srv(); );
+    boost::scoped_ptr<NakedDhcpv6Srv> srv;
+    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv()) );
 
     // a dummy content for client-id
-    boost::shared_array<uint8_t> clntDuid(new uint8_t[32]);
-    for (int i = 0; i < 32; i++)
+    OptionBuffer clntDuid(32);
+    for (int i = 0; i < 32; i++) {
         clntDuid[i] = 100 + i;
+    }
 
-    boost::shared_ptr<Pkt6> sol =
-        boost::shared_ptr<Pkt6>(new Pkt6(DHCPV6_SOLICIT,
-                                         1234, Pkt6::UDP));
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
 
     boost::shared_ptr<Option6IA> ia =
         boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, 234));
@@ -113,9 +183,9 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     // - server-id
     // - IA that includes IAADDR
 
-    boost::shared_ptr<Option> clientid =
-        boost::shared_ptr<Option>(new Option(Option::V6, D6O_CLIENTID,
-                                             clntDuid, 0, 16));
+    OptionPtr clientid = OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+                                              clntDuid.begin(),
+                                              clntDuid.begin() + 16));
     sol->addOption(clientid);
 
     boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
@@ -126,7 +196,7 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     EXPECT_EQ( DHCPV6_ADVERTISE, reply->getType() );
     EXPECT_EQ( 1234, reply->getTransid() );
 
-    boost::shared_ptr<Option> tmp = reply->getOption(D6O_IA_NA);
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
     ASSERT_TRUE( tmp );
 
     Option6IA* reply_ia = dynamic_cast<Option6IA*>(tmp.get());
@@ -151,8 +221,6 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
     EXPECT_TRUE(tmp->getData() == srv->getServerID()->getData());
 
     // more checks to be implemented
-    delete srv;
-
 }
 
 }

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

@@ -1,4 +1,4 @@
-# Copyright (C) 2011 Internet Systems Consortium.
+# Copyright (C) 2011,2012 Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -23,49 +23,150 @@ import socket
 from isc.net.addr import IPAddr
 import time
 import isc
+import fcntl
 
 class TestDhcpv6Daemon(unittest.TestCase):
     def setUp(self):
+        # don't redirect stdout/stderr here as we want to print out things
+        # during the test
+        pass
+
+    def tearDown(self):
+        pass
+
+    def runCommand(self, params, wait=1):
+        """
+        This method runs a command and returns a touple: (returncode, stdout, stderr)
+        """
+        ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
+
+        print("Running command: %s" % (" ".join(params)))
+
         # redirect stdout to a pipe so we can check that our
         # process spawning is doing the right thing with stdout
-        self.old_stdout = os.dup(sys.stdout.fileno())
-        self.pipes = os.pipe()
-        os.dup2(self.pipes[1], sys.stdout.fileno())
-        os.close(self.pipes[1])
+        self.stdout_old = os.dup(sys.stdout.fileno())
+        self.stdout_pipes = os.pipe()
+        os.dup2(self.stdout_pipes[1], sys.stdout.fileno())
+        os.close(self.stdout_pipes[1])
+
+        # do the same trick for stderr:
+        self.stderr_old = os.dup(sys.stderr.fileno())
+        self.stderr_pipes = os.pipe()
+        os.dup2(self.stderr_pipes[1], sys.stderr.fileno())
+        os.close(self.stderr_pipes[1])
+
         # note that we use dup2() to restore the original stdout
         # to the main program ASAP in each test... this prevents
         # hangs reading from the child process (as the pipe is only
         # open in the child), and also insures nice pretty output
 
-    def tearDown(self):
-        # clean up our stdout munging
-        os.dup2(self.old_stdout, sys.stdout.fileno())
-        os.close(self.pipes[0])
+        pi = ProcessInfo('Test Process', params)
+        pi.spawn()
+        time.sleep(wait)
+        os.dup2(self.stdout_old, sys.stdout.fileno())
+        os.dup2(self.stderr_old, sys.stderr.fileno())
+        self.assertNotEqual(pi.process, None)
+        self.assertTrue(type(pi.pid) is int)
+
+        # Set non-blocking read on pipes. Process may not print anything
+        # on specific output and the we would hang without this.
+        fd = self.stdout_pipes[0]
+        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+        fd = self.stderr_pipes[0]
+        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+        # There's potential problem if b10-dhcp4 prints out more
+        # than 4k of text
+        try:
+            output = os.read(self.stdout_pipes[0], 4096)
+        except OSError:
+            print("No data available from stdout")
+            output = ""
+
+        # read can return None. Make sure we have a string
+        if (output is None):
+            output = ""
+
+        try:
+            error = os.read(self.stderr_pipes[0], 4096)
+        except OSError:
+            print("No data available on stderr")
+            error = ""
+
+        # read can return None. Make sure we have a string
+        if (error is None):
+            error = ""
+
+        try:
+            if (not pi.process.poll()):
+                # let's be nice at first...
+                pi.process.terminate()
+        except OSError:
+            print("Ignoring failed kill attempt. Process is dead already.")
+
+        # call this to get returncode, process should be dead by now
+        rc = pi.process.wait()
+
+        # Clean up our stdout/stderr munging.
+        os.dup2(self.stdout_old, sys.stdout.fileno())
+        os.close(self.stdout_pipes[0])
+
+        os.dup2(self.stderr_old, sys.stderr.fileno())
+        os.close(self.stderr_pipes[0])
+
+        print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
+               % (rc, len(output), len(error)) )
+
+        return (rc, output, error)
 
     def test_alive(self):
         """
         Simple test. Checks that b10-dhcp6 can be started and prints out info 
         about starting DHCPv6 operation.
         """
-        pi = ProcessInfo('Test Process', [ '../b10-dhcp6' , '-v' ])
-        pi.spawn()
-        time.sleep(1)
-        os.dup2(self.old_stdout, sys.stdout.fileno())
-        self.assertNotEqual(pi.process, None)
-        self.assertTrue(type(pi.pid) is int)
-        output = os.read(self.pipes[0], 4096)
+        print("Note: Purpose of some of the tests is to check if DHCPv6 server can be started,")
+        print("      not that is can bind sockets correctly. Please ignore binding errors.")
+        (returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
+
         self.assertEqual( str(output).count("[b10-dhcp6] Initiating DHCPv6 operation."), 1)
 
-        # kill this process
-        # XXX: b10-dhcp6 is too dumb to understand 'shutdown' command for now,
-        #      so let's just kill the bastard
+    def test_portnumber_0(self):
+        print("Check that specifying port number 0 is not allowed.")
 
-        # TODO: Ignore errors for now. This test will be more thorough once ticket #1503
-        # (passing port number to b10-dhcp6 daemon) is implemented.
-        try:
-            os.kill(pi.pid, signal.SIGTERM)
-        except OSError:
-            print("Ignoring failed kill attempt. Process is dead already.")
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p', '0'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+    def test_portnumber_missing(self):
+        print("Check that -p option requires a parameter.")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("option requires an argument"), 1)
+
+    def test_portnumber_nonroot(self):
+        print("Check that specifying unprivileged port number will work.")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p', '10057'])
+
+        # When invalid port number is specified, return code must not be success
+        # TODO: Temporarily commented out as socket binding on systems that do not have
+        #       interface detection implemented currently fails.
+        # self.assertTrue(returncode == 0)
+
+        # Check that there is a message on stdout about opening proper port
+        self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
 
 if __name__ == '__main__':
     unittest.main()

+ 1 - 1
src/bin/host/host.cc

@@ -232,7 +232,7 @@ main(int argc, char* argv[]) {
     argv += optind;
 
     if (argc < 1) {
-        cout << "Usage: host [-adprv] [-c class] [-t type] hostname [server]\n";
+        cout << "Usage: host [-adrv] [-c class] [-p port] [-t type] hostname [server]\n";
         exit(1);
     }
 

+ 29 - 29
src/bin/resolver/resolver_messages.mes

@@ -152,6 +152,27 @@ the parsing of the body of the message failed due to some protocol error
 (although the parsing of the header succeeded).  The message parameters
 give a textual description of the problem and the RCODE returned.
 
+% RESOLVER_QUERY_ACCEPTED query accepted: '%1/%2/%3' from %4
+This debug message is produced by the resolver when an incoming query
+is accepted in terms of the query ACL.  The log message shows the query
+in the form of <query name>/<query type>/<query class>, and the client
+that sends the query in the form of <Source IP address>#<source port>.
+
+% RESOLVER_QUERY_DROPPED query dropped: '%1/%2/%3' from %4
+This is an informational message that indicates an incoming query has
+been dropped by the resolver because of the query ACL.  Unlike the
+RESOLVER_QUERY_REJECTED case, the server does not return any response.
+The log message shows the query in the form of <query name>/<query
+type>/<query class>, and the client that sends the query in the form of
+<Source IP address>#<source port>.
+
+% RESOLVER_QUERY_REJECTED query rejected: '%1/%2/%3' from %4
+This is an informational message that indicates an incoming query has
+been rejected by the resolver because of the query ACL.  This results
+in a response with an RCODE of REFUSED. The log message shows the query
+in the form of <query name>/<query type>/<query class>, and the client
+that sends the query in the form of <Source IP address>#<source port>.
+
 % RESOLVER_QUERY_SETUP query setup
 This is a debug message noting that the resolver is creating a
 RecursiveQuery object.
@@ -197,6 +218,10 @@ there comes a time - the lookup timeout - when even the resolver gives up.
 At this point it will wait for pending upstream queries to complete or
 timeout and drop the query.
 
+% RESOLVER_SET_QUERY_ACL query ACL is configured
+This debug message is generated when a new query ACL is configured for
+the resolver.
+
 % RESOLVER_SET_ROOT_ADDRESS setting root address %1(%2)
 This message gives the address of one of the root servers used by the
 resolver.  It is output during startup and may appear multiple times,
@@ -205,6 +230,10 @@ once for each root server address.
 % RESOLVER_SHUTDOWN resolver shutdown complete
 This informational message is output when the resolver has shut down.
 
+% RESOLVER_SHUTDOWN_RECEIVED received command to shut down
+A debug message noting that the server was asked to terminate and is
+complying to the request.
+
 % RESOLVER_STARTED resolver started
 This informational message is output by the resolver when all initialization
 has been completed and it is entering its main loop.
@@ -221,32 +250,3 @@ has been ignored.
 This is debug message output when the resolver received a message with an
 unsupported opcode (it can only process QUERY opcodes).  It will return
 a message to the sender with the RCODE set to NOTIMP.
-
-% RESOLVER_SET_QUERY_ACL query ACL is configured
-This debug message is generated when a new query ACL is configured for
-the resolver.
-
-% RESOLVER_QUERY_ACCEPTED query accepted: '%1/%2/%3' from %4
-This debug message is produced by the resolver when an incoming query
-is accepted in terms of the query ACL.  The log message shows the query
-in the form of <query name>/<query type>/<query class>, and the client
-that sends the query in the form of <Source IP address>#<source port>.
-
-% RESOLVER_QUERY_REJECTED query rejected: '%1/%2/%3' from %4
-This is an informational message that indicates an incoming query has
-been rejected by the resolver because of the query ACL.  This results
-in a response with an RCODE of REFUSED. The log message shows the query
-in the form of <query name>/<query type>/<query class>, and the client
-that sends the query in the form of <Source IP address>#<source port>.
-
-% RESOLVER_QUERY_DROPPED query dropped: '%1/%2/%3' from %4
-This is an informational message that indicates an incoming query has
-been dropped by the resolver because of the query ACL.  Unlike the
-RESOLVER_QUERY_REJECTED case, the server does not return any response.
-The log message shows the query in the form of <query name>/<query
-type>/<query class>, and the client that sends the query in the form of
-<Source IP address>#<source port>.
-
-% RESOLVER_SHUTDOWN_RECEIVED received command to shut down
-A debug message noting that the server was asked to terminate and is
-complying to the request.

+ 1 - 1
src/bin/sockcreator/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = tests
+SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 

+ 6 - 1
src/bin/stats/stats_httpd.py.in

@@ -189,7 +189,12 @@ class StatsHttpd:
         self.load_config()
         self.http_addrs = []
         self.mccs.start()
-        self.open_httpd()
+        try:
+            self.open_httpd()
+        except HttpServerError:
+            # if some exception, e.g. address in use, is raised, then it closes mccs and httpd
+            self.close_mccs()
+            raise
 
     def open_mccs(self):
         """Opens a ModuleCCSession object"""

+ 16 - 16
src/bin/stats/stats_httpd_messages.mes

@@ -24,14 +24,14 @@ The stats-httpd module was unable to connect to the BIND 10 command
 and control bus. A likely problem is that the message bus daemon
 (b10-msgq) is not running. The stats-httpd module will now shut down.
 
-% STATHTTPD_CLOSING_CC_SESSION stopping cc session
-Debug message indicating that the stats-httpd module is disconnecting
-from the command and control bus.
-
 % STATHTTPD_CLOSING closing %1#%2
 The stats-httpd daemon will stop listening for requests on the given
 address and port number.
 
+% STATHTTPD_CLOSING_CC_SESSION stopping cc session
+Debug message indicating that the stats-httpd module is disconnecting
+from the command and control bus.
+
 % STATHTTPD_HANDLE_CONFIG reading configuration: %1
 The stats-httpd daemon has received new configuration data and will now
 process it. The (changed) data is printed.
@@ -49,18 +49,18 @@ An unknown command has been sent to the stats-httpd module. The
 stats-httpd module will respond with an error, and the command will
 be ignored.
 
-% STATHTTPD_SERVER_ERROR HTTP server error: %1
-An internal error occurred while handling an HTTP request. An HTTP 500
-response will be sent back, and the specific error is printed. This
-is an error condition that likely points to a module that is not
-responding correctly to statistic requests.
-
 % STATHTTPD_SERVER_DATAERROR HTTP server data error: %1
 An internal error occurred while handling an HTTP request. An HTTP 404
 response will be sent back, and the specific error is printed. This
 is an error condition that likely points the specified data
 corresponding to the requested URI is incorrect.
 
+% STATHTTPD_SERVER_ERROR HTTP server error: %1
+An internal error occurred while handling an HTTP request. An HTTP 500
+response will be sent back, and the specific error is printed. This
+is an error condition that likely points to a module that is not
+responding correctly to statistic requests.
+
 % STATHTTPD_SERVER_INIT_ERROR HTTP server initialization error: %1
 There was a problem initializing the HTTP server in the stats-httpd
 module upon receiving its configuration data. The most likely cause
@@ -71,12 +71,6 @@ and an error is sent back.
 % STATHTTPD_SHUTDOWN shutting down
 The stats-httpd daemon is shutting down.
 
-% STATHTTPD_START_SERVER_INIT_ERROR HTTP server initialization error: %1
-There was a problem initializing the HTTP server in the stats-httpd
-module upon startup. The most likely cause is that it was not able
-to bind to the listening port. The specific error is printed, and the
-module will shut down.
-
 % STATHTTPD_STARTED listening on %1#%2
 The stats-httpd daemon will now start listening for requests on the
 given address and port number.
@@ -85,6 +79,12 @@ given address and port number.
 Debug message indicating that the stats-httpd module is connecting to
 the command and control bus.
 
+% STATHTTPD_START_SERVER_INIT_ERROR HTTP server initialization error: %1
+There was a problem initializing the HTTP server in the stats-httpd
+module upon startup. The most likely cause is that it was not able
+to bind to the listening port. The specific error is printed, and the
+module will shut down.
+
 % STATHTTPD_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
 There was a keyboard interrupt signal to stop the stats-httpd
 daemon. The daemon will now shut down.

+ 13 - 13
src/bin/stats/stats_messages.mes

@@ -28,6 +28,12 @@ control bus. A likely problem is that the message bus daemon
 This debug message is printed when the stats module has received a
 configuration update from the configuration manager.
 
+% STATS_RECEIVED_SHOWSCHEMA_ALL_COMMAND received command to show all statistics schema
+The stats module received a command to show all statistics schemas of all modules.
+
+% STATS_RECEIVED_SHOWSCHEMA_NAME_COMMAND received command to show statistics schema for %1
+The stats module received a command to show the specified statistics schema of the specified module.
+
 % STATS_RECEIVED_SHOW_ALL_COMMAND received command to show all statistics
 The stats module received a command to show all statistics that it has
 collected.
@@ -51,6 +57,13 @@ will respond with an error and the command will be ignored.
 This debug message is printed when a request is sent to the boss module
 to send its data to the stats module.
 
+% STATS_STARTING starting
+The stats module will be now starting.
+
+% STATS_START_ERROR stats module error: %1
+An internal error occurred while starting the stats module. The stats
+module will be now shutting down.
+
 % STATS_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
 There was a keyboard interrupt signal to stop the stats module. The
 daemon will now shut down.
@@ -61,16 +74,3 @@ is unknown in the implementation. The most likely cause is an
 installation problem, where the specification file stats.spec is
 from a different version of BIND 10 than the stats module itself.
 Please check your installation.
-
-% STATS_STARTING starting
-The stats module will be now starting.
-
-% STATS_RECEIVED_SHOWSCHEMA_ALL_COMMAND received command to show all statistics schema
-The stats module received a command to show all statistics schemas of all modules.
-
-% STATS_RECEIVED_SHOWSCHEMA_NAME_COMMAND received command to show statistics schema for %1
-The stats module received a command to show the specified statistics schema of the specified module.
-
-% STATS_START_ERROR stats module error: %1
-An internal error occurred while starting the stats module. The stats
-module will be now shutting down.

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

@@ -24,6 +24,7 @@ endif
 	B10_FROM_SOURCE=$(abs_top_srcdir) \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 	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 ; \
 	done
 

+ 18 - 0
src/bin/stats/tests/b10-stats-httpd_test.py

@@ -676,6 +676,24 @@ class TestStatsHttpd(unittest.TestCase):
         self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
         self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
         self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
+        ans = send_command(
+            isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
+            "ConfigManager", {"module_name":"StatsHttpd"})
+        # assert StatsHttpd is added to ConfigManager
+        self.assertNotEqual(ans, (0,{}))
+        self.assertTrue(ans[1]['module_name'], 'StatsHttpd')
+
+    def test_init_hterr(self):
+        orig_open_httpd = stats_httpd.StatsHttpd.open_httpd
+        def err_open_httpd(arg): raise stats_httpd.HttpServerError
+        stats_httpd.StatsHttpd.open_httpd = err_open_httpd
+        self.assertRaises(stats_httpd.HttpServerError, stats_httpd.StatsHttpd)
+        ans = send_command(
+            isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
+            "ConfigManager", {"module_name":"StatsHttpd"})
+        # assert StatsHttpd is removed from ConfigManager
+        self.assertEqual(ans, (0,{}))
+        stats_httpd.StatsHttpd.open_httpd = orig_open_httpd
 
     def test_openclose_mccs(self):
         self.stats_httpd = MyStatsHttpd(get_availaddr())

BIN
src/bin/xfrin/tests/testdata/example.com.sqlite3


+ 316 - 0
src/bin/xfrin/tests/xfrin_test.py

@@ -139,6 +139,9 @@ class MockCC(MockModuleCCSession):
         if identifier == "zones/use_ixfr":
             return False
 
+    def remove_remote_config(self, module_name):
+        pass
+
 class MockDataSourceClient():
     '''A simple mock data source client.
 
@@ -2575,6 +2578,134 @@ class TestXfrin(unittest.TestCase):
         self.common_ixfr_setup('refresh', False)
         self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
 
+class TestXfrinMemoryZones(unittest.TestCase):
+    def setUp(self):
+        self.xfr = MockXfrin()
+        # Configuration snippet containing 2 memory datasources,
+        # one for IN and one for CH. Both contain a zone 'example.com'
+        # the IN ds also contains a zone example2.com, and a zone example3.com,
+        # which is of file type 'text' (and hence, should be ignored)
+        self.config = { 'datasources': [
+                          { 'type': 'memory',
+                            'class': 'IN',
+                            'zones': [
+                              { 'origin': 'example.com',
+                                'filetype': 'sqlite3' },
+                              { 'origin': 'EXAMPLE2.com.',
+                                'filetype': 'sqlite3' },
+                              { 'origin': 'example3.com',
+                                'filetype': 'text' }
+                            ]
+                          },
+                          { 'type': 'memory',
+                            'class': 'ch',
+                            'zones': [
+                              { 'origin': 'example.com',
+                                'filetype': 'sqlite3' }
+                            ]
+                          }
+                      ] }
+
+    def test_updates(self):
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+        # add them all
+        self.xfr._set_memory_zones(self.config, None)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
+        # Remove the CH data source from the self.config snippet, and update
+        del self.config['datasources'][1]
+        self.xfr._set_memory_zones(self.config, None)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+        # Remove example2.com from the datasource, and update
+        del self.config['datasources'][0]['zones'][1]
+        self.xfr._set_memory_zones(self.config, None)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+        # If 'datasources' is not in the self.config update list (i.e. its
+        # self.config has not changed), no difference should be found
+        self.xfr._set_memory_zones({}, None)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+        # If datasources list becomes empty, everything should be removed
+        self.config['datasources'][0]['zones'] = []
+        self.xfr._set_memory_zones(self.config, None)
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+    def test_normalization(self):
+        self.xfr._set_memory_zones(self.config, None)
+        # make sure it is case insensitive, root-dot-insensitive,
+        # and supports CLASSXXX notation
+        self.assertTrue(self.xfr._is_memory_zone("EXAMPLE.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "in"))
+        self.assertTrue(self.xfr._is_memory_zone("example2.com.", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "CLASS3"))
+
+    def test_bad_name(self):
+        # First set it to some config
+        self.xfr._set_memory_zones(self.config, None)
+
+        # Error checking; bad owner name should result in no changes
+        self.config['datasources'][1]['zones'][0]['origin'] = ".."
+        self.xfr._set_memory_zones(self.config, None)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
+    def test_bad_class(self):
+        # First set it to some config
+        self.xfr._set_memory_zones(self.config, None)
+
+        # Error checking; bad owner name should result in no changes
+        self.config['datasources'][1]['class'] = "Foo"
+        self.xfr._set_memory_zones(self.config, None)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
+    def test_no_filetype(self):
+        # omitting the filetype should leave that zone out, but not
+        # the rest
+        del self.config['datasources'][1]['zones'][0]['filetype']
+        self.xfr._set_memory_zones(self.config, None)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
+
+    def test_class_filetype(self):
+        # omitting the class should have it default to what is in the
+        # specfile for Auth.
+        AuthConfigData = isc.config.config_data.ConfigData(
+            isc.config.module_spec_from_file(xfrin.AUTH_SPECFILE_LOCATION))
+        del self.config['datasources'][0]['class']
+        self.xfr._set_memory_zones(self.config, AuthConfigData)
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
+        self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
+        self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+
 def raise_interrupt():
     raise KeyboardInterrupt()
 
@@ -2606,6 +2737,44 @@ class TestMain(unittest.TestCase):
         MockXfrin.check_command_hook = raise_exception
         main(MockXfrin, False)
 
+class TestXfrinProcessMockCC:
+    def __init__(self):
+        self.get_called = False
+        self.get_called_correctly = False
+        self.config = []
+
+    def get_remote_config_value(self, module, identifier):
+        self.get_called = True
+        if module == 'Auth' and identifier == 'datasources':
+            self.get_called_correctly = True
+            return (self.config, False)
+        else:
+            return (None, True)
+
+class TestXfrinProcessMockCCSession:
+    def __init__(self):
+        self.send_called = False
+        self.send_called_correctly = False
+        self.recv_called = False
+        self.recv_called_correctly = False
+
+    def group_sendmsg(self, msg, module):
+        self.send_called = True
+        if module == 'Auth' and msg['command'][0] == 'loadzone':
+            self.send_called_correctly = True
+            seq = "random-e068c2de26d760f20cf10afc4b87ef0f"
+        else:
+            seq = None
+
+        return seq
+
+    def group_recvmsg(self, message, seq):
+        self.recv_called = True
+        if message == False and seq == "random-e068c2de26d760f20cf10afc4b87ef0f":
+            self.recv_called_correctly = True
+        # return values are ignored
+        return (None, None)
+
 class TestXfrinProcess(unittest.TestCase):
     """
     Some tests for the xfrin_process function. This replaces the
@@ -2621,6 +2790,8 @@ class TestXfrinProcess(unittest.TestCase):
 
         Also sets up several internal variables to watch what happens.
         """
+        self._module_cc = TestXfrinProcessMockCC()
+        self._send_cc_session = TestXfrinProcessMockCCSession()
         # This will hold a "log" of what transfers were attempted.
         self.__transfers = []
         # This will "log" if failures or successes happened.
@@ -2665,6 +2836,9 @@ class TestXfrinProcess(unittest.TestCase):
         Part of pretending to be the server as well. This just logs the
         success/failure of the previous operation.
         """
+        if ret == XFRIN_OK:
+            xfrin._do_auth_loadzone(self, zone_name, rrclass)
+
         self.__published.append(ret)
 
     def close(self):
@@ -2695,12 +2869,22 @@ class TestXfrinProcess(unittest.TestCase):
         # Create a connection for each attempt
         self.assertEqual(len(transfers), self.__created_connections)
         self.assertEqual([published], self.__published)
+        if published == XFRIN_OK:
+            self.assertTrue(self._module_cc.get_called)
+            self.assertTrue(self._module_cc.get_called_correctly)
+        else:
+            self.assertFalse(self._module_cc.get_called)
+            self.assertFalse(self._module_cc.get_called_correctly)
 
     def test_ixfr_ok(self):
         """
         Everything OK the first time, over IXFR.
         """
         self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
 
     def test_axfr_ok(self):
         """
@@ -2731,6 +2915,138 @@ class TestXfrinProcess(unittest.TestCase):
         """
         self.__do_test([XFRIN_FAIL, XFRIN_FAIL],
                        [RRType.IXFR(), RRType.AXFR()], RRType.IXFR())
+
+    def test_inmem_ok(self):
+        """
+        Inmem configuration where all the configuration is just right
+        for loadzone to be sent to b10-auth (origin is the name received
+        by xfrin, filetype is sqlite3, type is memory and class is the
+        one received by xfrin).
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
+                                              'file': 'data/inmem-xfrin.sqlite3'}],
+                                   'type': 'memory', 'class': 'IN'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertTrue(self._send_cc_session.send_called)
+        self.assertTrue(self._send_cc_session.send_called_correctly)
+        self.assertTrue(self._send_cc_session.recv_called)
+        self.assertTrue(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_datasource_type_not_memory(self):
+        """
+        Inmem configuration where the datasource type is not memory. In
+        this case, loadzone should not be sent to b10-auth.
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
+                                              'file': 'data/inmem-xfrin.sqlite3'}],
+                                   'type': 'punched-card', 'class': 'IN'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_datasource_type_is_missing(self):
+        """
+        Inmem configuration where the datasource type is missing. In
+        this case, loadzone should not be sent to b10-auth.
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
+                                              'file': 'data/inmem-xfrin.sqlite3'}],
+                                   'class': 'IN'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_backend_type_not_sqlite3(self):
+        """
+        Inmem configuration where the datasource backing file is not of
+        type sqlite3. In this case, loadzone should not be sent to
+        b10-auth.
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'postgresql',
+                                              'file': 'data/inmem-xfrin.db'}],
+                                   'type': 'memory', 'class': 'IN'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_backend_type_is_missing(self):
+        """
+        Inmem configuration where the datasource backing file type is
+        not set. In this case, loadzone should not be sent to b10-auth.
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'example.org',
+                                              'file': 'data/inmem-xfrin'}],
+                                   'type': 'memory', 'class': 'IN'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_class_is_different(self):
+        """
+        Inmem configuration where the datasource class does not match
+        the received class. In this case, loadzone should not be sent to
+        b10-auth.
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
+                                              'file': 'data/inmem-xfrin.sqlite3'}],
+                                   'type': 'memory', 'class': 'XX'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_class_is_missing(self):
+        """
+        Inmem configuration where the datasource class is missing. In
+        this case, we assume the IN class and loadzone may be sent to
+        b10-auth if everything else matches.
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'example.org', 'filetype': 'sqlite3',
+                                              'file': 'data/inmem-xfrin.sqlite3'}],
+                                   'type': 'memory'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertTrue(self._send_cc_session.send_called)
+        self.assertTrue(self._send_cc_session.send_called_correctly)
+        self.assertTrue(self._send_cc_session.recv_called)
+        self.assertTrue(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_name_doesnt_match(self):
+        """
+        Inmem configuration where the origin does not match the received
+        name. In this case, loadzone should not be sent to b10-auth.
+        """
+        self._module_cc.config = [{'zones': [{'origin': 'isc.org', 'filetype': 'sqlite3',
+                                              'file': 'data/inmem-xfrin.sqlite3'}],
+                                   'type': 'memory', 'class': 'IN'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
+
+    def test_inmem_name_is_missing(self):
+        """
+        Inmem configuration where the origin is missing. In this case,
+        loadzone should not be sent to b10-auth.
+        """
+        self._module_cc.config = [{'zones': [{'filetype': 'sqlite3',
+                                              'file': 'data/inmem-xfrin.sqlite3'}],
+                                   'type': 'memory', 'class': 'IN'}]
+        self.__do_test([XFRIN_OK], [RRType.IXFR()], RRType.IXFR())
+        self.assertFalse(self._send_cc_session.send_called)
+        self.assertFalse(self._send_cc_session.send_called_correctly)
+        self.assertFalse(self._send_cc_session.recv_called)
+        self.assertFalse(self._send_cc_session.recv_called_correctly)
+
 class TestFormatting(unittest.TestCase):
     # If the formatting functions are moved to a more general library
     # (ticket #1379), these tests should be moved with them.

+ 107 - 15
src/bin/xfrin/xfrin.py.in

@@ -33,11 +33,17 @@ import isc.util.process
 from isc.datasrc import DataSourceClient, ZoneFinder
 import isc.net.parse
 from isc.xfrin.diff import Diff
+from isc.server_common.auth_command import auth_loadzone_command
 from isc.log_messages.xfrin_messages import *
 
 isc.log.init("b10-xfrin")
 logger = isc.log.Logger("xfrin")
 
+# Pending system-wide debug level definitions, the ones we
+# use here are hardcoded for now
+DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
+DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
+
 try:
     from pydnspp import *
 except ImportError as e:
@@ -61,6 +67,7 @@ else:
 SPECFILE_LOCATION = SPECFILE_PATH + "/xfrin.spec"
 AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
 
+AUTH_MODULE_NAME = 'Auth'
 XFROUT_MODULE_NAME = 'Xfrout'
 ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
 REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
@@ -362,7 +369,6 @@ class XfrinFirstData(XfrinState):
                  conn.zone_str())
             # We are now going to add RRs to the new zone.  We need create
             # a Diff object.  It will be used throughtout the XFR session.
-            # DISABLE FOR DEBUG
             conn._diff = Diff(conn._datasrc_client, conn._zone_name, True)
             self.set_xfrstate(conn, XfrinAXFR())
         return False
@@ -1242,10 +1248,24 @@ class ZoneInfo:
         return (self.master_addr.family, socket.SOCK_STREAM,
                 (str(self.master_addr), self.master_port))
 
+def _do_auth_loadzone(server, zone_name, zone_class):
+    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:
     def __init__(self):
         self._max_transfers_in = 10
         self._zones = {}
+        # This is a set of (zone/class) tuples (both as strings),
+        # representing the in-memory zones maintaned by Xfrin. It
+        # is used to trigger Auth/in-memory so that it reloads
+        # zones when they have been transfered in
+        self._memory_zones = set()
         self._cc_setup()
         self.recorder = XfrinRecorder()
         self._shutdown_event = threading.Event()
@@ -1264,6 +1284,8 @@ class Xfrin:
         self._module_cc.start()
         config_data = self._module_cc.get_full_config()
         self.config_handler(config_data)
+        self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION,
+                                          self._auth_config_handler)
 
     def _cc_check_command(self):
         '''This is a straightforward wrapper for cc.check_command,
@@ -1310,10 +1332,78 @@ class Xfrin:
 
         return create_answer(0)
 
+    def _auth_config_handler(self, new_config, config_data):
+        # Config handler for changes in Auth configuration
+        self._set_db_file()
+        self._set_memory_zones(new_config, config_data)
+
+    def _clear_memory_zones(self):
+        """Clears the memory_zones set; called before processing the
+           changed list of memory datasource zones that have file type
+           sqlite3"""
+        self._memory_zones.clear()
+
+    def _is_memory_zone(self, zone_name_str, zone_class_str):
+        """Returns true if the given zone/class combination is configured
+           in the in-memory datasource of the Auth process with file type
+           'sqlite3'.
+           Note: this method is not thread-safe. We are considering
+           changing the threaded model here, but if we do not, take
+           care in accessing and updating the memory zone set (or add
+           locks)
+        """
+        # Normalize them first, if either conversion fails, return false
+        # (they won't be in the set anyway)
+        try:
+            zone_name_str = Name(zone_name_str).to_text().lower()
+            zone_class_str = RRClass(zone_class_str).to_text()
+        except Exception:
+            return False
+        return (zone_name_str, zone_class_str) in self._memory_zones
+
+    def _set_memory_zones(self, new_config, config_data):
+        """Part of the _auth_config_handler function, keeps an internal set
+           of zones in the datasources config subset that have 'sqlite3' as
+           their file type.
+           Note: this method is not thread-safe. We are considering
+           changing the threaded model here, but if we do not, take
+           care in accessing and updating the memory zone set (or add
+           locks)
+        """
+        # walk through the data and collect the memory zones
+        # If this causes any exception, assume we were passed bad data
+        # and keep the original set
+        new_memory_zones = set()
+        try:
+            if "datasources" in new_config:
+                for datasource in new_config["datasources"]:
+                    if "class" in datasource:
+                        ds_class = RRClass(datasource["class"])
+                    else:
+                        # Get the default
+                        ds_class = RRClass(config_data.get_default_value(
+                                               "datasources/class"))
+                    if datasource["type"] == "memory":
+                        for zone in datasource["zones"]:
+                            if "filetype" in zone and \
+                               zone["filetype"] == "sqlite3":
+                                zone_name = Name(zone["origin"])
+                                zone_name_str = zone_name.to_text().lower()
+                                new_memory_zones.add((zone_name_str,
+                                                      ds_class.to_text()))
+                # Ok, we can use the data, update our list
+                self._memory_zones = new_memory_zones
+        except Exception:
+            # Something is wrong with the data. If this data even reached us,
+            # we cannot do more than assume the real module has logged and
+            # reported an error. Keep the old set.
+            return
+
     def shutdown(self):
         ''' shutdown the xfrin process. the thread which is doing xfrin should be
         terminated.
         '''
+        self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
         self._module_cc.send_stopping()
         self._shutdown_event.set()
         main_thread = threading.currentThread()
@@ -1446,22 +1536,21 @@ class Xfrin:
         return (addr.family, socket.SOCK_STREAM, (str(addr), port))
 
     def _get_db_file(self):
-        #TODO, the db file path should be got in auth server's configuration
-        # if we need access to this configuration more often, we
-        # should add it on start, and not remove it here
-        # (or, if we have writable ds, we might not need this in
-        # the first place)
-        self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
-        db_file, is_default = self._module_cc.get_remote_config_value("Auth", "database_file")
+        return self._db_file
+
+    def _set_db_file(self):
+        db_file, is_default =\
+            self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
         if is_default and "B10_FROM_BUILD" in os.environ:
-            # this too should be unnecessary, but currently the
-            # 'from build' override isn't stored in the config
-            # (and we don't have writable datasources yet)
-            db_file = os.environ["B10_FROM_BUILD"] + os.sep + "bind10_zones.sqlite3"
-        self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
-        return db_file
+            # override the local database setting if it is default and we
+            # are running from the source tree
+            # This should be hidden inside the data source library and/or
+            # done as a configuration, and this special case should be gone).
+            db_file = os.environ["B10_FROM_BUILD"] + os.sep +\
+                      "bind10_zones.sqlite3"
+        self._db_file = db_file
 
-    def publish_xfrin_news(self, zone_name, zone_class,  xfr_result):
+    def publish_xfrin_news(self, zone_name, zone_class, xfr_result):
         '''Send command to xfrout/zone manager module.
         If xfrin has finished successfully for one zone, tell the good
         news(command: zone_new_data_ready) to zone manager and xfrout.
@@ -1470,6 +1559,7 @@ class Xfrin:
         param = {'zone_name': zone_name.to_text(),
                  'zone_class': zone_class.to_text()}
         if xfr_result == XFRIN_OK:
+            _do_auth_loadzone(self, zone_name, zone_class)
             msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
             # catch the exception, in case msgq has been killed.
             try:
@@ -1488,6 +1578,7 @@ class Xfrin:
                     pass        # for now we just ignore the failure
             except socket.error as err:
                 logger.error(XFRIN_MSGQ_SEND_ERROR, XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME)
+
         else:
             msg = create_command(ZONE_XFRIN_FAILED, param)
             # catch the exception, in case msgq has been killed.
@@ -1502,6 +1593,7 @@ class Xfrin:
                 logger.error(XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER, ZONE_MANAGER_MODULE_NAME)
 
     def startup(self):
+        logger.debug(DBG_PROCESS, XFRIN_STARTED)
         while not self._shutdown_event.is_set():
             self._cc_check_command()
 

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


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