diff --git a/.gitignore b/.gitignore
index da9d3b4bdb..3480cb68f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+*.gcda
+*.gcno
+*.gcov
*.la
*.lo
*.o
@@ -28,4 +31,7 @@ TAGS
/py-compile
/stamp-h1
+/all.info
+/coverage-cpp-html
/dns++.pc
+/report.info
diff --git a/AUTHORS b/AUTHORS
index e69de29bb2..67cb090193 100644
--- a/AUTHORS
+++ b/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
diff --git a/ChangeLog b/ChangeLog
index 7f76c0c487..38e1eb5940 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,9 +1,108 @@
-4XX. [func]* tomek
+450. [func]* tomek
b10-dhcp4: DHCPv4 server component is now integrated into
BIND10 framework. It can be started from BIND10 (using bindctl)
and can receive commands. The only supported command for now
is 'Dhcp4 shutdown'.
+bind10-devel-20120621 released on June 21. 2012
+
+449. [bug] muks
+ b10-xfin: fixed a bug where xfrin sent the wrong notification
+ message to zonemgr on successful zone transfer. This also
+ solves other reported problems such as too frequent attempts
+ of zone refreshing (see Trac #1786 and #1834).
+ (Trac #2023, git b5fbf8a408a047a2552e89ef435a609f5df58d8c)
+
+448. [func] team
+ b10-ddns is now functional and handles dynamic update requests
+ per RFC 2136. See BIND 10 guide for configuration and operation
+ details.
+ (Multiple Trac tickets)
+
+447. [bug] jinmei
+ Fixed a bug in b10-xfrout where a helper thread could fall into
+ an infinite loop if b10-auth stops while the thread is waiting for
+ forwarded requests from b10-auth.
+ (Trac #988 and #1833, git 95a03bbefb559615f3f6e529d408b749964d390a)
+
+446. [bug] muks
+ A number of warnings reported by Python about unclosed file and
+ socket objects were fixed. Some related code was also made safer.
+ (Trac #1828, git 464682a2180c672f1ed12d8a56fd0a5ab3eb96ed)
+
+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.
@@ -37,8 +136,8 @@
(Trac #1843, git 551657702a4197ef302c567b5c0eaf2fded3e121)
428. [bug] marcin
- perfdhcp: bind to local address to allow reception of replies from IPv6
- DHCP servers.
+ perfdhcp: bind to local address to allow reception of
+ replies from IPv6 DHCP servers.
(Trac #1908, git 597e059afaa4a89e767f8f10d2a4d78223af3940)
427. [bug] jinmei
@@ -48,10 +147,11 @@
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 #1891, git 672f129700dae33b701bb02069cf276238d66be3)
+ (Trac #1781,#1788,#1891, git 672f129700dae33b701bb02069cf276238d66be3)
426. [bug] vorner
- The NSEC3 records are now included when transferring a signed zone out.
+ The NSEC3 records are now included when transferring a
+ signed zone out.
(Trac #1782, git 36efa7d10ecc4efd39d2ce4dfffa0cbdeffa74b0)
425. [func]* muks
@@ -202,7 +302,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
diff --git a/Makefile.am b/Makefile.am
index 54216b612a..7024294276 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -16,6 +16,26 @@ 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
+
+.PHONY: check-valgrind check-valgrind-suppress
+
+check-valgrind:
+if HAVE_VALGRIND
+ @VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --track-origins=yes --num-callers=48 --leak-check=full --fullpath-after=" \
+ make -C $(abs_top_builddir) check
+else
+ @echo "*** Valgrind is required for check-valgrind ***"; exit 1;
+endif
+
+check-valgrind-suppress:
+if HAVE_VALGRIND
+ @VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --error-exitcode=1 --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions --suppressions=$(abs_top_srcdir)/src/valgrind-suppressions.revisit --num-callers=48 --leak-check=full --fullpath-after=" \
+ make -C $(abs_top_builddir) check
+else
+ @echo "*** Valgrind is required for check-valgrind-suppress ***"; exit 1;
+endif
+
clean-cpp-coverage:
@if [ $(USE_LCOV) = yes ] ; then \
$(LCOV) --directory . --zerocounters; \
@@ -405,3 +425,5 @@ EXTRA_DIST += ext/coroutine/coroutine.h
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = dns++.pc
+
+CLEANFILES = $(abs_top_builddir)/logger_lockfile
diff --git a/README b/README
index 3f6892395e..4ef941e886 100644
--- a/README
+++ b/README
@@ -2,17 +2,14 @@
This is the source for the development version of BIND 10.
BIND is the popular implementation of a DNS server, developer
-interfaces, and DNS tools. BIND 10 is a rewrite of BIND 9. BIND 10
-is written in C++ and Python and provides a modular environment
-for serving, maintaining, and developing DNS.
+interfaces, and DNS tools. BIND 10 is a rewrite of BIND 9 and ISC
+DHCP. BIND 10 is written in C++ and Python and provides a modular
+environment for serving, maintaining, and developing DNS and DHCP.
BIND10-devel is new development leading up to the production
BIND 10 release. It contains prototype code and experimental
interfaces. Nevertheless it is ready to use now for testing the
-new BIND 10 infrastructure ideas. The Year 3 goals of the five
-year plan are described here:
-
- http://bind10.isc.org/wiki/Year3Goals
+new BIND 10 infrastructure ideas.
This release includes the bind10 master process, b10-msgq message
bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
diff --git a/compatcheck/Makefile.am b/compatcheck/Makefile.am
index 15ef017008..6cc4036161 100644
--- a/compatcheck/Makefile.am
+++ b/compatcheck/Makefile.am
@@ -1,12 +1,17 @@
-# We're going to abuse install-data-local for a pre-install check.
-# This is to be considered a short term hack and is expected to be removed
-# in a near future version.
+# We're going to abuse install-data-local for a pre-install check. This may
+# not be the cleanest way to do this type of job, but that's the least ugly
+# one we've found.
+#
+# Note also that if any test needs to examine some file that has possibly
+# been installed before (e.g., older DB or configuration file), it should be
+# referenced with the prefix of DESTDIR. Otherwise
+# 'make DESTDIR=/somewhere install' may not work.
install-data-local:
- if test -e $(localstatedir)/$(PACKAGE)/zone.sqlite3; then \
+ if test -e $(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3; then \
$(SHELL) $(top_builddir)/src/bin/dbutil/run_dbutil.sh --check \
- $(localstatedir)/$(PACKAGE)/zone.sqlite3 || \
+ $(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 " \
- "$(localstatedir)/$(PACKAGE)/zone.sqlite3"; exit 1) \
+ "$(DESTDIR)$(localstatedir)/$(PACKAGE)/zone.sqlite3"; exit 1) \
fi
diff --git a/configure.ac b/configure.ac
index af9125f5af..70df25d7e8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -362,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])]
)
]
)
@@ -407,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"
@@ -982,6 +982,15 @@ AC_ARG_ENABLE(logger-checks, [AC_HELP_STRING([--enable-logger-checks],
AM_CONDITIONAL(ENABLE_LOGGER_CHECKS, test x$enable_logger_checks != xno)
AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Check logger messages?])])
+# Check for valgrind
+AC_PATH_PROG(VALGRIND, valgrind, no)
+AM_CONDITIONAL(HAVE_VALGRIND, test "x$VALGRIND" != "xno")
+
+found_valgrind="not found"
+if test "x$VALGRIND" != "xno"; then
+ found_valgrind="found"
+fi
+
AC_CONFIG_FILES([Makefile
doc/Makefile
doc/guide/Makefile
@@ -1067,6 +1076,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
@@ -1120,6 +1131,7 @@ 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
@@ -1184,6 +1196,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
@@ -1232,6 +1245,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
@@ -1287,8 +1301,10 @@ Features:
Developer:
Google Tests: $gtest_path
+ Valgrind: $found_valgrind
C++ Code Coverage: $USE_LCOV
Python Code Coverage: $USE_PYCOVERAGE
+ Logger checks: $enable_logger_checks
Generate Manuals: $enable_man
END
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 8730ae4c8d..6d91bf273d 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -579,7 +579,7 @@ INPUT = ../src/lib/exceptions ../src/lib/cc \
../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/bin/dhcp4 devel
+ ../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
diff --git a/doc/guide/Makefile.am b/doc/guide/Makefile.am
index ffe89c9437..7d90d37b6b 100644
--- a/doc/guide/Makefile.am
+++ b/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
diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html
index b2286f4602..7a1a120a7b 100644
--- a/doc/guide/bind10-guide.html
+++ b/doc/guide/bind10-guide.html
@@ -1,4 +1,4 @@
-
BIND 10 Guide
BIND 10 Guide
Administrator Reference for BIND 10
This is the reference guide for BIND 10 version
+
BIND 10 Guide
BIND 10 Guide
Administrator Reference for BIND 10
This is the reference guide for BIND 10 version
20120405.
BIND 10 is a framework that features Domain Name System
(DNS) suite and Dynamic Host Configuration Protocol (DHCP)
servers managed by Internet Systems Consortium (ISC). It
@@ -10,9 +10,9 @@
The most up-to-date version of this document (in PDF, HTML,
and plain text formats), along with other documents for
BIND 10, can be found at http://bind10.isc.org/docs.
-
BIND is the popular implementation of a DNS server, developer
interfaces, and DNS tools.
BIND 10 is a rewrite of BIND 9. BIND 10 is written in C++ and Python
@@ -23,10 +23,11 @@
This guide covers the experimental prototype of
BIND 10 version 20120405.
-
1.1. Supported Platforms
- BIND 10 builds have been tested on Debian GNU/Linux 5 and unstable,
- Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, CentOS
- Linux 5.3, and MacOS 10.6.
+
1.1. Supported Platforms
+ BIND 10 builds have been tested on (in no particular order)
+ Debian GNU/Linux 5 and unstable, Ubuntu 9.10, NetBSD 5,
+ Solaris 10 and 11, FreeBSD 7 and 8, CentOS Linux 5.3,
+ MacOS 10.6 and 10.7, and OpenBSD 5.1.
It has been tested on Sparc, i386, and amd64 hardware
platforms.
@@ -51,11 +52,13 @@
It needs at least SQLite version 3.3.9.
- The b10-xfrin, b10-xfrout,
- and b10-zonemgr 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 b10-ddns, b10-xfrin,
+ b10-xfrout, and b10-zonemgr
+ components require the libpython3 library and the Python
+ _sqlite3.so module (which is included with Python).
+ The b10-stats-httpd component uses the
+ Python pyexpat.so module.
+ The Python modules need to be built for the corresponding Python 3.
Note
Some operating systems do not provide these dependencies
in their default installation nor standard packages
@@ -89,6 +92,12 @@
b10-cmdctl —
Command and control service.
This process allows external control of the BIND 10 system.
+
+ b10-ddns —
+ Dynamic DNS update service.
+ This process is used to handle incoming DNS update
+ requests to allow granted clients to update zones
+ for which BIND 10 is serving as a primary server.
b10-msgq —
Message bus daemon.
@@ -162,7 +171,7 @@
and, of course, DNS. These include detailed developer
documentation and code examples.
-
In addition to the run-time requirements, building BIND 10
from source code requires various development include headers.
Note
@@ -224,14 +233,14 @@
the Git code revision control system or as a downloadable
tar file. It may also be available in pre-compiled ready-to-use
packages from operating system vendors.
-
2.3.1. Download Tar File
+
2.3.1. Download Tar File
Downloading a release tar file is the recommended method to
obtain the source code.
The BIND 10 releases are available as tar file downloads from
ftp://ftp.isc.org/isc/bind10/.
Periodic development snapshots may also be available.
-
2.3.2. Retrieve from Git
+
2.3.2. Retrieve from Git
Downloading this "bleeding edge" code is recommended only for
developers or advanced users. Using development code in a production
environment is not recommended.
@@ -265,7 +274,7 @@
autoheader,
automake,
and related commands.
-
2.3.3. Configure before the build
+
2.3.3. Configure before the build
BIND 10 uses the GNU Build System to discover build environment
details.
To generate the makefiles using the defaults, simply run:
@@ -296,16 +305,16 @@
If the configure fails, it may be due to missing or old
dependencies.
-
2.3.4. Build
+
2.3.4. Build
After the configure step is complete, to build the executables
from the C++ code and prepare the Python scripts, run:
$ make
-
2.3.5. Install
+
2.3.5. Install
To install the BIND 10 executables, support files,
and documentation, run:
$ make install
-
Note
The install step may require superuser privileges.
2.3.6. Install Hierarchy
+
Note
The install step may require superuser privileges.
2.3.6. Install Hierarchy
The following is the layout of the complete BIND 10 installation:
bin/ —
@@ -397,7 +406,7 @@
during startup or shutdown. Unless specified, the component is started
in usual way. This is the list of components that need to be started
in a special way, with the value of special used for them:
-
Table 3.1.
Component
Special
Description
b10-auth
auth
Authoritative server
b10-resolver
resolver
The resolver
b10-cmdctl
cmdctl
The command control (remote control interface)
+
Table 3.1.
Component
Special
Description
b10-auth
auth
Authoritative server
b10-resolver
resolver
The resolver
b10-cmdctl
cmdctl
The command control (remote control interface)
The kind specifies how a failure of the component should
be handled. If it is set to “dispensable”
@@ -425,7 +434,7 @@
message bus. The special components already know their
address, but the usual ones don't. The address is by
convention the thing after b10-, with
- the first letter capital (eg. b10-stats
+ the first letter capitalized (eg. b10-stats
would have “Stats” as its address).
@@ -625,12 +634,12 @@ shutdown
the details and relays (over a b10-msgq command
channel) the configuration on to the specified module.
The b10-auth is the authoritative DNS server.
It supports EDNS0 and DNSSEC. It supports IPv6.
Normally it is started by the bind10 master
process.
-
8.1. Server Configurations
+
8.1. Server Configurations
b10-auth is configured via the
b10-cfgmgr configuration manager.
The module name is “Auth”.
@@ -649,9 +658,10 @@ This may be a temporary setting until then.
class to optionally select the class
(it defaults to “IN”);
and
- zones to define the
- file path name and the
- origin (default domain).
+ zones to define
+ the file path name,
+ the filetype (e.g., sqlite3),
+ and the origin (default domain).
By default, this is empty.
@@ -661,7 +671,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 named-compilezone -D.
+ generated by named-compilezone -D, or
+ must be an SQLite3 database.
listen_on
@@ -671,6 +682,21 @@ This may be a temporary setting until then.
and port number.
By default, b10-auth listens on port 53
on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses.
+
Note
+ 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.
+
+ 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 — 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.
+
statistics-interval
statistics-interval is the timer interval
in seconds for b10-auth to share its
@@ -710,7 +736,7 @@ This may be a temporary setting until then.
if configured.)
-
8.2. Data Source Backends
Note
+
8.2. Data Source Backends
Note
For the development prototype release, b10-auth
supports a SQLite3 data source backend and in-memory data source
backend.
@@ -742,7 +768,26 @@ This may be a temporary setting until then.
The authoritative server will begin serving it immediately
after it is loaded.
-
+
8.2.2. In-memory Data Source With SQLite3 Backend
+
+ The following commands to bindctl
+ provide an example of configuring an in-memory data
+ source containing the “example.org” zone
+ with a SQLite3 backend file named “example.org.sqlite3”:
+
+
+
+
> config add Auth/datasources
+> config set Auth/datasources[1]/type "memory"
+> config add Auth/datasources[1]/zones
+> config set Auth/datasources[1]/zones[0]/origin "example.org"
+> config set Auth/datasources[1]/zones[0]/file "example.org.sqlite3"
+> config set Auth/datasources[1]/zones[0]/filetype "sqlite3"
+> config commit
+
+ The authoritative server will begin serving it immediately
+ after it is loaded.
+
8.2.3. Reloading an In-memory Data Source
Use the Auth loadzone command in
bindctl to reload a changed master
file into memory; for example:
@@ -750,7 +795,7 @@ This may be a temporary setting until then.
> Auth loadzone origin="example.com"
-
+
8.2.4. Disabling In-memory Data Sources
By default, the memory data source is disabled; it must be
configured explicitly. To disable all the in-memory zones,
specify a null list for Auth/datasources:
@@ -770,7 +815,7 @@ This may be a temporary setting until then.
and/or zones[0]
for the relevant zone as needed.)
-
8.3. Loading Master Zones Files
+
8.3. Loading Master Zones Files
RFC 1035 style DNS master zone files may imported
into a BIND 10 SQLite3 data source by using the
b10-loadzone utility.
@@ -799,7 +844,7 @@ This may be a temporary setting until then.
If you reload a zone already existing in the database,
all records from that prior zone disappear and a whole new set
appears.
-
Incoming zones are transferred using the b10-xfrin
process which is started by bind10.
When received, the zone is stored in the corresponding BIND 10
@@ -813,11 +858,7 @@ This may be a temporary setting until then.
IXFR. Due to some implementation limitations of the current
development release, however, it only tries AXFR by default,
and care should be taken to enable IXFR.
-
Note
- 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.
-
9.1. Configuration for Incoming Zone Transfers
+
9.1. Configuration for Incoming Zone Transfers
In practice, you need to specify a list of secondary zones to
enable incoming zone transfers for these zones (you can still
trigger a zone transfer manually, without a prior configuration
@@ -833,7 +874,7 @@ This may be a temporary setting until then.
> config commit
(We assume there has been no zone configuration before).
-
9.2. Enabling IXFR
+
9.2. Enabling IXFR
As noted above, b10-xfrin uses AXFR for
zone transfers by default. To enable IXFR for zone transfers
for a particular zone, set the use_ixfr
@@ -885,12 +926,24 @@ This may be a temporary setting until then.
(i.e. no SOA record for it), b10-zonemgr
will automatically tell b10-xfrin
to transfer the zone in.
-
9.4. Trigger an Incoming Zone Transfer Manually
+
9.4. Trigger an Incoming Zone Transfer Manually
To manually trigger a zone transfer to retrieve a remote zone,
you may use the bindctl utility.
For example, at the bindctl prompt run:
+ 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, b10-auth is
+ automatically sent a loadzone command to
+ reload the corresponding zone into memory from the backend.
+
+ BIND 10 supports the server side of the Dynamic DNS Update
+ (DDNS) protocol as defined in RFC 2136.
+ This service is provided by the b10-ddns
+ component, which is started by the bind10
+ process if configured so.
+
+ When the b10-auth authoritative DNS server
+ receives an UPDATE request, it internally forwards the request
+ to b10-ddns, which handles the rest of
+ request processing.
+ When the processing is completed b10-ddns
+ will send a response to the client with the RCODE set to the
+ value as specified in RFC 2136 (NOERROR for successful update,
+ REFUSED if rejected due to ACL check, etc).
+ If the zone has been changed as a result, it will internally
+ notify b10-xfrout so that other secondary
+ servers will be notified via the DNS notify protocol.
+ In addition, if b10-auth serves the updated
+ zone from its in-memory cache (as described in
+ Section 8.2.2, “In-memory Data Source With SQLite3 Backend”),
+ b10-ddns will also
+ notify b10-auth so that b10-auth
+ will re-cache the updated zone content.
+
+ The b10-ddns component supports requests over
+ both UDP and TCP, and both IPv6 and IPv4; for TCP requests,
+ however, it terminates the TCP connection immediately after
+ each single request has been processed. Clients cannot reuse the
+ same TCP connection for multiple requests. (This is a current
+ implementation limitation of b10-ddns.
+ While RFC 2136 doesn't specify anything about such reuse of TCP
+ connection, there is no reason for disallowing it as RFC 1035
+ generally allows multiple requests sent over a single TCP
+ connection. BIND 9 supports such reuse.)
+
+ As of this writing b10-ddns does not support
+ update forwarding for secondary zones.
+ If it receives an update request for a secondary zone, it will
+ immediately return a response with an RCODE of NOTIMP.
+
Note
+ For feature completeness update forwarding should be
+ eventually supported. But right now it's considered a lower
+ priority task and there is no specific plan of implementing
+ this feature.
+
+
+
11.1. Enabling Dynamic Update
+ First off, it must be made sure that a few components on which
+ b10-ddns depends are configured to run,
+ which are b10-auth
+ and b10-zonemgr.
+ In addition, b10-xfrout should also be
+ configured to run; otherwise the notification after an update
+ (see above) will fail with a timeout, suspending the DDNS
+ service while b10-ddns waits for the
+ response (see the description of the DDNS_UPDATE_NOTIFY_FAIL
+ log message for further details).
+ If BIND 10 is already configured to provide authoritative DNS
+ service they should normally be configured to run already.
+
+ Second, for the obvious reason dynamic update requires that the
+ underlying data source storing the zone data be writable.
+ In the current implementation this means the zone must be stored
+ in an SQLite3-based data source.
+ Also, right now, the b10-ddns component
+ configures itself with the data source referring to the
+ “database_file” configuration parameter of
+ b10-auth.
+ So this information must be configured correctly before starting
+ b10-ddns.
+
+
Note
+ The way to configure data sources is now being revised.
+ Configuration on the data source for DDNS will be very
+ likely to be changed in a backward incompatible manner in
+ a near future version.
+
+
+ In general, if something goes wrong regarding the dependency
+ described above, b10-ddns will log the
+ related event at the warning or error level.
+ It's advisable to check the log message when you first enable
+ DDNS or if it doesn't work as you expect to see if there's any
+ warning or error log message.
+
+ Next, to enable the DDNS service, b10-ddns
+ needs to be explicitly configured to run.
+ It can be done by using the bindctl
+ utility. For example:
+
+> config add Boss/components b10-ddns
+> config set Boss/components/b10-ddns/address DDNS
+> config set Boss/components/b10-ddns/kind dispensable
+> config commit
+
+
Note
+ In theory "kind" could be omitted because "dispensable" is its
+ default. But there's some peculiar behavior (which should
+ be a bug and should be fixed eventually; see Trac ticket
+ #2064) with bindctl and you'll still need to specify that explicitly.
+ Likewise, "address" may look unnecessary because
+ b10-ddns would start and work without
+ specifying it. But for it to shutdown gracefully this
+ parameter should also be specified.
+
+
11.2. Access Control
+ By default b10-ddns rejects any update
+ requests from any clients by returning a response with an RCODE
+ of REFUSED.
+ To allow updates to take effect, an access control rule
+ (called update ACL) with a policy allowing updates must explicitly be
+ configured.
+ Update ACL must be configured per zone basis in the
+ “zones” configuration parameter of
+ b10-ddns.
+ This is a list of per-zone configurations regarding DDNS.
+ Each list element consists of the following parameters:
+
origin
The zone's origin name
class
The RR class of the zone
+ (normally “IN”, and in that case
+ can be omitted in configuration)
update_acl
List of access control rules (ACL) for the zone
+ The syntax of the ACL is the same as ACLs for other
+ components.
+ Specific examples are given below.
+
+ In general, an update ACL rule that allows an update request
+ should be configured with a TSIG key.
+ This is an example update ACL that allows updates to the zone
+ named “example.org” of RR class “IN”
+ from clients that send requests signed with a TSIG whose
+ key name is "key.example.org" (and refuses all others):
+
+> config add DDNS/zones
+> config set DDNS/zones[0]/origin example.org
+> config set DDNS/zones[0]/class IN
+(Note: "class" can be omitted)
+> config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "key": "key.example.org"}
+> config commit
+
+ Multiple rules can be specified in the ACL, and an ACL rule
+ can consist of multiple constraints, such as a combination of
+ IP address and TSIG.
+ The following configuration sequence will add a new rule to
+ the ACL created in the above example. This additional rule
+ allows update requests sent from a client
+ using TSIG key name of "key.example" (different from the
+ key used in the previous example) and has an IPv6 address of ::1.
+
+ (Note the "add" in the first line. Before this sequence, we
+ have had only entry in zones[0]/update_acl. The "add" command
+ with a value (rule) adds a new entry and sets it to the given rule.
+ Due to a limitation of the current implementation, it doesn't
+ work if you first try to just add a new entry and then set it to
+ a given rule).
+
Note
+ The b10-ddns component accepts an ACL
+ rule that just allows updates from a specific IP address
+ (i.e., without requiring TSIG), but this is highly
+ discouraged (remember that requests can be made over UDP and
+ spoofing the source address of a UDP packet is often pretty
+ easy).
+ Unless you know what you are doing and that you can accept
+ its consequence, any update ACL rule that allows updates
+ should have a TSIG key in its constraints.
+
+ The ACL rules will be checked in the listed order, and the
+ first matching one will apply.
+ If none of the rules matches, the default rule will apply,
+ which is rejecting any requests in the case of
+ b10-ddns.
+
+ Other actions than "ACCEPT", namely "REJECT" and "DROP", can be
+ used, too.
+ See Chapter 12, Recursive Name Server about their effects.
+
+ Currently update ACL can only control updates per zone basis;
+ it's not possible to specify access control with higher
+ granularity such as for particular domain names or specific
+ types of RRs.
+
+
Note
+ Contrary to what RFC 2136 (literally) specifies,
+ b10-ddns checks the update ACL before
+ checking the prerequisites of the update request.
+ This is a deliberate implementation decision.
+ This counter intuitive specification has been repeatedly
+ discussed among implementers and in the IETF, and it is now
+ widely agreed that it does not make sense to strictly follow
+ that part of RFC.
+ One known specific bad result of following the RFC is that it
+ could leak information about which name or record exists or does not
+ exist in the zone as a result of prerequisite checks even if a
+ zone is somehow configured to reject normal queries from
+ arbitrary clients.
+ There have been other troubles that could have been avoided if
+ the ACL could be checked before the prerequisite check.
+
11.3. Miscellaneous Operational Issues
+ Unlike BIND 9, BIND 10 currently does not support automatic
+ resigning of DNSSEC-signed zone when it's updated via DDNS.
+ It could be possible to resign the updated zone afterwards
+ or make sure the update request also updates related DNSSEC
+ records, but that will be pretty error-prone operation.
+ In general, it's not advisable to allow DDNS for a signed zone
+ at this moment.
+
+ Also unlike BIND 9, it's currently not possible
+ to “freeze” a zone temporarily in order to
+ suspend DDNS while you manually update the zone.
+ If you need to make manual updates to a dynamic zone,
+ you'll need to temporarily reject any updates to the zone via
+ the update ACLs.
+
+ Dynamic updates are only applicable to primary zones.
+ In order to avoid updating secondary zones via DDNS requests,
+ b10-ddns refers to the
+ “secondary_zones” configuration of
+ b10-zonemgr. Zones listed in
+ “secondary_zones” will never be updated via DDNS
+ regardless of the update ACL configuration;
+ b10-ddns will return a response with an
+ RCODE of NOTAUTH as specified in RFC 2136.
+ If you have a "conceptual" secondary zone whose content is a
+ copy of some external source but is not updated via the
+ standard zone transfers and therefore not listed in
+ “secondary_zones”, be careful not to allow DDNS
+ for the zone; it would be quite likely to lead to inconsistent
+ state between different servers.
+ Normally this should not be a problem because the default
+ update ACL rejects any update requests, but you may want to
+ take an extra care about the configuration if you have such
+ type of secondary zones.
+
+ The difference of two versions of a zone, before and after a
+ DDNS transaction, is automatically recorded in the underlying
+ data source, and can be retrieved in the form of outbound
+ IXFR.
+ This is done automatically; it does not require specific
+ configuration to make this possible.
+
The b10-resolver process is started by
bind10.
@@ -968,7 +1268,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
(Replace the “2”
as needed; run “config show
- Resolver/listen_on” if needed.)
11.1. Access Control
+ Resolver/listen_on” if needed.)
12.1. Access Control
By default, the b10-resolver daemon only accepts
DNS queries from the localhost (127.0.0.1 and ::1).
The Resolver/query_acl configuration may
@@ -1001,7 +1301,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
(Replace the “2”
as needed; run “config show
Resolver/query_acl” if needed.)
Note
This prototype access control configuration
- syntax may be changed.
11.2. Forwarding
+ syntax may be changed.
12.2. Forwarding
To enable forwarding, the upstream address and port must be
configured to forward queries to, such as:
@@ -1021,7 +1321,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
> config set Resolver/forward_addresses []
> config commit
Dynamic Host Configuration Protocol for IPv4 (DHCP or
DHCPv4) and Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
are protocols that allow one node (server) to provision
configuration parameters to many hosts and devices (clients). To
@@ -1031,7 +1331,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
somewhat similar, these are two radically different
protocols. BIND10 offers server implementations for both DHCPv4
and DHCPv6. This chapter is about DHCP for IPv4. For a description
- of the DHCPv6 server, see Chapter 13, DHCPv6 Server.
The DHCPv4 server component is currently under intense
+ of the DHCPv6 server, see Chapter 14, DHCPv6 Server.
BIND10 provides the DHCPv4 server component since December
+
13.1. DHCPv4 Server Usage
BIND10 provides the DHCPv4 server component since December
2011. It is a skeleton server and can be described as an early
prototype that is not fully functional yet. It is mature enough
to conduct first tests in lab environment, but it has
- significant limitations. See Section 12.4, “DHCPv4 Server Limitations” for
+ significant limitations. See Section 13.4, “DHCPv4 Server Limitations” for
details.
b10-dhcp4 is a BIND10 component and is being
@@ -1078,7 +1378,7 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
be started directly, but rather via
bind10. Please be aware of this planned
change.
-
12.2. DHCPv4 Server Configuration
+
13.2. DHCPv4 Server Configuration
The DHCPv4 server does not have a lease database implemented yet
nor any support for configuration, so every time the same set
of configuration options (including the same fixed address)
@@ -1098,12 +1398,12 @@ const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
const std::string HARDCODED_SERVER_ID = "192.0.2.1";
Lease database and configuration support is planned for 2012.
-
12.3. Supported standards
The following standards and draft standards are currently
+
13.3. Supported standards
The following standards and draft standards are currently
supported:
RFC2131: Supported messages are DISCOVER, OFFER,
REQUEST, and ACK.
RFC2132: Supported options are: PAD (0),
END(255), Message Type(53), DHCP Server Identifier (54),
Domain Name (15), DNS Servers (6), IP Address Lease Time
- (51), Subnet mask (1), and Routers (3).
12.4. DHCPv4 Server Limitations
These are the current limitations of the DHCPv4 server
+ (51), Subnet mask (1), and Routers (3).
13.4. DHCPv4 Server Limitations
These are the current limitations of the DHCPv4 server
software. Most of them are reflections of the early stage of
development and should be treated as “not implemented
yet”, rather than actual limitations.
During initial IPv4 node configuration, the
@@ -1119,7 +1419,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
address.
b10-dhcp4 does not support any
configuration mechanisms yet. The whole configuration is
currently hardcoded. The only way to tweak configuration
- is to directly modify source code. See see Section 12.2, “DHCPv4 Server Configuration” for details.
Upon start, the server will open sockets on all
interfaces that are not loopback, are up and running and
have IPv4 address.
PRL (Parameter Request List, a list of options
requested by a client) is currently ignored and server
@@ -1128,16 +1428,16 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
permanent. If you have legacy nodes that can't use DHCP and
require BOOTP support, please use latest version of ISC DHCP
http://www.isc.org/software/dhcp.
b10-dhcp4 does not verify that
assigned address is unused. According to RFC2131, the
allocating server should verify that address is no used by
sending ICMP echo request.
Address renewal (RENEW), rebinding (REBIND),
confirmation (CONFIRM), duplication report (DECLINE) and
release (RELEASE) are not supported yet.
DNS Update is not supported yet.
-v (verbose) command line option is currently
- the default, and cannot be disabled.
Dynamic Host Configuration Protocol for IPv6 (DHCPv6) is
specified in RFC3315. BIND10 provides DHCPv6 server implementation
that is described in this chapter. For a description of the DHCPv4
- server implementation, see Chapter 12, DHCPv4 Server.
+ server implementation, see Chapter 13, DHCPv4 Server.
BIND10 provides the DHCPv6 server component since September
2011. It is a skeleton server and can be described as an early
prototype that is not fully functional yet. It is mature
enough to conduct first tests in lab environment, but it has
- significant limitations. See Section 13.4, “DHCPv6 Server Limitations” for
+ significant limitations. See Section 14.4, “DHCPv6 Server Limitations” for
details.
The DHCPv6 server is implemented as b10-dhcp6
@@ -1190,7 +1490,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
be started directly, but rather via
bind10. Please be aware of this planned
change.
-
13.2. DHCPv6 Server Configuration
+
14.2. DHCPv6 Server Configuration
The DHCPv6 server does not have lease database implemented yet
or any support for configuration, so every time the same set
of configuration options (including the same fixed address)
@@ -1209,10 +1509,10 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
Lease database and configuration support is planned for 2012.
-
13.3. Supported DHCPv6 Standards
The following standards and draft standards are currently
+
14.3. Supported DHCPv6 Standards
The following standards and draft standards are currently
supported:
RFC3315: Supported messages are SOLICIT,
ADVERTISE, REQUEST, and REPLY. Supported options are
- SERVER_ID, CLIENT_ID, IA_NA, and IAADDRESS.
RFC3646: Supported option is DNS_SERVERS.
13.4. DHCPv6 Server Limitations
These are the current limitations of the DHCPv6 server
+ SERVER_ID, CLIENT_ID, IA_NA, and IAADDRESS.
RFC3646: Supported option is DNS_SERVERS.
14.4. DHCPv6 Server Limitations
These are the current limitations of the DHCPv6 server
software. Most of them are reflections of the early stage of
development and should be treated as “not implemented
yet”, rather than actual limitations.
b10-dhcp6 does not support any
configuration mechanisms yet. The whole configuration is
currently hardcoded. The only way to tweak configuration
- is to directly modify source code. See see Section 13.2, “DHCPv6 Server Configuration” for details.
Upon start, the server will open sockets on all
interfaces that are not loopback, are up, running and are
multicast capable and have IPv6 address. Support for
multiple interfaces is not coded in reception routines yet,
@@ -1235,9 +1535,9 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
assigns DNS SERVER option.
Temporary addresses are not supported yet.
Prefix delegation is not supported yet.
Address renewal (RENEW), rebinding (REBIND),
confirmation (CONFIRM), duplication report (DECLINE) and
release (RELEASE) are not supported yet.
libdhcp++ is a common library written in C++ that handles
many DHCP-related tasks, like DHCPv4 and DHCPv6 packets parsing,
manipulation and assembly, option parsing, manipulation and
assembly, network interface detection and socket operations, like
@@ -1248,13 +1548,13 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
b10-dhcp4 and b10-dhcp6
only, it is designed to be portable, universal library useful for
any kind of DHCP-related software.
-
14.1. Interface detection
Both DHCPv4 and DHCPv6 components share network
+
15.1. Interface detection
Both DHCPv4 and DHCPv6 components share network
interface detection routines. Interface detection is
currently only supported on Linux systems.
For non-Linux systems, there is currently stub
implementation provided. Interface manager detects loopback
interfaces only as their name (lo or lo0) can be easily predicted.
Please contact BIND10 development team if you are interested
- in running DHCP components on systems other than Linux.
14.2. DHCPv4/DHCPv6 packet handling
TODO: Describe packet handling here, with pointers to wiki
Chapter 15. Statistics
+ in running DHCP components on systems other than Linux.
15.2. DHCPv4/DHCPv6 packet handling
TODO: Describe packet handling here, with pointers to wiki
Chapter 16. Statistics
The b10-stats process is started by
bind10.
It periodically collects statistics data from various modules
@@ -1292,7 +1592,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
The logging system in BIND 10 is configured through the
Logging module. All BIND 10 modules will look at the
@@ -1301,7 +1601,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
-
16.1.1. Loggers
+
17.1.1. Loggers
Within BIND 10, a message is logged through a component
called a "logger". Different parts of BIND 10 log messages
@@ -1322,7 +1622,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
(what to log), and the output_options
(where to log).
-
16.1.1.1. name (string)
+
17.1.1.1. name (string)
Each logger in the system has a name, the name being that
of the component using it to log messages. For instance,
if you want to configure logging for the resolver module,
@@ -1395,7 +1695,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
“Auth.cache” logger will appear in the output
with a logger name of “b10-auth.cache”).
-
16.1.1.2. severity (string)
+
17.1.1.2. severity (string)
This specifies the category of messages logged.
Each message is logged with an associated severity which
@@ -1411,7 +1711,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
-
16.1.1.3. output_options (list)
+
17.1.1.3. output_options (list)
Each logger can have zero or more
output_options. These specify where log
@@ -1421,7 +1721,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
The other options for a logger are:
-
16.1.1.4. debuglevel (integer)
+
17.1.1.4. debuglevel (integer)
When a logger's severity is set to DEBUG, this value
specifies what debug messages should be printed. It ranges
@@ -1430,7 +1730,7 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
If severity for the logger is not DEBUG, this value is ignored.
-
16.1.1.5. additive (true or false)
+
17.1.1.5. additive (true or false)
If this is true, the output_options from
the parent will be used. For example, if there are two
@@ -1444,18 +1744,18 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
-
16.1.2. Output Options
+
17.1.2. Output Options
The main settings for an output option are the
destination and a value called
output, the meaning of which depends on
the destination that is set.
-
16.1.2.1. destination (string)
+
17.1.2.1. destination (string)
The destination is the type of output. It can be one of:
-
console
file
syslog
16.1.2.2. output (string)
+
console
file
syslog
17.1.2.2. output (string)
Depending on what is set as the output destination, this
value is interpreted as follows:
@@ -1485,12 +1785,12 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
The other options for output_options are:
-
16.1.2.2.1. flush (true of false)
+
17.1.2.2.1. flush (true of false)
Flush buffers after each log message. Doing this will
reduce performance but will ensure that if the program
terminates abnormally, all messages up to the point of
termination are output.
-
16.1.2.2.2. maxsize (integer)
+
17.1.2.2.2. maxsize (integer)
Only relevant when destination is file, this is maximum
file size of output files in bytes. When the maximum
size is reached, the file is renamed and a new file opened.
@@ -1499,11 +1799,11 @@ const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
etc.)
If this is 0, no maximum file size is used.
-
16.1.2.2.3. maxver (integer)
+
17.1.2.2.3. maxver (integer)
Maximum number of old log files to keep around when
rolling the output file. Only relevant when
destination is “file”.
-
16.1.3. Example session
+
17.1.3. Example session
In this example we want to set the global logging to
write to the file /var/log/my_bind10.log,
@@ -1664,7 +1964,7 @@ Logging/loggers[0]/output_options[0]/maxver 8 integer (modified)
And every module will now be using the values from the
logger named “*”.
-
BIND 10 is a Domain Name System (DNS) suite managed by
Internet Systems Consortium (ISC). It includes DNS libraries
and modular components for controlling authoritative and
recursive DNS servers.
- This is the messages manual for BIND 10 version 20120127.
+ This is the messages manual for BIND 10 version 20120405.
The most up-to-date version of this document, along with
other documents for BIND 10, can be found at
http://bind10.isc.org/docs.
@@ -131,6 +131,19 @@ discovered that the memory data source is disabled for the given class.
AUTH_MEM_DATASRC_ENABLED memory data source is enabled for class %1
This is a debug message reporting that the authoritative server has
discovered that the memory data source is enabled for the given class.
+
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_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
This debug message is logged by the authoritative server when it receives
a NOTIFY packet that contains zero or more than one question. (A valid
@@ -183,6 +196,17 @@ This is a debug message issued when the authoritative server has received
a command from the statistics module to send it data. The 'sendstats'
command is handled differently to other commands, which is why the debug
message associated with it has its own code.
+
AUTH_RESPONSE_FAILURE exception while building response to query: %1
+This is a debug message, generated by the authoritative server when an
+attempt to create a response to a received DNS packet has failed. The
+reason for the failure is given in the log message. A SERVFAIL response
+is sent back. The most likely cause of this is an error in the data
+source implementation; it is either creating bad responses or raising
+exceptions itself.
+
AUTH_RESPONSE_FAILURE_UNKNOWN unknown exception while building response to query
+This debug message is similar to AUTH_RESPONSE_FAILURE, but further
+details about the error are unknown, because it was signaled by something
+which is not an exception. This is definitely a bug.
AUTH_RESPONSE_RECEIVED received response message, ignoring
This is a debug message, this is output if the authoritative server
receives a DNS packet with the QR bit set, i.e. a DNS response. The
@@ -275,7 +299,7 @@ NOTIFY request will not be honored.
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_COMPONENT_FAILED component %1 (pid %2) failed with %3 exit status
The process terminated, but the bind10 boss didn't expect it to, which means
it must have failed.
BIND10_COMPONENT_RESTART component %1 is about to restart
@@ -384,6 +408,10 @@ so BIND 10 will now shut down. The specific error is printed.
The boss module is sending a SIGKILL signal to the given process.
BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2)
The boss module is sending a SIGTERM signal to the given process.
+
BIND10_SETGID setting GID to %1
+The boss switches the process group ID to the given value. This happens
+when BIND 10 starts with the -u option, and the group ID will be set to
+that of the specified user.
BIND10_SETUID setting UID to %1
The boss switches the user it runs as to the given UID.
BIND10_SHUTDOWN stopping the server
@@ -585,7 +613,7 @@ same RRset, but from more trusted source, so the old one is kept and new one
ignored.
CACHE_RRSET_UPDATE updating RRset %1/%2/%3 in the cache
Debug message. The RRset is updating its data with this given RRset.
-
This marks a low level error, we tried to read data from the message queue
daemon asynchronously, but the ASIO library returned an error.
CC_CONN_ERROR error connecting to message queue (%1)
@@ -660,6 +688,11 @@ all messages must contain at least the envelope.
An older version of the configuration database has been found, from which
there was an automatic upgrade path to the current version. These changes
are now applied, and no action from the administrator is necessary.
+
CFGMGR_BACKED_UP_CONFIG_FILE Config file %1 was removed; a backup was made at %2
+BIND 10 has been started with the command to clear the configuration
+file. The existing file has been backed up (moved) to the given file
+name. A new configuration file will be created in the original location
+when necessary.
CFGMGR_BAD_UPDATE_RESPONSE_FROM_MODULE Unable to parse response from module %1: %2
The configuration manager sent a configuration update to a module, but
the module responded with an answer that could not be parsed. The answer
@@ -669,6 +702,9 @@ assumed to have failed, and will not be stored.
CFGMGR_CC_SESSION_ERROR Error connecting to command channel: %1
The configuration manager daemon was unable to connect to the messaging
system. The most likely cause is that msgq is not running.
+
CFGMGR_CONFIG_FILE Configuration manager starting with configuration file: %1
+The configuration manager is starting, reading and saving the configuration
+settings to the shown file.
CFGMGR_DATA_READ_ERROR error reading configuration database from disk: %1
There was a problem reading the persistent configuration data as stored
on disk. The file may be corrupted, or it is of a version from where
@@ -870,6 +906,33 @@ means no limit.
The datasource tried to provide an NSEC proof that the named domain does not
exist, but the database backend doesn't support DNSSEC. No proof is included
in the answer as a result.
+
DATASRC_DATABASE_FINDNSEC3 Looking for NSEC3 for %1 in %2 mode
+Debug information. A search in an database data source for NSEC3 that
+matches or covers the given name is being started.
+
DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1 at label count %2: %3
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed. 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.
+
DATASRC_DATABASE_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+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.
+
DATASRC_DATABASE_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+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).
+
DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV looking for previous NSEC3 for %1 at label count %2 (hash %3)
+Debug information. An exact match on hash (see
+DATASRC_DATABASE_FINDNSEC3_TRYHASH) was unsuccessful. We get the previous hash
+to that one instead.
DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4
Debug information. The database data source is looking up records with the given
name and type in the database.
@@ -912,7 +975,7 @@ name and class, but not for the given type.
A search in the database for RRs for the specified name, type and class has
located RRs that match the name and class but not the type. DNSSEC information
has been requested and returned.
-
DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %5
+
DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %2
The data returned by the database backend contained data for the given domain
name, and it either matches the type or has a relevant type. The RRset that is
returned is printed.
@@ -925,10 +988,12 @@ While iterating through the zone, the program reached end of the data.
While iterating through the zone, the program extracted next RRset from it.
The name and RRtype of the RRset is indicated in the message.
DATASRC_DATABASE_ITERATE_TTL_MISMATCH TTL values differ for RRs of %1/%2/%3, setting to %4
-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.
DATASRC_DATABASE_JOURNALREADER_END %1/%2 on %3 from %4 to %5
This is a debug message indicating that the program (successfully)
reaches the end of sequences of a zone's differences. The zone's name
@@ -1009,7 +1074,7 @@ The given wildcard matches the name being sough but it as an empty
nonterminal (e.g. there's nothing at *.example.com but something like
subdomain.*.example.org, do exist: so *.example.org exists in the
namespace but has no RRs assopciated with it). This will produce NXRRSET.
-
DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %5 with RRset %6
+
DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
The database doesn't contain directly matching name. When searching
for a wildcard match, a wildcard record matching the name and type of
the query was found. The data at this point is returned.
@@ -1259,8 +1324,10 @@ not have any DS record. This indicates problem with the provided data.
An attempt to add a NSEC3 record into the message failed, because the zone does
not have any DS record. This indicates problem with the provided data.
DATASRC_QUERY_NO_ZONE no zone containing '%1' in class '%2'
-Lookup of domain failed because the data have no zone that contain the
-domain. Maybe someone sent a query to the wrong server for some reason.
+Debug information. Lookup of domain failed because the datasource
+has no zone that contains the domain. Maybe someone sent a query
+to the wrong server for some reason. This may also happen when
+looking in the datasource for addresses for NS records.
DATASRC_QUERY_PROCESS processing query '%1/%2' in the '%3' class
Debug information. A sure query is being processed now.
DATASRC_QUERY_PROVE_NX_FAIL unable to prove nonexistence of '%1'
@@ -1307,6 +1374,16 @@ While processing a wildcard, a referral was met. But it wasn't possible to get
enough information for it. The code is 1 for error, 2 for not implemented.
DATASRC_SQLITE_CLOSE closing SQLite database
Debug information. The SQLite data source is closing the database file.
+
DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible
+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.
+
+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.
DATASRC_SQLITE_CONNCLOSE Closing sqlite database
The database file is no longer needed and is being closed.
DATASRC_SQLITE_FIND_NSEC3_NO_ZONE no such zone '%1'
The SQLite data source was asked to provide a NSEC3 record for given zone.
But it doesn't contain that zone.
+
DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected
+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.
+
+The database should be updated using the means described in the BIND
+10 documentation.
DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
A wrapper object to hold database connection is being initialized.
DATASRC_SQLITE_OPEN opening SQLite database '%1'
@@ -1389,11 +1473,85 @@ data source.
DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
This indicates a programming error. An internal task of unknown type was
generated.
+
DBUTIL_BACKUP created backup of %1 in %2
+A backup for the given database file was created. Same of original file and
+backup are given in the output message.
+
DBUTIL_CHECK_ERROR unable to check database version: %1
+There was an error while trying to check the current version of the database
+schema. The error is shown in the message.
+
DBUTIL_CHECK_NOCONFIRM --noconfirm is not compatible with --check
+b10-dbutil was called with --check and --noconfirm. --noconfirm only has
+meaning with --upgrade, so this is considered an error.
+
DBUTIL_CHECK_OK this is the latest version of the database schema. No upgrade is required
+The database schema version has been checked, and is up to date.
+No action is required.
+
DBUTIL_CHECK_UPGRADE_NEEDED re-run this program with the --upgrade switch to upgrade
+The database schema version is not up to date, and an update is required.
+Please run the dbutil tool again, with the --upgrade argument.
+
DBUTIL_COMMAND_NONE must select one of --check or --upgrade
+b10-dbutil was called with neither --check nor --upgrade. One action must be
+provided.
+
DBUTIL_COMMAND_UPGRADE_CHECK --upgrade is not compatible with --check
+b10-dbutil was called with both the commands --upgrade and --check. Only one
+action can be performed at a time.
+
DBUTIL_DATABASE_MAY_BE_CORRUPT database file %1 may be corrupt, restore it from backup (%2)
+The upgrade failed while it was in progress; the database may now be in an
+inconsistent state, and it is advised to restore it from the backup that was
+created when b10-dbutil started.
+
DBUTIL_EXECUTE Executing SQL statement: %1
+Debug message; the given SQL statement is executed
+
DBUTIL_FILE Database file: %1
+The database file that is being checked.
+
DBUTIL_NO_FILE must supply name of the database file to upgrade
+b10-dbutil was called without a database file. Currently, it cannot find this
+file on its own, and it must be provided.
+
DBUTIL_STATEMENT_ERROR failed to execute %1: %2
+The given database statement failed to execute. The error is shown in the
+message.
+
DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected
+There were too many command-line arguments to b10-dbutil
+
DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed
+The user aborted the upgrade, and b10-dbutil will now exit.
+
DBUTIL_UPGRADE_DBUTIL please get the latest version of b10-dbutil and re-run
+A database schema was found that was newer than this version of dbutil, which
+is apparently out of date and should be upgraded itself.
+
DBUTIL_UPGRADE_FAILED upgrade failed: %1
+While the upgrade was in progress, an unexpected error occurred. The error
+is shown in the message.
+
DBUTIL_UPGRADE_NOT_ATTEMPTED database upgrade was not attempted
+Due to the earlier failure, the database schema upgrade was not attempted,
+and b10-dbutil will now exit.
+
DBUTIL_UPGRADE_NOT_NEEDED database already at latest version, no upgrade necessary
+b10-dbutil was told to upgrade the database schema, but it is already at the
+latest version.
+
DBUTIL_UPGRADE_NOT_POSSIBLE database at a later version than this utility can support
+b10-dbutil was told to upgrade the database schema, but it is at a higher
+version than this tool currently supports. Please update b10-dbutil and try
+again.
+
+The database schema update was completed successfully.
+
DBUTIL_UPGRADING upgrading database from %1 to %2
+An upgrade is in progress, the versions of the current upgrade action are shown.
+
DBUTIL_VERSION_CURRENT database version %1
+The current version of the database schema.
+
DBUTIL_VERSION_HIGH database is at a later version (%1) than this program can cope with (%2)
+The database schema is at a higher version than b10-dbutil knows about.
+
DBUTIL_VERSION_LOW database version %1, latest version is %2.
+The database schema is not up to date, the current version and the latest
+version are in the message.
DDNS_ACCEPT_FAILURE error accepting a connection: %1
There was a low-level error when we tried to accept an incoming connection
(probably coming from b10-auth). We continue serving on whatever other
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.
@@ -1404,12 +1562,41 @@ configuration manager b10-cfgmgr is not running.
DDNS_CONFIG_ERROR error found in configuration data: %1
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
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
@@ -1421,12 +1608,80 @@ Debug message. We received a connection and we are going to start handling
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_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_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.
+
+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_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
@@ -1435,6 +1690,9 @@ following log messages to see what of it.
The ddns process is shutting down. It will no longer listen for new commands
or updates. Any command or update that is being addressed at this moment will
be completed, after which the process will exit.
+
DDNS_STARTED ddns server is running and listening for updates
+The ddns process has successfully started and is now ready to receive commands
+and updates.
DDNS_STOPPED ddns server has stopped
The ddns process has successfully stopped and is no longer listening for or
handling commands or updates, and will now exit.
@@ -1445,6 +1703,212 @@ 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 component of the updates, but the
+notification fails. One possible cause of this is that the external
+component is not really running and it times out in waiting for the
+response, although it will be less likely to happen in practice
+because these components will normally be configured to run when the
+server provides the authoritative DNS service; ddns is rather optional
+among them. If this happens, however, it will suspend b10-ddns for a
+few seconds during which it cannot handle new requests (some may be
+delayed, some may be dropped, depending on the volume of the incoming
+requests). This is obviously bad, and if this error happens due to
+this reason, the administrator should make sure the component in
+question should be configured to run. For a longer term, b10-ddns
+should be more robust about this case such as by making this
+notification asynchronously and/or detecting the existence of the
+external components to avoid hopeless notification in the first place.
+Severity of this error for the receiving components depends on the
+type of the component. 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.
+
LIBDDNS_DATASRC_ERROR update client %1 failed due to data source error: %2
+An update attempt failed due to some error in the corresponding data
+source. This is generally an unexpected event, but can still happen
+for various reasons such as DB lock contention or a failure of the
+backend DB server. The cause of the error is also logged. It's
+advisable to check the message, and, if necessary, take an appropriate
+action (e.g., restarting the DB server if it dies). If this message
+is logged the data source isn't modified due to the
+corresponding update request. When used by the b10-ddns, the server
+will return a response with an RCODE of SERVFAIL.
+
LIBDDNS_PREREQ_FORMERR update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it has a non-zero TTL value.
+A FORMERR error response is sent to the client.
+
LIBDDNS_PREREQ_FORMERR_ANY update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it either has a non-zero
+TTL value, or has rdata fields. A FORMERR error response is sent to the client.
+
LIBDDNS_PREREQ_FORMERR_CLASS update client %1 for zone %2: Format error in prerequisite (%3). Bad class.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, the class of the
+prerequisite should either match the class of the zone in the Zone Section,
+or it should be ANY or NONE, and it is not. A FORMERR error response is sent
+to the client.
+
LIBDDNS_PREREQ_FORMERR_NONE update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it either has a non-zero
+TTL value, or has rdata fields. A FORMERR error response is sent to the client.
+
LIBDDNS_PREREQ_NAME_IN_USE_FAILED update client %1 for zone %2: 'Name is in use' prerequisite not satisfied (%3), rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'Name is in use'. From RFC2136:
+Name is in use. At least one RR with a specified NAME (in
+the zone and class specified by the Zone Section) must exist.
+Note that this prerequisite is NOT satisfied by empty
+nonterminals.
+
LIBDDNS_PREREQ_NAME_NOT_IN_USE_FAILED update client %1 for zone %2: 'Name is not in use' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'Name is not in use'.
+From RFC2136:
+Name is not in use. No RR of any type is owned by a
+specified NAME. Note that this prerequisite IS satisfied by
+empty nonterminals.
+
LIBDDNS_PREREQ_NOTZONE update client %1 for zone %2: prerequisite not in zone (%3)
+A DDNS UPDATE prerequisite has a name that does not appear to be inside
+the zone specified in the Zone section of the UPDATE message.
+The specific prerequisite is shown. A NOTZONE error response is sent to
+the client.
+
LIBDDNS_PREREQ_RRSET_DOES_NOT_EXIST_FAILED update client %1 for zone %2: 'RRset does not exist' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset does not exist'.
+From RFC2136:
+RRset does not exist. No RRs with a specified NAME and TYPE
+(in the zone and class denoted by the Zone Section) can exist.
+
LIBDDNS_PREREQ_RRSET_EXISTS_FAILED update client %1 for zone %2: 'RRset exists (value independent)' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset exists (value independent)'.
+From RFC2136:
+RRset exists (value dependent). A set of RRs with a
+specified NAME and TYPE exists and has the same members
+with the same RDATAs as the RRset specified here in this
+Section.
+
LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED update client %1 for zone %2: 'RRset exists (value dependent)' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset exists (value dependent)'.
+From RFC2136:
+RRset exists (value independent). At least one RR with a
+specified NAME and TYPE (in the zone and class specified by
+the Zone Section) must exist.
+
LIBDDNS_UPDATE_ADD_BAD_TYPE update client %1 for zone %2: update addition RR bad type: %3
+The Update section of a DDNS update message contains a statement
+that tries to add a record of an invalid type. Most likely the
+record has an RRType that is considered a 'meta' type, which
+cannot be zone content data. The specific record is shown.
+A FORMERR response is sent back to the client.
+
LIBDDNS_UPDATE_APPROVED update client %1 for zone %2 approved
+Debug message. An update request was approved in terms of the zone's
+update ACL.
+
LIBDDNS_UPDATE_BAD_CLASS update client %1 for zone %2: bad class in update RR: %3
+The Update section of a DDNS update message contains an RRset with
+a bad class. The class of the update RRset must be either the same
+as the class in the Zone Section, ANY, or NONE.
+A FORMERR response is sent back to the client.
+
LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
+An error occured while committing the DDNS update changes to the
+datasource. The specific error is printed. A SERVFAIL response is sent
+back to the client.
+
LIBDDNS_UPDATE_DELETE_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
+The Update section of a DDNS update message contains a statement
+that tries to delete an rrset of an invalid type. Most likely the
+record has an RRType that is considered a 'meta' type, which
+cannot be zone content data. The specific record is shown.
+A FORMERR response is sent back to the client.
+
LIBDDNS_UPDATE_DELETE_NONZERO_TTL update client %1 for zone %2: update deletion RR has non-zero TTL: %3
+The Update section of a DDNS update message contains a 'delete rrset'
+statement with a non-zero TTL. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
LIBDDNS_UPDATE_DELETE_RRSET_NOT_EMPTY update client %1 for zone %2: update deletion RR contains data %3
+The Update section of a DDNS update message contains a 'delete rrset'
+statement with a non-empty RRset. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
LIBDDNS_UPDATE_DELETE_RR_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
+The Update section of a DDNS update message contains a statement
+that tries to delete one or more rrs of an invalid type. Most
+likely the records have an RRType that is considered a 'meta'
+type, which cannot be zone content data. The specific record is
+shown. A FORMERR response is sent back to the client.
+
LIBDDNS_UPDATE_DELETE_RR_NONZERO_TTL update client %1 for zone %2: update deletion RR has non-zero TTL: %3
+The Update section of a DDNS update message contains a 'delete rrs'
+statement with a non-zero TTL. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
LIBDDNS_UPDATE_DENIED update client %1 for zone %2 denied
+Informational message. An update request was denied because it was
+rejected by the zone's update ACL. When this library is used by
+b10-ddns, the server will respond to the request with an RCODE of
+REFUSED as described in Section 3.3 of RFC2136.
+
LIBDDNS_UPDATE_DROPPED update client %1 for zone %2 dropped
+Informational message. An update request was denied because it was
+rejected by the zone's update ACL. When this library is used by
+b10-ddns, the server will then completely ignore the request; no
+response will be sent.
+
LIBDDNS_UPDATE_ERROR update client %1 for zone %2: %3
+Debug message. An error is found in processing a dynamic update
+request. This log message is used for general errors that are not
+normally expected to happen. So, in general, it would mean some
+problem in the client implementation or an interoperability issue
+with this implementation. The client's address, the zone name and
+class, and description of the error are logged.
+
LIBDDNS_UPDATE_FORWARD_FAIL update client %1 for zone %2: update forwarding not supported
+Debug message. An update request is sent to a secondary server. This
+is not necessarily invalid, but this implementation does not yet
+support update forwarding as specified in Section 6 of RFC2136 and it
+will simply return a response with an RCODE of NOTIMP to the client.
+The client's address and the zone name/class are logged.
+
LIBDDNS_UPDATE_NOTAUTH update client %1 for zone %2: not authoritative for update zone
+Debug message. An update request was received for a zone for which
+the receiving server doesn't have authority. In theory this is an
+unexpected event, but there are client implementations that could send
+update requests carelessly, so it may not necessarily be so uncommon
+in practice. If possible, you may want to check the implementation or
+configuration of those clients to suppress the requests. As specified
+in Section 3.1 of RFC2136, the receiving server will return a response
+with an RCODE of NOTAUTH.
+
LIBDDNS_UPDATE_NOTZONE update client %1 for zone %2: update RR out of zone %3
+A DDNS UPDATE record has a name that does not appear to be inside
+the zone specified in the Zone section of the UPDATE message.
+The specific update record is shown. A NOTZONE error response is
+sent to the client.
+
LIBDDNS_UPDATE_PREREQUISITE_FAILED prerequisite failed in update client %1 for zone %2: result code %3
+The handling of the prerequisite section (RFC2136 Section 3.2) found
+that one of the prerequisites was not satisfied. The result code
+should give more information on what prerequisite type failed.
+If the result code is FORMERR, the prerequisite section was not well-formed.
+An error response with the given result code is sent back to the client.
+
LIBDDNS_UPDATE_UNCAUGHT_EXCEPTION update client %1 for zone %2: uncaught exception while processing update section: %3
+An uncaught exception was encountered while processing the Update
+section of a DDNS message. The specific exception is shown in the log message.
+To make sure DDNS service is not interrupted, this problem is caught instead
+of reraised; The update is aborted, and a SERVFAIL is sent back to the client.
+This is most probably a bug in the DDNS code, but *could* be caused by
+the data source.
LIBXFRIN_DIFFERENT_TTL multiple data with different TTLs (%1, %2) on %3/%4/%5. Adjusting %2 -> %1.
The xfrin module received an update containing multiple rdata changes for the
same RRset. But the TTLs of these don't match each other. As we combine them
@@ -1502,6 +1966,8 @@ the reason given.
An invalid message identification (ID) has been found during the read of
a message file. Message IDs should comprise only alphanumeric characters
and the underscore, and should not start with a digit.
+
LOG_LOCK_TEST_MESSAGE this is a test message.
+This is a log message used in testing.
LOG_NAMESPACE_EXTRA_ARGS line %1: $NAMESPACE directive has too many arguments
The $NAMESPACE directive in a message file takes a single argument, a
namespace in which all the generated symbol names are placed. This error
@@ -1703,6 +2169,30 @@ an answer with a different given type and class.
This message indicates an internal error in the NSAS. Please raise a
bug report.
+
PYSERVER_COMMON_AUTH_CONFIG_NAME_PARSER_ERROR Invalid name when parsing Auth configuration: %1
+There was an invalid name when parsing Auth configuration.
+
PYSERVER_COMMON_AUTH_CONFIG_RRCLASS_ERROR Invalid RRClass when parsing Auth configuration: %1
+There was an invalid RR class when parsing Auth configuration.
+
PYSERVER_COMMON_DNS_TCP_SEND_DONE completed sending TCP message to %1 (%2 bytes in total)
+Debug message. A complete DNS message has been successfully
+transmitted over a TCP connection, possibly after multiple send
+operations. The destination address and the total size of the message
+(including the 2-byte length field) are shown in the log message.
+
PYSERVER_COMMON_DNS_TCP_SEND_ERROR failed to send TCP message to %1 (%2/%3 bytes sent): %4
+A DNS message has been attempted to be sent out over a TCP connection,
+but it failed due to some network error. Although it's not expected
+to happen too often, it can still happen for various reasons. The
+administrator may want to examine the cause of the failure, which is
+included in the log message, to see if it requires some action to
+be taken at the server side. When this message is logged, the
+corresponding TCP connection was closed immediately after the error
+was detected.
+
PYSERVER_COMMON_DNS_TCP_SEND_PENDING sent part TCP message to %1 (up to %2/%3 bytes)
+Debug message. A part of DNS message has been transmitted over a TCP
+connection, and it's suspended because further attempt would block.
+The destination address and the total size of the message that has
+been transmitted so far (including the 2-byte length field) are shown
+in the log message.
PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
A debug message noting that the global TSIG keyring is being removed from
memory. Most programs don't do that, they just exit, which is OK.
@@ -2064,7 +2554,7 @@ resolver. It is output during startup and may appear multiple times,
once for each root server address.
RESOLVER_SHUTDOWN resolver shutdown complete
This informational message is output when the resolver has shut down.
-
RESOLVER_SHUTDOWN (1) asked to shut down, doing so
+
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
@@ -2270,6 +2760,10 @@ 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.
+
XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3
+There was a successful zone transfer, and the zone is served by b10-auth
+in the in-memory data source using sqlite3 as a backend. We send the
+"loadzone" command for the zone to b10-auth.
XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
The serial fields of the first and last SOAs of AXFR (including AXFR-style
IXFR) are not the same. According to RFC 5936 these two SOAs must be the
@@ -2317,6 +2811,30 @@ is not equal to the requested SOA serial.
XFRIN_IMPORT_DNS error importing python DNS module: %1
There was an error importing the python DNS module pydnspp. The most
likely cause is a PYTHONPATH problem.
+
XFRIN_IXFR_TRANSFER_SUCCESS incremental IXFR transfer of zone %1 succeeded (messages: %2, changesets: %3, deletions: %4, additions: %5, bytes: %6, run time: %7 seconds, %8 bytes/second)
+The IXFR transfer for the given zone was successful.
+The provided information contains the following values:
+
+messages: Number of overhead DNS messages in the transfer.
+
+changesets: Number of difference sequences.
+
+deletions: Number of Resource Records deleted by all the changesets combined,
+including the SOA records.
+
+additions: Number of Resource Records added by all the changesets combined,
+including the SOA records.
+
+bytes: Full size of the transfer data on the wire.
+
+run time: Time (in seconds) the complete ixfr took.
+
+bytes/second: Transfer speed.
+
+Note that there is no cross-checking of additions and deletions; if the same
+RR gets added and deleted in multiple changesets, it is counted each time;
+therefore, for each changeset, there should at least be 1 deletion and 1
+addition (the updated SOA record).
XFRIN_IXFR_UPTODATE IXFR requested serial for %1 is %2, master has %3, not updating
The first SOA record in an IXFR response indicates the zone's serial
at the primary server is not newer than the client's. This is
@@ -2330,6 +2848,9 @@ aborts the transfer just like a successful case.
There was a problem sending a message to the xfrout module or the
zone manager. This most likely means that the msgq daemon has quit or
was killed.
+
XFRIN_MSGQ_SEND_ERROR_AUTH error while contacting %1
+There was a problem sending a message to b10-auth. This most likely
+means that the msgq daemon has quit or was killed.
XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER error while contacting %1
There was a problem sending a message to the zone manager. This most
likely means that the msgq daemon has quit or was killed.
@@ -2343,11 +2864,26 @@ There was an internal command to retransfer the given zone, but the
zone is not known to the system. This may indicate that the configuration
for xfrin is incomplete, or there was a typographical error in the
zone name in the configuration.
-
XFRIN_STARTING starting resolver with command line '%1'
-An informational message, this is output when the resolver starts up.
+
XFRIN_STARTED xfrin started
+This informational message is output by xfrin when all initialization
+has been completed and it is entering its main loop.
XFRIN_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
There was a keyboard interrupt signal to stop the xfrin daemon. The
daemon will now shut down.
+
XFRIN_TRANSFER_SUCCESS full %1 transfer of zone %2 succeeded (messages: %3, records: %4, bytes: %5, run time: %6 seconds, %7 bytes/second)
+The AXFR transfer of the given zone was successful.
+The provided information contains the following values:
+
+messages: Number of overhead DNS messages in the transfer
+
+records: Number of Resource Records in the full transfer, excluding the
+final SOA record that marks the end of the AXFR.
+
+bytes: Full size of the transfer data on the wire.
+
+run time: Time (in seconds) the complete axfr took
+
+bytes/second: Transfer speed
XFRIN_UNKNOWN_ERROR unknown error: %1
An uncaught exception was raised while running the xfrin daemon. The
exception message is printed in the log message.
@@ -2389,8 +2925,6 @@ is recommended to check the primary server configuration.
XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started
A connection to the master server has been made, the serial value in
the SOA record has been checked, and a zone transfer has been started.
-
XFRIN_XFR_TRANSFER_SUCCESS %1 transfer of zone %2 succeeded
-The XFR transfer of the given zone was successfully completed.
XFRIN_ZONE_CREATED Zone %1 not found in the given data source, newly created
On starting an xfrin session, it is identified that the zone to be
transferred is not found in the data source. This can happen if a
@@ -2509,11 +3043,15 @@ do not understand or support. The xfrout request will be ignored.
In general, this should only occur for unexpected problems like
memory allocation failures, as the query should already have been
parsed by the b10-auth daemon, before it was passed here.
-
XFROUT_PROCESS_REQUEST_ERROR error processing transfer request: %2
-There was an error processing a transfer request. The error is included
-in the log message, but at this point no specific information other
-than that could be given. This points to incomplete exception handling
-in the code.
+
XFROUT_PROCESS_REQUEST_ERROR error processing transfer request: %1
+There was an error in receiving a transfer request from b10-auth.
+This is generally an unexpected event, but is possible when, for
+example, b10-auth terminates in the middle of forwarding the request.
+When this happens it's unlikely to be recoverable with the same
+communication session with b10-auth, so b10-xfrout drops it and
+waits for a new session. In any case, this error indicates that
+there's something very wrong in the system, so it's advisable to check
+the over all status of the BIND 10 system.
XFROUT_QUERY_DROPPED %1 client %2: request to transfer %3 dropped
The xfrout process silently dropped a request to transfer zone to
given host. This is required by the ACLs. The %2 represents the IP
@@ -2538,9 +3076,16 @@ The xfrout daemon received a shutdown command from the command channel
and will now shut down.
XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR error receiving the file descriptor for an XFR connection
There was an error receiving the file descriptor for the transfer
-request. Normally, the request is received by b10-auth, and passed on
-to the xfrout daemon, so it can answer directly. However, there was a
-problem receiving this file descriptor. The request will be ignored.
+request from b10-auth. There can be several reasons for this, but
+the most likely cause is that b10-auth terminates for some reason
+(maybe it's a bug of b10-auth, maybe it's an intentional restart by
+the administrator), so depending on how this happens it may or may not
+be a serious error. But in any case this is not expected to happen
+frequently, and it's advisable to figure out how this happened if
+this message is logged. Even if this error happens xfrout will reset
+its internal state and will keep receiving further requests. So
+if it's just a temporary restart of b10-auth the administrator does
+not have to do anything.
The unix socket file xfrout needs for contact with the auth daemon
already exists, and needs to be removed first, but there is a problem
@@ -2557,6 +3102,9 @@ the xfrout daemon that a new xfrout request has arrived. This should
be a result of rare local error such as memory allocation failure and
shouldn't happen under normal conditions. The error is included in the
log message.
+
XFROUT_STARTED xfrout started
+This informational message is output by xfrout when all initialization
+has been completed and it is entering its main loop.
XFROUT_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
There was a keyboard interrupt signal to stop the xfrout daemon. The
daemon will now shut down.
@@ -2673,6 +3221,9 @@ connecting to the command channel daemon. The most usual cause of this
problem is that the daemon is not running.
ZONEMGR_SHUTDOWN zone manager has shut down
A debug message, output when the zone manager has shut down completely.
+
ZONEMGR_STARTED zonemgr started
+This informational message is output by zonemgr when all initialization
+has been completed and it is entering its main loop.
ZONEMGR_STARTING zone manager starting
A debug message output when the zone manager starts up.
ZONEMGR_TIMER_THREAD_RUNNING trying to start timer thread but one is already running
@@ -2683,9 +3234,11 @@ a problem with stopping a previous instance of the timer. Please submit
a bug report.
ZONEMGR_UNKNOWN_ZONE_FAIL zone %1 (class %2) is not known to the zone manager
An XFRIN operation has failed but the zone that was the subject of the
-operation is not being managed by the zone manager. This may indicate
-an error in the program (as the operation should not have been initiated
-if this were the case). Please submit a bug report.
+operation is not being managed by the zone manager. This can be either the
+result of a bindctl command to transfer in a currently unknown (or mistyped)
+zone, or, if this error appears without the administrator giving transfer
+commands, it can indicate an error in the program, as it should not have
+initiated transfers of unknown zones on its own.
ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager
A NOTIFY was received but the zone that was the subject of the operation
is not being managed by the zone manager. This may indicate an error
diff --git a/doc/guide/bind10-messages.xml b/doc/guide/bind10-messages.xml
index 60f9665eb5..e0e192f5db 100644
--- a/doc/guide/bind10-messages.xml
+++ b/doc/guide/bind10-messages.xml
@@ -303,6 +303,24 @@ discovered that the memory data source is enabled for the given class.
+
+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_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
@@ -882,6 +900,15 @@ The boss module is sending a SIGTERM signal to the given process.
+
+BIND10_SETGID setting GID to %1
+
+The boss switches the process group ID to the given value. This happens
+when BIND 10 starts with the -u option, and the group ID will be set to
+that of the specified user.
+
+
+
BIND10_SETUID setting UID to %1
@@ -1399,7 +1426,7 @@ Debug message. The RRset is updating its data with this given RRset.
-CC_ASYNC_READ_FAILED asynchronous read failed
+CC_ASYNC_READ_FAILED asynchronous read failed (error code = %1)
This marks a low level error, we tried to read data from the message queue
daemon asynchronously, but the ASIO library returned an error.
@@ -1588,6 +1615,16 @@ are now applied, and no action from the administrator is necessary.
+
+CFGMGR_BACKED_UP_CONFIG_FILE Config file %1 was removed; a backup was made at %2
+
+BIND 10 has been started with the command to clear the configuration
+file. The existing file has been backed up (moved) to the given file
+name. A new configuration file will be created in the original location
+when necessary.
+
+
+
CFGMGR_BAD_UPDATE_RESPONSE_FROM_MODULE Unable to parse response from module %1: %2
@@ -1607,6 +1644,14 @@ system. The most likely cause is that msgq is not running.
+
+CFGMGR_CONFIG_FILE Configuration manager starting with configuration file: %1
+
+The configuration manager is starting, reading and saving the configuration
+settings to the shown file.
+
+
+
CFGMGR_DATA_READ_ERROR error reading configuration database from disk: %1
@@ -1639,15 +1684,6 @@ configuration is not stored.
-
-CFGMGR_RENAMED_CONFIG_FILE renamed configuration file %1 to %2, will create new %1
-
-BIND 10 has been started with the command to clear the configuration file.
-The existing file is backed up to the given file name, so that data is not
-immediately lost if this was done by accident.
-
-
-
CFGMGR_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
@@ -2057,6 +2093,58 @@ in the answer as a result.
+
+DATASRC_DATABASE_FINDNSEC3 Looking for NSEC3 for %1 in %2 mode
+
+Debug information. A search in an database data source for NSEC3 that
+matches or covers the given name is being started.
+
+
+
+
+DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1 at label count %2: %3
+
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed. 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.
+
+
+
+
+DATASRC_DATABASE_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+
+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.
+
+
+
+
+DATASRC_DATABASE_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+
+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).
+
+
+
+
+DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV looking for previous NSEC3 for %1 at label count %2 (hash %3)
+
+Debug information. An exact match on hash (see
+DATASRC_DATABASE_FINDNSEC3_TRYHASH) was unsuccessful. We get the previous hash
+to that one instead.
+
+
+
DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4
@@ -2155,7 +2243,7 @@ has been requested and returned.
-DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %5
+DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %2
The data returned by the database backend contained data for the given domain
name, and it either matches the type or has a relevant type. The RRset that is
@@ -2189,10 +2277,12 @@ The name and RRtype of the RRset is indicated in the message.
DATASRC_DATABASE_ITERATE_TTL_MISMATCH TTL values differ for RRs of %1/%2/%3, setting to %4
-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.
@@ -2352,7 +2442,7 @@ namespace but has no RRs assopciated with it). This will produce NXRRSET.
-DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %5 with RRset %6
+DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
The database doesn't contain directly matching name. When searching
for a wildcard match, a wildcard record matching the name and type of
@@ -3096,6 +3186,21 @@ Debug information. The SQLite data source is closing the database file.
+
+DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible
+
+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.
+
+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.
+
+
+
DATASRC_SQLITE_CONNCLOSE Closing sqlite database
@@ -3235,6 +3340,18 @@ But it doesn't contain that zone.
+
+DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected
+
+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.
+
+The database should be updated using the means described in the BIND
+10 documentation.
+
+
+
DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
@@ -3517,6 +3634,16 @@ 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
@@ -3542,6 +3669,18 @@ 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
@@ -3553,6 +3692,33 @@ 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
@@ -3574,6 +3740,15 @@ 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
@@ -3582,11 +3757,105 @@ and will now shut down.
-
-DDNS_RUNNING ddns server is running and listening for updates
+
+DDNS_RECEIVED_ZONEMGR_UPDATE received configuration updates from zonemgr
-The ddns process has successfully started and is now ready to receive commands
-and updates.
+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_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.
@@ -3608,6 +3877,14 @@ be completed, after which the process will exit.
+
+DDNS_STARTED ddns server is running and listening for updates
+
+The ddns process has successfully started and is now ready to receive commands
+and updates.
+
+
+
DDNS_STOPPED ddns server has stopped
@@ -3633,6 +3910,362 @@ 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 component of the updates, but the
+notification fails. One possible cause of this is that the external
+component is not really running and it times out in waiting for the
+response, although it will be less likely to happen in practice
+because these components will normally be configured to run when the
+server provides the authoritative DNS service; ddns is rather optional
+among them. If this happens, however, it will suspend b10-ddns for a
+few seconds during which it cannot handle new requests (some may be
+delayed, some may be dropped, depending on the volume of the incoming
+requests). This is obviously bad, and if this error happens due to
+this reason, the administrator should make sure the component in
+question should be configured to run. For a longer term, b10-ddns
+should be more robust about this case such as by making this
+notification asynchronously and/or detecting the existence of the
+external components to avoid hopeless notification in the first place.
+Severity of this error for the receiving components depends on the
+type of the component. 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.
+
+
+
+
+LIBDDNS_DATASRC_ERROR update client %1 failed due to data source error: %2
+
+An update attempt failed due to some error in the corresponding data
+source. This is generally an unexpected event, but can still happen
+for various reasons such as DB lock contention or a failure of the
+backend DB server. The cause of the error is also logged. It's
+advisable to check the message, and, if necessary, take an appropriate
+action (e.g., restarting the DB server if it dies). If this message
+is logged the data source isn't modified due to the
+corresponding update request. When used by the b10-ddns, the server
+will return a response with an RCODE of SERVFAIL.
+
+
+
+
+LIBDDNS_PREREQ_FORMERR update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL.
+
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it has a non-zero TTL value.
+A FORMERR error response is sent to the client.
+
+
+
+
+LIBDDNS_PREREQ_FORMERR_ANY update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found.
+
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it either has a non-zero
+TTL value, or has rdata fields. A FORMERR error response is sent to the client.
+
+
+
+
+LIBDDNS_PREREQ_FORMERR_CLASS update client %1 for zone %2: Format error in prerequisite (%3). Bad class.
+
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, the class of the
+prerequisite should either match the class of the zone in the Zone Section,
+or it should be ANY or NONE, and it is not. A FORMERR error response is sent
+to the client.
+
+
+
+
+LIBDDNS_PREREQ_FORMERR_NONE update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found.
+
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it either has a non-zero
+TTL value, or has rdata fields. A FORMERR error response is sent to the client.
+
+
+
+
+LIBDDNS_PREREQ_NAME_IN_USE_FAILED update client %1 for zone %2: 'Name is in use' prerequisite not satisfied (%3), rcode: %4
+
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'Name is in use'. From RFC2136:
+Name is in use. At least one RR with a specified NAME (in
+the zone and class specified by the Zone Section) must exist.
+Note that this prerequisite is NOT satisfied by empty
+nonterminals.
+
+
+
+
+LIBDDNS_PREREQ_NAME_NOT_IN_USE_FAILED update client %1 for zone %2: 'Name is not in use' (%3) prerequisite not satisfied, rcode: %4
+
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'Name is not in use'.
+From RFC2136:
+Name is not in use. No RR of any type is owned by a
+specified NAME. Note that this prerequisite IS satisfied by
+empty nonterminals.
+
+
+
+
+LIBDDNS_PREREQ_NOTZONE update client %1 for zone %2: prerequisite not in zone (%3)
+
+A DDNS UPDATE prerequisite has a name that does not appear to be inside
+the zone specified in the Zone section of the UPDATE message.
+The specific prerequisite is shown. A NOTZONE error response is sent to
+the client.
+
+
+
+
+LIBDDNS_PREREQ_RRSET_DOES_NOT_EXIST_FAILED update client %1 for zone %2: 'RRset does not exist' (%3) prerequisite not satisfied, rcode: %4
+
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset does not exist'.
+From RFC2136:
+RRset does not exist. No RRs with a specified NAME and TYPE
+(in the zone and class denoted by the Zone Section) can exist.
+
+
+
+
+LIBDDNS_PREREQ_RRSET_EXISTS_FAILED update client %1 for zone %2: 'RRset exists (value independent)' (%3) prerequisite not satisfied, rcode: %4
+
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset exists (value independent)'.
+From RFC2136:
+RRset exists (value dependent). A set of RRs with a
+specified NAME and TYPE exists and has the same members
+with the same RDATAs as the RRset specified here in this
+Section.
+
+
+
+
+LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED update client %1 for zone %2: 'RRset exists (value dependent)' (%3) prerequisite not satisfied, rcode: %4
+
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset exists (value dependent)'.
+From RFC2136:
+RRset exists (value independent). At least one RR with a
+specified NAME and TYPE (in the zone and class specified by
+the Zone Section) must exist.
+
+
+
+
+LIBDDNS_UPDATE_ADD_BAD_TYPE update client %1 for zone %2: update addition RR bad type: %3
+
+The Update section of a DDNS update message contains a statement
+that tries to add a record of an invalid type. Most likely the
+record has an RRType that is considered a 'meta' type, which
+cannot be zone content data. The specific record is shown.
+A FORMERR response is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_APPROVED update client %1 for zone %2 approved
+
+Debug message. An update request was approved in terms of the zone's
+update ACL.
+
+
+
+
+LIBDDNS_UPDATE_BAD_CLASS update client %1 for zone %2: bad class in update RR: %3
+
+The Update section of a DDNS update message contains an RRset with
+a bad class. The class of the update RRset must be either the same
+as the class in the Zone Section, ANY, or NONE.
+A FORMERR response is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
+
+An error occured while committing the DDNS update changes to the
+datasource. The specific error is printed. A SERVFAIL response is sent
+back to the client.
+
+
+
+
+LIBDDNS_UPDATE_DELETE_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
+
+The Update section of a DDNS update message contains a statement
+that tries to delete an rrset of an invalid type. Most likely the
+record has an RRType that is considered a 'meta' type, which
+cannot be zone content data. The specific record is shown.
+A FORMERR response is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_DELETE_NONZERO_TTL update client %1 for zone %2: update deletion RR has non-zero TTL: %3
+
+The Update section of a DDNS update message contains a 'delete rrset'
+statement with a non-zero TTL. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_DELETE_RRSET_NOT_EMPTY update client %1 for zone %2: update deletion RR contains data %3
+
+The Update section of a DDNS update message contains a 'delete rrset'
+statement with a non-empty RRset. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_DELETE_RR_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
+
+The Update section of a DDNS update message contains a statement
+that tries to delete one or more rrs of an invalid type. Most
+likely the records have an RRType that is considered a 'meta'
+type, which cannot be zone content data. The specific record is
+shown. A FORMERR response is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_DELETE_RR_NONZERO_TTL update client %1 for zone %2: update deletion RR has non-zero TTL: %3
+
+The Update section of a DDNS update message contains a 'delete rrs'
+statement with a non-zero TTL. This is not allowed by the protocol.
+A FORMERR response is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_DENIED update client %1 for zone %2 denied
+
+Informational message. An update request was denied because it was
+rejected by the zone's update ACL. When this library is used by
+b10-ddns, the server will respond to the request with an RCODE of
+REFUSED as described in Section 3.3 of RFC2136.
+
+
+
+
+LIBDDNS_UPDATE_DROPPED update client %1 for zone %2 dropped
+
+Informational message. An update request was denied because it was
+rejected by the zone's update ACL. When this library is used by
+b10-ddns, the server will then completely ignore the request; no
+response will be sent.
+
+
+
+
+LIBDDNS_UPDATE_ERROR update client %1 for zone %2: %3
+
+Debug message. An error is found in processing a dynamic update
+request. This log message is used for general errors that are not
+normally expected to happen. So, in general, it would mean some
+problem in the client implementation or an interoperability issue
+with this implementation. The client's address, the zone name and
+class, and description of the error are logged.
+
+
+
+
+LIBDDNS_UPDATE_FORWARD_FAIL update client %1 for zone %2: update forwarding not supported
+
+Debug message. An update request is sent to a secondary server. This
+is not necessarily invalid, but this implementation does not yet
+support update forwarding as specified in Section 6 of RFC2136 and it
+will simply return a response with an RCODE of NOTIMP to the client.
+The client's address and the zone name/class are logged.
+
+
+
+
+LIBDDNS_UPDATE_NOTAUTH update client %1 for zone %2: not authoritative for update zone
+
+Debug message. An update request was received for a zone for which
+the receiving server doesn't have authority. In theory this is an
+unexpected event, but there are client implementations that could send
+update requests carelessly, so it may not necessarily be so uncommon
+in practice. If possible, you may want to check the implementation or
+configuration of those clients to suppress the requests. As specified
+in Section 3.1 of RFC2136, the receiving server will return a response
+with an RCODE of NOTAUTH.
+
+
+
+
+LIBDDNS_UPDATE_NOTZONE update client %1 for zone %2: update RR out of zone %3
+
+A DDNS UPDATE record has a name that does not appear to be inside
+the zone specified in the Zone section of the UPDATE message.
+The specific update record is shown. A NOTZONE error response is
+sent to the client.
+
+
+
+
+LIBDDNS_UPDATE_PREREQUISITE_FAILED prerequisite failed in update client %1 for zone %2: result code %3
+
+The handling of the prerequisite section (RFC2136 Section 3.2) found
+that one of the prerequisites was not satisfied. The result code
+should give more information on what prerequisite type failed.
+If the result code is FORMERR, the prerequisite section was not well-formed.
+An error response with the given result code is sent back to the client.
+
+
+
+
+LIBDDNS_UPDATE_UNCAUGHT_EXCEPTION update client %1 for zone %2: uncaught exception while processing update section: %3
+
+An uncaught exception was encountered while processing the Update
+section of a DDNS message. The specific exception is shown in the log message.
+To make sure DDNS service is not interrupted, this problem is caught instead
+of reraised; The update is aborted, and a SERVFAIL is sent back to the client.
+This is most probably a bug in the DDNS code, but *could* be caused by
+the data source.
+
+
+
LIBXFRIN_DIFFERENT_TTL multiple data with different TTLs (%1, %2) on %3/%4/%5. Adjusting %2 -> %1.
@@ -3750,6 +4383,13 @@ and the underscore, and should not start with a digit.
+
+LOG_LOCK_TEST_MESSAGE this is a test message.
+
+This is a log message used in testing.
+
+
+
LOG_NAMESPACE_EXTRA_ARGS line %1: $NAMESPACE directive has too many arguments
@@ -4141,6 +4781,55 @@ bug report.
+
+PYSERVER_COMMON_AUTH_CONFIG_NAME_PARSER_ERROR Invalid name when parsing Auth configuration: %1
+
+There was an invalid name when parsing Auth configuration.
+
+
+
+
+PYSERVER_COMMON_AUTH_CONFIG_RRCLASS_ERROR Invalid RRClass when parsing Auth configuration: %1
+
+There was an invalid RR class when parsing Auth configuration.
+
+
+
+
+PYSERVER_COMMON_DNS_TCP_SEND_DONE completed sending TCP message to %1 (%2 bytes in total)
+
+Debug message. A complete DNS message has been successfully
+transmitted over a TCP connection, possibly after multiple send
+operations. The destination address and the total size of the message
+(including the 2-byte length field) are shown in the log message.
+
+
+
+
+PYSERVER_COMMON_DNS_TCP_SEND_ERROR failed to send TCP message to %1 (%2/%3 bytes sent): %4
+
+A DNS message has been attempted to be sent out over a TCP connection,
+but it failed due to some network error. Although it's not expected
+to happen too often, it can still happen for various reasons. The
+administrator may want to examine the cause of the failure, which is
+included in the log message, to see if it requires some action to
+be taken at the server side. When this message is logged, the
+corresponding TCP connection was closed immediately after the error
+was detected.
+
+
+
+
+PYSERVER_COMMON_DNS_TCP_SEND_PENDING sent part TCP message to %1 (up to %2/%3 bytes)
+
+Debug message. A part of DNS message has been transmitted over a TCP
+connection, and it's suspended because further attempt would block.
+The destination address and the total size of the message that has
+been transmitted so far (including the 2-byte length field) are shown
+in the log message.
+
+
+
PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
@@ -5388,6 +6077,15 @@ Please check your installation.
+
+XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3
+
+There was a successful zone transfer, and the zone is served by b10-auth
+in the in-memory data source using sqlite3 as a backend. We send the
+"loadzone" command for the zone to b10-auth.
+
+
+
XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
@@ -5542,6 +6240,14 @@ was killed.
+
+XFRIN_MSGQ_SEND_ERROR_AUTH error while contacting %1
+
+There was a problem sending a message to b10-auth. This most likely
+means that the msgq daemon has quit or was killed.
+
+
+
XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER error while contacting %1
@@ -5570,10 +6276,11 @@ zone name in the configuration.
-
-XFRIN_STARTING starting resolver with command line '%1'
+
+XFRIN_STARTED xfrin started
-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.
@@ -5909,12 +6616,16 @@ parsed by the b10-auth daemon, before it was passed here.
-XFROUT_PROCESS_REQUEST_ERROR error processing transfer request: %2
+XFROUT_PROCESS_REQUEST_ERROR error processing transfer request: %1
-There was an error processing a transfer request. The error is included
-in the log message, but at this point no specific information other
-than that could be given. This points to incomplete exception handling
-in the code.
+There was an error in receiving a transfer request from b10-auth.
+This is generally an unexpected event, but is possible when, for
+example, b10-auth terminates in the middle of forwarding the request.
+When this happens it's unlikely to be recoverable with the same
+communication session with b10-auth, so b10-xfrout drops it and
+waits for a new session. In any case, this error indicates that
+there's something very wrong in the system, so it's advisable to check
+the over all status of the BIND 10 system.
@@ -5964,9 +6675,16 @@ and will now shut down.
XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR error receiving the file descriptor for an XFR connection
There was an error receiving the file descriptor for the transfer
-request. Normally, the request is received by b10-auth, and passed on
-to the xfrout daemon, so it can answer directly. However, there was a
-problem receiving this file descriptor. The request will be ignored.
+request from b10-auth. There can be several reasons for this, but
+the most likely cause is that b10-auth terminates for some reason
+(maybe it's a bug of b10-auth, maybe it's an intentional restart by
+the administrator), so depending on how this happens it may or may not
+be a serious error. But in any case this is not expected to happen
+frequently, and it's advisable to figure out how this happened if
+this message is logged. Even if this error happens xfrout will reset
+its internal state and will keep receiving further requests. So
+if it's just a temporary restart of b10-auth the administrator does
+not have to do anything.
@@ -6001,6 +6719,14 @@ log message.
+
+XFROUT_STARTED xfrout started
+
+This informational message is output by xfrout when all initialization
+has been completed and it is entering its main loop.
+
+
+
XFROUT_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
@@ -6257,6 +6983,14 @@ A debug message, output when the zone manager has shut down completely.
+
+ZONEMGR_STARTED zonemgr started
+
+This informational message is output by zonemgr when all initialization
+has been completed and it is entering its main loop.
+
+
+
ZONEMGR_STARTING zone manager starting
diff --git a/src/Makefile.am b/src/Makefile.am
index ca4a702f4e..395553c6fa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1 +1,6 @@
SUBDIRS = lib bin
+
+EXTRA_DIST = \
+ cppcheck-suppress.lst \
+ valgrind-suppressions \
+ valgrind-suppressions.revisit
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index 66abbe2db6..34b515543f 100644
--- a/src/bin/auth/Makefile.am
+++ b/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
diff --git a/src/bin/auth/auth_config.cc b/src/bin/auth/auth_config.cc
index 3a04dc8a6b..c85a4eeccb 100644
--- a/src/bin/auth/auth_config.cc
+++ b/src/bin/auth/auth_config.cc
@@ -43,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 > datasources_;
set configured_sources_;
+ vector > clients_;
};
/// A derived \c AuthConfigParser for the version value
@@ -86,14 +83,40 @@ DatasourcesConfig::build(ConstElementPtr config_value) {
isc_throw(AuthConfigError, "Data source type '" <<
datasrc_type->stringValue() << "' already configured");
}
-
- boost::shared_ptr datasrc_config =
- boost::shared_ptr(
- createAuthConfigParser(server_, string("datasources/") +
- datasrc_type->stringValue(),
- true));
- datasrc_config->build(datasrc_elem);
- datasources_.push_back(datasrc_config);
+
+ // 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());
+ }
+
+ // 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(new DataSourceClientContainer(
+ datasrc_type->stringValue(),
+ datasrc_elem))));
configured_sources_.insert(datasrc_type->stringValue());
}
@@ -101,122 +124,19 @@ DatasourcesConfig::build(ConstElementPtr config_value) {
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());
-
- BOOST_FOREACH(boost::shared_ptr datasrc_config,
- datasources_) {
- datasrc_config->commit();
- }
-}
-
-/// 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_txt);
- }
-
- // We support the traditional text type and SQLite3 backend. For the
- // latter we create a client for the underlying SQLite3 data source,
- // and build the in-memory zone using an iterator of the underlying
- // zone.
- ConstElementPtr filetype = zone_config->get("filetype");
- const string filetype_txt = filetype ? filetype->stringValue() :
- "text";
- boost::scoped_ptr container;
- if (filetype_txt == "sqlite3") {
- container.reset(new DataSourceClientContainer(
- "sqlite3",
- Element::fromJSON("{\"database_file\": \"" +
- file_txt + "\"}")));
- } else if (filetype_txt != "text") {
- isc_throw(AuthConfigError, "Invalid filetype for zone "
- << origin_txt << ": " << filetype_txt);
- }
-
- // 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 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.
- */
- if (filetype_txt == "text") {
- zone_finder->load(file_txt);
- } else {
- zone_finder->load(*container->getInstance().getIterator(
- Name(origin_txt)));
- }
+ // 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);
}
}
@@ -314,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
@@ -329,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") {
@@ -351,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) {
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index b18feb13e6..9ac2c499ef 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -96,6 +96,20 @@ discovered that the memory data source is disabled for the given class.
This is a debug message reporting that the authoritative server has
discovered that the memory data source is enabled for the given class.
+% 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_NOTIFY_QUESTIONS invalid number of questions (%1) in incoming NOTIFY
This debug message is logged by the authoritative server when it receives
a NOTIFY packet that contains zero or more than one question. (A valid
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 9f5642ed34..2a47c38822 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -14,18 +14,10 @@
#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-#include
+#include
#include
+#include
#include
@@ -64,6 +56,18 @@
#include
#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
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 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.
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index 3be711bd0c..18d750331b 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -17,12 +17,9 @@
#include
-// For InMemoryClientPtr below. This should be a temporary definition until
-// we reorganize the data source framework.
-#include
-
#include
#include
+#include
#include
#include
#include
@@ -40,6 +37,11 @@
#include
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 InMemoryClientPtr;
-
- /// An immutable shared pointer type for \c InMemoryClient.
- typedef boost::shared_ptr
- 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_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.
///
diff --git a/src/bin/auth/b10-auth.8 b/src/bin/auth/b10-auth.8
index a5ef4fbc08..accc214af3 100644
--- a/src/bin/auth/b10-auth.8
+++ b/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
-.\" Date: March 28, 2012
+.\" Date: June 20, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-AUTH" "8" "March 28, 2012" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "June 20, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -49,7 +49,7 @@ Do not cache answers in memory\&. The default is to use the cache for faster res
.PP
\fB\-v\fR
.RS 4
-Enabled verbose mode\&. This enables diagnostic messages to STDERR\&.
+Enable verbose logging mode\&. This enables logging of diagnostic messages at the maximum debug level\&.
.RE
.SH "CONFIGURATION AND COMMANDS"
.PP
@@ -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
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index 7f3a492dba..44c036f040 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.xml
@@ -20,7 +20,7 @@
- March 28, 2012
+ June 20, 2012
@@ -94,8 +94,8 @@
- Enabled verbose mode. This enables diagnostic messages to
- STDERR.
+ Enable verbose logging mode. This enables logging of
+ diagnostic messages at the maximum debug level.
@@ -125,14 +125,21 @@
(it defaults to IN);
and
zones to define the
- file path name and the
- origin (default domain).
-
- By default, this is empty.
+ file path name,
+ origin (default domain), and optional
+ filetype.
+ By default, zones is empty.
+ For the in-memory data source (i.e., the type
+ is memory), the optional filetype
+ configuration item for zones 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.
- 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
diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc
index aa238c0886..2e705e4f2b 100644
--- a/src/bin/auth/benchmarks/query_bench.cc
+++ b/src/bin/auth/benchmarks/query_bench.cc
@@ -31,9 +31,10 @@
#include
#include
-
#include
+#include
+
#include
#include
#include
@@ -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:
diff --git a/src/bin/auth/command.cc b/src/bin/auth/command.cc
index 055c73a362..750ea28309 100644
--- a/src/bin/auth/command.cc
+++ b/src/bin/auth/command.cc
@@ -210,8 +210,8 @@ private:
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");
}
@@ -223,13 +223,16 @@ private:
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(
+ // 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(
result.zone_finder);
return (true);
diff --git a/src/bin/auth/common.cc b/src/bin/auth/common.cc
index 1602a1a3a4..2c21895b4a 100644
--- a/src/bin/auth/common.cc
+++ b/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);
}
}
}
diff --git a/src/bin/auth/common.h b/src/bin/auth/common.h
index cf71214a5c..9a1942c924 100644
--- a/src/bin/auth/common.h
+++ b/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.
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index fc2f7512dc..cf4f52e492 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -28,6 +28,7 @@
#include
#include
+#include
#include
#include
@@ -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 {
@@ -85,7 +87,7 @@ usage() {
cerr << "Usage: b10-auth [-u user] [-nv]"
<< endl;
cerr << "\t-n: do not cache answers in memory" << endl;
- cerr << "\t-v: verbose output" << endl;
+ cerr << "\t-v: verbose logging (debug-level)" << endl;
exit(1);
}
@@ -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();
diff --git a/src/bin/auth/spec_config.h.pre.in b/src/bin/auth/spec_config.h.pre.in
index 1b1df19b0e..586ea7c984 100644
--- a/src/bin/auth/spec_config.h.pre.in
+++ b/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"
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index f9fac2fdac..84fd5fae9c 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -19,6 +19,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
# Do not define global tests, use check-local so
# environment can be set (needed for dynamic loading)
TESTS =
@@ -44,9 +47,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
@@ -54,9 +57,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
@@ -71,6 +72,8 @@ 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
+run_unittests_LDADD += $(GTEST_LDADD)
+run_unittests_LDADD += $(SQLITE_LIBS)
check-local:
B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index a8815da971..0a41f5d574 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -14,11 +14,7 @@
#include
-#include
-
-#include
-
-#include
+#include
#include
#include
@@ -38,6 +34,7 @@
#include
#include
+#include
#include
#include
#include
@@ -45,10 +42,24 @@
#include
#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
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 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 {
@@ -1196,11 +1264,6 @@ public:
return (real_zone_finder_->findNSEC3(name, recursive));
}
- virtual isc::dns::Name
- findPreviousName(const isc::dns::Name& query) const {
- return (real_zone_finder_->findPreviousName(query));
- }
-
private:
isc::datasrc::ZoneFinderPtr real_zone_finder_;
ThrowWhen throw_when_;
@@ -1208,15 +1271,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 +1287,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 +1305,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 +1314,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
+ 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 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 +1407,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 +1447,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 +1467,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 +1515,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(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 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());
+}
+
}
diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc
index bcaf4b115c..ec00d11bec 100644
--- a/src/bin/auth/tests/command_unittest.cc
+++ b/src/bin/auth/tests/command_unittest.cc
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
@@ -32,6 +33,7 @@
#include
+#include
#include
#include
@@ -51,6 +53,7 @@ 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;
@@ -59,7 +62,7 @@ namespace {
class AuthCommandTest : public ::testing::Test {
protected:
AuthCommandTest() :
- server_(false, xfrout_),
+ server_(false, xfrout_, ddns_forwarder_),
rcode_(-1),
expect_rcode_(0),
itimer_(server_.getIOService())
@@ -68,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
@@ -233,7 +237,14 @@ 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 " -c " TEST_DATA_DIR
@@ -250,8 +261,6 @@ TEST_F(AuthCommandTest, loadZone) {
newZoneChecks(server_);
}
-// This test uses dynamic load of a data source module, and won't work when
-// statically linked.
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
DISABLED_loadZoneSQLite3
@@ -289,36 +298,57 @@ TEST_F(AuthCommandTest,
" }"
" ]"
" }"
- "]}"));
+ "],"
+ " \"database_file\": \"" + test_db + "\""
+ "}"));
module_session.setLocalConfig(map);
server_.setConfigSession(&module_session);
- // The loadzone command needs the zone to be already loaded, because
- // it is used for reloading only
- AuthSrv::InMemoryClientPtr dsrc(new InMemoryClient());
- dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
- Name("example.org"))));
- server_.setInMemoryClient(RRClass::IN(), dsrc);
+ 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\"}"));
+ Element::fromJSON(
+ "{\"origin\": \"example.org\"}"));
checkAnswer(0);
- // Get the zone and look if there are data in it (the original one was
- // empty)
- ASSERT_TRUE(server_.getInMemoryClient(RRClass::IN()));
+ // And now it should be present too.
EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
findZone(Name("example.org")).zone_finder->
- find(Name("example.org"), RRType::SOA())->code);
+ find(Name("www.example.org"), RRType::A())->code);
- // Some error cases. First, the zone has no configuration.
- dsrc->addZone(ZoneFinderPtr(new InMemoryZoneFinder(RRClass::IN(),
- Name("example.com"))));
+ // 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->
@@ -326,7 +356,8 @@ TEST_F(AuthCommandTest,
module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
result_ = execAuthServerCommand(server_, "loadzone",
- Element::fromJSON("{\"origin\": \"example.org\"}"));
+ Element::fromJSON(
+ "{\"origin\": \"example.org\"}"));
checkAnswer(1);
// The previous zone is not hurt in any way
@@ -373,7 +404,14 @@ TEST_F(AuthCommandTest,
find(Name("example.org"), RRType::SOA())->code);
}
-TEST_F(AuthCommandTest, loadBrokenZone) {
+TEST_F(AuthCommandTest,
+#ifdef USE_STATIC_LINK
+ DISABLED_loadBrokenZone
+#else
+ loadBrokenZone
+#endif
+ )
+{
configureZones(server_);
ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
@@ -386,7 +424,14 @@ 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
@@ -419,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
diff --git a/src/bin/auth/tests/common_unittest.cc b/src/bin/auth/tests/common_unittest.cc
index 184988d179..b2d072af32 100644
--- a/src/bin/auth/tests/common_unittest.cc
+++ b/src/bin/auth/tests/common_unittest.cc
@@ -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);
}
}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index d471a53c9a..e2d193ab5d 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -32,6 +32,7 @@
#include "datasrc_util.h"
+#include
#include
#include
#include
@@ -44,6 +45,7 @@ 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 {
@@ -52,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).
@@ -63,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());
}
@@ -96,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(
@@ -106,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) {
@@ -176,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\","
@@ -245,7 +276,14 @@ TEST_F(MemoryDatasrcConfigTest,
DataSourceError);
}
-TEST_F(MemoryDatasrcConfigTest, addOneWithFiletypeText) {
+TEST_F(MemoryDatasrcConfigTest,
+#ifdef USE_STATIC_LINK
+ DISABLED_addOneWithFiletypeText
+#else
+ addOneWithFiletypeText
+#endif
+ )
+{
// Explicitly specifying "text" is okay.
parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
@@ -257,7 +295,14 @@ TEST_F(MemoryDatasrcConfigTest, addOneWithFiletypeText) {
EXPECT_EQ(1, server.getInMemoryClient(rrclass)->getZoneCount());
}
-TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
+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\","
@@ -273,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\","
@@ -304,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\","
@@ -328,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
@@ -339,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\","
@@ -352,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) {
@@ -365,7 +432,7 @@ TEST_F(MemoryDatasrcConfigTest, addDuplicateZones) {
" {\"origin\": \"example.com\","
" \"file\": \"" TEST_DATA_DIR
"/example.com.zone\"}]}]")),
- AuthConfigError);
+ DataSourceError);
}
TEST_F(MemoryDatasrcConfigTest, addBadZone) {
@@ -374,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(
@@ -423,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\"}]")),
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 63429aeb49..4c404e6cfb 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -425,10 +425,6 @@ public:
// answers when DNSSEC is required.
void setNSEC3Flag(bool on) { use_nsec3_ = on; }
- virtual Name findPreviousName(const Name&) const {
- isc_throw(isc::NotImplemented, "Mock doesn't support previous name");
- }
-
// This method allows tests to insert new record in the middle of the test.
//
// \param record_txt textual RR representation of RR (such as soa_txt, etc)
diff --git a/src/bin/bind10/bind10.8 b/src/bin/bind10/bind10.8
index 2dafaab362..9e085e31cd 100644
--- a/src/bin/bind10/bind10.8
+++ b/src/bin/bind10/bind10.8
@@ -2,12 +2,12 @@
.\" Title: bind10
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2
-.\" Date: March 1, 2012
+.\" Date: April 12, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "BIND10" "8" "March 1, 2012" "BIND10" "BIND10"
+.TH "BIND10" "8" "April 12, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -22,7 +22,7 @@
bind10 \- BIND 10 boss process
.SH "SYNOPSIS"
.HP \w'\fBbind10\fR\ 'u
-\fBbind10\fR [\fB\-c\ \fR\fB\fIconfig\-filename\fR\fR] [\fB\-i\fR] [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fIdata_path\fR\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-w\ \fR\fB\fIwait_time\fR\fR] [\fB\-\-cmdctl\-port\fR\ \fIport\fR] [\fB\-\-config\-file\fR\ \fIconfig\-filename\fR] [\fB\-\-data\-path\fR\ \fIdirectory\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-no\-kill\fR] [\fB\-\-pid\-file\fR\ \fIfilename\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-verbose\fR] [\fB\-\-wait\ \fR\fB\fIwait_time\fR\fR]
+\fBbind10\fR [\fB\-c\ \fR\fB\fIconfig\-filename\fR\fR] [\fB\-i\fR] [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fIdata_path\fR\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-w\ \fR\fB\fIwait_time\fR\fR] [\fB\-\-clear\-config\fR] [\fB\-\-cmdctl\-port\fR\ \fIport\fR] [\fB\-\-config\-file\fR\ \fIconfig\-filename\fR] [\fB\-\-data\-path\fR\ \fIdirectory\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-no\-kill\fR] [\fB\-\-pid\-file\fR\ \fIfilename\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-verbose\fR] [\fB\-\-wait\ \fR\fB\fIwait_time\fR\fR]
.SH "DESCRIPTION"
.PP
The
@@ -38,6 +38,13 @@ The configuration filename to use\&. Can be either absolute or relative to data
b10\-config\&.db\&.
.RE
.PP
+\fB\-\-clear\-config\fR
+.RS 4
+This will create a backup of the existing configuration file, remove it and start
+b10\-cfgmgr(8)
+with the default configuration\&. 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\-\-cmdctl\-port\fR \fIport\fR
.RS 4
The
@@ -130,18 +137,6 @@ to manage under
.IP \(bu 2.3
.\}
-\fI/Boss/components/b10\-auth\fR
-.RE
-.sp
-.RS 4
-.ie n \{\
-\h'-04'\(bu\h'+03'\c
-.\}
-.el \{\
-.sp -1
-.IP \(bu 2.3
-.\}
-
\fI/Boss/components/b10\-cmdctl\fR
.RE
.sp
@@ -156,54 +151,6 @@ to manage under
\fI/Boss/components/b10\-stats\fR
.RE
-.sp
-.RS 4
-.ie n \{\
-\h'-04'\(bu\h'+03'\c
-.\}
-.el \{\
-.sp -1
-.IP \(bu 2.3
-.\}
-
-\fI/Boss/components/b10\-stats\-httpd\fR
-.RE
-.sp
-.RS 4
-.ie n \{\
-\h'-04'\(bu\h'+03'\c
-.\}
-.el \{\
-.sp -1
-.IP \(bu 2.3
-.\}
-
-\fI/Boss/components/b10\-xfrin\fR
-.RE
-.sp
-.RS 4
-.ie n \{\
-\h'-04'\(bu\h'+03'\c
-.\}
-.el \{\
-.sp -1
-.IP \(bu 2.3
-.\}
-
-\fI/Boss/components/b10\-xfrout\fR
-.RE
-.sp
-.RS 4
-.ie n \{\
-\h'-04'\(bu\h'+03'\c
-.\}
-.el \{\
-.sp -1
-.IP \(bu 2.3
-.\}
-
-\fI/Boss/components/b10\-zonemgr\fR
-.RE
.PP
(Note that the startup of
\fBb10\-sockcreator\fR,
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index 2501fee2bb..40537830ec 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -20,7 +20,7 @@
- March 1, 2012
+ April 12, 2012
@@ -52,6 +52,7 @@
+ portconfig-filenamedirectory
@@ -104,6 +105,25 @@
+
+
+
+
+
+
+ This will create a backup of the existing configuration
+ file, remove it and start
+ b10-cfgmgr8
+ with the default configuration.
+ The name of the backup file can be found in the logs
+ (CFGMGR_BACKED_UP_CONFIG_FILE).
+ (It will append a number to the backup filename if a
+ previous backup file exists.)
+
+
+
+
+
port
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index 3dd938feb3..c7515833e8 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/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.
@@ -160,6 +160,11 @@ The boss module is sending a SIGKILL signal to the given process.
% BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2)
The boss module is sending a SIGTERM signal to the given process.
+% BIND10_SETGID setting GID to %1
+The boss switches the process group ID to the given value. This happens
+when BIND 10 starts with the -u option, and the group ID will be set to
+that of the specified user.
+
% BIND10_SETUID setting UID to %1
The boss switches the user it runs as to the given UID.
@@ -290,4 +295,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.
-
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 37b845d5e6..b9dbc3656d 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/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
@@ -168,8 +169,8 @@ class BoB:
def __init__(self, msgq_socket_file=None, data_path=None,
config_filename=None, clear_config=False, nocache=False,
- verbose=False, nokill=False, setuid=None, username=None,
- cmdctl_port=None, wait_time=10):
+ verbose=False, nokill=False, setuid=None, setgid=None,
+ username=None, cmdctl_port=None, wait_time=10):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
@@ -207,6 +208,7 @@ class BoB:
self.components_to_restart = []
self.runnable = False
self.uid = setuid
+ self.gid = setgid
self.username = username
self.verbose = verbose
self.nokill = nokill
@@ -1122,6 +1124,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
@@ -1133,12 +1157,14 @@ def main():
# Check user ID.
setuid = None
+ setgid = None
username = None
if options.user:
# Try getting information about the user, assuming UID passed.
try:
pw_ent = pwd.getpwuid(int(options.user))
setuid = pw_ent.pw_uid
+ setgid = pw_ent.pw_gid
username = pw_ent.pw_name
except ValueError:
pass
@@ -1152,6 +1178,7 @@ def main():
try:
pw_ent = pwd.getpwnam(options.user)
setuid = pw_ent.pw_uid
+ setgid = pw_ent.pw_gid
username = pw_ent.pw_name
except KeyError:
pass
@@ -1182,7 +1209,7 @@ def main():
boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
options.config_file, options.clear_config,
options.nocache, options.verbose, options.nokill,
- setuid, username, options.cmdctl_port,
+ setuid, setgid, username, options.cmdctl_port,
options.wait_time)
startup_result = boss_of_bind.startup()
if startup_result:
@@ -1201,6 +1228,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)
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index d54ee56be1..a5e3fabc9b 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/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
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 84a9da968d..6ed7411050 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -1055,22 +1055,29 @@ class TestPIDFile(unittest.TestCase):
# dump PID to the file, and confirm the content is correct
dump_pid(self.pid_file)
my_pid = os.getpid()
- self.assertEqual(my_pid, int(open(self.pid_file, "r").read()))
+ with open(self.pid_file, "r") as f:
+ self.assertEqual(my_pid, int(f.read()))
def test_dump_pid(self):
self.check_pid_file()
# make sure any existing content will be removed
- open(self.pid_file, "w").write('dummy data\n')
+ with open(self.pid_file, "w") as f:
+ f.write('dummy data\n')
self.check_pid_file()
def test_unlink_pid_file_notexist(self):
dummy_data = 'dummy_data\n'
- open(self.pid_file, "w").write(dummy_data)
+
+ with open(self.pid_file, "w") as f:
+ f.write(dummy_data)
+
unlink_pid_file("no_such_pid_file")
+
# the file specified for unlink_pid_file doesn't exist,
# and the original content of the file should be intact.
- self.assertEqual(dummy_data, open(self.pid_file, "r").read())
+ with open(self.pid_file, "r") as f:
+ self.assertEqual(dummy_data, f.read())
def test_dump_pid_with_none(self):
# Check the behavior of dump_pid() and unlink_pid_file() with None.
@@ -1079,9 +1086,14 @@ class TestPIDFile(unittest.TestCase):
self.assertFalse(os.path.exists(self.pid_file))
dummy_data = 'dummy_data\n'
- open(self.pid_file, "w").write(dummy_data)
+
+ with open(self.pid_file, "w") as f:
+ f.write(dummy_data)
+
unlink_pid_file(None)
- self.assertEqual(dummy_data, open(self.pid_file, "r").read())
+
+ with open(self.pid_file, "r") as f:
+ self.assertEqual(dummy_data, f.read())
def test_dump_pid_failure(self):
# the attempt to open file will fail, which should result in exception.
@@ -1463,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)
diff --git a/src/bin/bindctl/bindctl.1 b/src/bin/bindctl/bindctl.1
index 97700d6ea2..6aee29c279 100644
--- a/src/bin/bindctl/bindctl.1
+++ b/src/bin/bindctl/bindctl.1
@@ -2,12 +2,12 @@
.\" Title: bindctl
.\" Author: [see the "AUTHORS" section]
.\" Generator: DocBook XSL Stylesheets v1.75.2
-.\" Date: December 23, 2010
+.\" Date: June 20, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "BINDCTL" "1" "December 23, 2010" "BIND10" "BIND10"
+.TH "BINDCTL" "1" "June 20, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -35,7 +35,7 @@ via its interactive command interpreter\&.
communicates over a HTTPS REST\-ful interface provided by
\fBb10-cmdctl\fR(8)\&. The
\fBb10-cfgmgr\fR(8)
-daemon stores the configurations and defines the commands\&.
+daemon stores the configurations\&.
.SH "ARGUMENTS"
.PP
The arguments are as follows:
@@ -91,9 +91,9 @@ Display the version number and exit\&.
.SH "AUTHENTICATION"
.PP
The tool will authenticate using a username and password\&. On the first successful login, it will save the details to a comma\-separated\-value (CSV) file which will be used for later uses of
-\fBbindctl\fR\&. The file name is
-default_user\&.csv
-located under the directory specified by the \-\-csv\-file\-dir option\&.
+\fBbindctl\fR\&. The file name is "default_user\&.csv" located under the directory specified by the
+\fB\-\-csv\-file\-dir\fR
+option\&.
.SH "USAGE"
.PP
The
@@ -115,8 +115,7 @@ keyword to receive usage assistance for a module or a module\'s command\&.
The
\fBquit\fR
command is used to exit
-\fBbindctl\fR
-(and doesn\'t stop the BIND 10 services)\&.
+\fBbindctl\fR\&. (It doesn\'t stop the BIND 10 services\&.)
.PP
The following module is available by default:
\fBconfig\fR
diff --git a/src/bin/bindctl/bindctl.xml b/src/bin/bindctl/bindctl.xml
index eff1de2c18..3993739169 100644
--- a/src/bin/bindctl/bindctl.xml
+++ b/src/bin/bindctl/bindctl.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[]>
@@ -208,8 +209,8 @@
The quit
command is used to exit
- bindctl
- (and doesn't stop the BIND 10 services).
+ bindctl.
+ (It doesn't stop the BIND 10 services.)
diff --git a/src/bin/bindctl/command_sets.py b/src/bin/bindctl/command_sets.py
index 9e2c2efd47..c001ec8ca5 100644
--- a/src/bin/bindctl/command_sets.py
+++ b/src/bin/bindctl/command_sets.py
@@ -92,4 +92,3 @@ def prepare_execute_commands(tool):
module.add_command(cmd)
tool.add_module_info(module)
-
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 1ddb9163d4..bcfb6c594d 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -425,6 +425,12 @@ class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
class TestBindCmdInterpreter(unittest.TestCase):
+ def setUp(self):
+ self.old_stdout = sys.stdout
+
+ def tearDown(self):
+ sys.stdout = self.old_stdout
+
def _create_invalid_csv_file(self, csvfilename):
import csv
csvfile = open(csvfilename, 'w')
@@ -447,19 +453,17 @@ class TestBindCmdInterpreter(unittest.TestCase):
self.assertEqual(new_csv_dir, custom_cmd.csv_file_dir)
def test_get_saved_user_info(self):
- old_stdout = sys.stdout
- sys.stdout = open(os.devnull, 'w')
- cmd = bindcmd.BindCmdInterpreter()
- users = cmd._get_saved_user_info('/notexist', 'csv_file.csv')
- self.assertEqual([], users)
-
- csvfilename = 'csv_file.csv'
- self._create_invalid_csv_file(csvfilename)
- users = cmd._get_saved_user_info('./', csvfilename)
- self.assertEqual([], users)
- os.remove(csvfilename)
- sys.stdout = old_stdout
+ with open(os.devnull, 'w') as f:
+ sys.stdout = f
+ cmd = bindcmd.BindCmdInterpreter()
+ users = cmd._get_saved_user_info('/notexist', 'csv_file.csv')
+ self.assertEqual([], users)
+ csvfilename = 'csv_file.csv'
+ self._create_invalid_csv_file(csvfilename)
+ users = cmd._get_saved_user_info('./', csvfilename)
+ self.assertEqual([], users)
+ os.remove(csvfilename)
class TestCommandLineOptions(unittest.TestCase):
def setUp(self):
diff --git a/src/bin/cfgmgr/b10-cfgmgr.8 b/src/bin/cfgmgr/b10-cfgmgr.8
index 719f4c67cb..e8ec567cc0 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.8
+++ b/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
-.\" Date: March 10, 2010
+.\" Date: June 20, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-CFGMGR" "8" "March 10, 2010" "BIND10" "BIND10"
+.TH "B10\-CFGMGR" "8" "June 20, 2012" "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
@@ -42,22 +42,23 @@ C\-Channel connection\&. If this connection is not established,
will exit\&.
.PP
The daemon may be cleanly stopped by sending the SIGTERM signal to the process\&. This shutdown does not notify the subscribers\&.
-.PP
-When it exits, it saves its current configuration to
-/usr/local/var/bind10\-devel/b10\-config\&.db\&.
-
.SH "ARGUMENTS"
.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
-The configuration database filename to use\&. Can be either absolute or relative to data path\&.
-.sp
-Defaults to b10\-config\&.db
+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\-p\fR\fIdata\-path\fR, \fB\-\-data\-path\fR \fIdata\-path\fR
+\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\&. It defaults to "b10\-config\&.db"\&.
+.RE
+.PP
+\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
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index 760b6d8e97..f1d0308adf 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/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()
diff --git a/src/bin/cfgmgr/b10-cfgmgr.xml b/src/bin/cfgmgr/b10-cfgmgr.xml
index 785a05841f..ff5706c9cd 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.xml
+++ b/src/bin/cfgmgr/b10-cfgmgr.xml
@@ -20,7 +20,7 @@
- March 10, 2010
+ June 20, 2012
@@ -44,8 +44,11 @@
b10-cfgmgr
-
-
+
+
+
+
+
@@ -77,13 +80,6 @@
subscribers.
-
- When it exits, it saves its current configuration to
- /usr/local/var/bind10-devel/b10-config.db.
-
-
-
-
@@ -95,19 +91,37 @@
- config-filename,
- config-filename
+
- The configuration database filename to use. Can be either
- absolute or relative to data path.
- Defaults to b10-config.db
+
+ This will create a backup of the existing configuration
+ file, remove it, and
+ b10-cfgmgr8
+ will use the default configurations.
+ The name of the backup file can be found in the logs
+ (CFGMGR_BACKED_UP_CONFIG_FILE).
+ (It will append a number to the backup filename if a
+ previous backup file exists.)
+
- data-path,
+ config-filename,
+ config-filename
+
+
+ The configuration database filename to use. Can be either
+ absolute or relative to data path.
+ It defaults to "b10-config.db".
+
+
+
+
+
+ data-path,
data-path
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index ca91c9c79f..351e8bf964 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/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()
diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index 89d89ea64d..b5b65f6b33 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/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
diff --git a/src/bin/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index 5fdabb4852..856adf1c79 100644
--- a/src/bin/cmdctl/tests/cmdctl_test.py
+++ b/src/bin/cmdctl/tests/cmdctl_test.py
@@ -84,6 +84,7 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
self.handler.rfile = open("check.tmp", 'w+b')
def tearDown(self):
+ sys.stdout.close()
sys.stdout = self.old_stdout
self.handler.rfile.close()
os.remove('check.tmp')
@@ -306,6 +307,7 @@ class TestCommandControl(unittest.TestCase):
self.cmdctl = MyCommandControl(None, True)
def tearDown(self):
+ sys.stdout.close()
sys.stdout = self.old_stdout
def _check_config(self, cmdctl):
@@ -427,6 +429,9 @@ class TestSecureHTTPServer(unittest.TestCase):
MyCommandControl, verbose=True)
def tearDown(self):
+ # both sys.stdout and sys.stderr are the same, so closing one is
+ # sufficient
+ sys.stdout.close()
sys.stdout = self.old_stdout
sys.stderr = self.old_stderr
diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8
index 437a69d1ec..cb43fa382d 100644
--- a/src/bin/dbutil/b10-dbutil.8
+++ b/src/bin/dbutil/b10-dbutil.8
@@ -2,21 +2,12 @@
.\" Title: b10-dbutil
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2
-.\" Date: March 20, 2012
+.\" Date: June 20, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-DBUTIL" "8" "March 20, 2012" "BIND10" "BIND10"
-.\" -----------------------------------------------------------------
-.\" * Define some portability stuff
-.\" -----------------------------------------------------------------
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.\" http://bugs.debian.org/507673
-.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.ie \n(.g .ds Aq \(aq
-.el .ds Aq '
+.TH "B10\-DBUTIL" "8" "June 20, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -38,13 +29,14 @@ b10-dbutil \- Zone Database Maintenance Utility
.PP
The
\fBb10\-dbutil\fR
-utility is a general administration utility for SQL databases\&. (Currently only SQLite is supported by BIND 10\&.) It can report the current verion of the schema, and upgrade an existing database to the latest version of the schema\&.
+utility is a general administration utility for SQL databases for BIND 10\&. (Currently only SQLite is supported by BIND 10\&.) It can report the current verion of the schema, and upgrade an existing database to the latest version of the schema\&.
.PP
\fBb10\-dbutil\fR
-operates in one of two modes, check mode or upgrade mode\&.
+operates in one of two modesr: check mode or upgrade mode\&.
.PP
-In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, 2 if the schema is at a version not yet supported by this version of b10\-dbutil\&. Any higher value indicates an error during command\-line parsing or execution\&.
+In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, or 2 if the schema is at a version not yet supported by this version of
+\fBb10\-dbutil\fR\&. Any higher value indicates an error during command\-line parsing or execution\&.
.PP
When the upgrade function is selected (\fBb10\-dbutil \-\-upgrade\fR), the utility takes a copy of the database, then upgrades it to the latest version of the schema\&. The contents of the database remain intact\&. (The backup file is a file in the same directory as the database file\&. It has the same name, with "\&.backup" appended to it\&. If a file of that name already exists, the file will have the suffix "\&.backup\-1"\&. If that exists, the file will be suffixed "\&.backup\-2", and so on)\&. Exit status is 0 if the upgrade is either succesful or aborted by the user, and non\-zero if there is an error\&.
.PP
@@ -57,24 +49,29 @@ The arguments are as follows:
.PP
\fB\-\-check\fR
.RS 4
-Selects the version check function, which reports the current version of the database\&. This is incompatible with the \-\-upgrade option\&.
+Selects the version check function, which reports the current version of the database\&. This is mutually exclusive with the
+\fB\-\-upgrade\fR
+option\&.
.RE
.PP
\fB\-\-noconfirm\fR
.RS 4
-Only valid with \-\-upgrade, this disables the prompt\&. Normally the utility will print a warning that an upgrade is about to take place and request that you type "Yes" to continue\&. If this switch is given on the command line, no prompt will be issued: the utility will just perform the upgrade\&.
+Only valid with
+\fB\-\-upgrade\fR, this disables the prompt\&. Normally the utility will print a warning that an upgrade is about to take place and request that you type "Yes" to continue\&. If this switch is given on the command line, no prompt will be issued and the utility will just perform the upgrade\&.
.RE
.PP
\fB\-\-upgrade\fR
.RS 4
-Selects the upgrade function, which upgrades the database to the latest version of the schema\&. This is incompatible with the \-\-upgrade option\&.
+Selects the upgrade function, which upgrades the database to the latest version of the schema\&. This is mutually exclusive with the
+\fB\-\-check\fR
+option\&.
.sp
-The upgrade function will upgrade a BIND 10 database \- no matter how old the schema \- preserving all data\&. A backup file is created before the upgrade (with the same name as the database, but with "\&.backup" suffixed to it)\&. If the upgrade fails, this file can be copied back to restore the original database\&.
+The upgrade function will upgrade a BIND 10 database \(em no matter how old the schema \(em preserving all data\&. A backup file is created before the upgrade (with the same name as the database, but with "\&.backup" suffixed to it)\&. If the upgrade fails, this file can be copied back to restore the original database\&.
.RE
.PP
\fB\-\-verbose\fR
.RS 4
-Enable verbose mode\&. Each SQL command issued by the utility will be printed to stderr before it is executed\&.
+Enable verbose mode\&. Each SQL command issued by the utility will be printed to STDERR before it is executed\&.
.RE
.PP
\fB\-\-quiet\fR
@@ -84,7 +81,7 @@ Enable quiet mode\&. No output is printed, except errors during command\-line ar
.PP
\fB\fIdbfile\fR\fR
.RS 4
-Name of the database file to check of upgrade\&.
+Name of the database file to check or upgrade\&.
.RE
.SH "COPYRIGHT"
.br
diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml
index c1c0dee1ca..752b8a8504 100644
--- a/src/bin/dbutil/b10-dbutil.xml
+++ b/src/bin/dbutil/b10-dbutil.xml
@@ -20,7 +20,7 @@
- March 20, 2012
+ June 20, 2012
@@ -60,14 +60,15 @@
DESCRIPTION
- The b10-dbutil utility is a general administration
- utility for SQL databases. (Currently only SQLite is supported by
- BIND 10.) It can report the current verion of the schema, and upgrade
- an existing database to the latest version of the schema.
+ The b10-dbutil utility is a general
+ administration utility for SQL databases for BIND 10. (Currently
+ only SQLite is supported by BIND 10.) It can report the
+ current verion of the schema, and upgrade an existing database
+ to the latest version of the schema.
- b10-dbutil operates in one of two modes, check mode
+ b10-dbutil operates in one of two modesr: check mode
or upgrade mode.
@@ -76,9 +77,10 @@
utility reads the version of the database schema from the database
and prints it. It will tell you whether the schema is at the latest
version supported by BIND 10. Exit status is 0 if the schema is at
- the correct version, 1 if the schema is at an older version, 2 if
+ the correct version, 1 if the schema is at an older version, or 2 if
the schema is at a version not yet supported by this version of
- b10-dbutil. Any higher value indicates an error during command-line
+ b10-dbutil.
+ Any higher value indicates an error during command-line
parsing or execution.
@@ -115,8 +117,8 @@
Selects the version check function, which reports the
- current version of the database. This is incompatible
- with the --upgrade option.
+ current version of the database. This is mutually exclusive
+ with the option.
@@ -126,11 +128,12 @@
- Only valid with --upgrade, this disables the prompt.
+ Only valid with , this disables
+ the prompt.
Normally the utility will print a warning that an upgrade is
about to take place and request that you type "Yes" to continue.
If this switch is given on the command line, no prompt will
- be issued: the utility will just perform the upgrade.
+ be issued and the utility will just perform the upgrade.
@@ -141,15 +144,16 @@
Selects the upgrade function, which upgrades the database
- to the latest version of the schema. This is incompatible
- with the --upgrade option.
+ to the latest version of the schema. This is mutually exclusive
+ with the option.
- The upgrade function will upgrade a BIND 10 database - no matter how
- old the schema - preserving all data. A backup file is created
- before the upgrade (with the same name as the database, but with
- ".backup" suffixed to it). If the upgrade fails, this file can
- be copied back to restore the original database.
+ The upgrade function will upgrade a BIND 10 database —
+ no matter how old the schema — preserving all data.
+ A backup file is created before the upgrade (with the
+ same name as the database, but with ".backup" suffixed
+ to it). If the upgrade fails, this file can be copied
+ back to restore the original database.
@@ -160,7 +164,7 @@
Enable verbose mode. Each SQL command issued by the
- utility will be printed to stderr before it is executed.
+ utility will be printed to STDERR before it is executed.
@@ -181,7 +185,7 @@
- Name of the database file to check of upgrade.
+ Name of the database file to check or upgrade.
diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in
index 81f351e7ad..4b76a5680e 100755
--- a/src/bin/dbutil/dbutil.py.in
+++ b/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.
diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am
index c03b262aa2..b4231b3fea 100644
--- a/src/bin/dbutil/tests/Makefile.am
+++ b/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
diff --git a/src/bin/ddns/b10-ddns.8 b/src/bin/ddns/b10-ddns.8
index 131b6ccc37..95d82f4555 100644
--- a/src/bin/ddns/b10-ddns.8
+++ b/src/bin/ddns/b10-ddns.8
@@ -2,12 +2,12 @@
.\" Title: b10-ddns
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2
-.\" Date: February 28, 2012
+.\" Date: June 18, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-DDNS" "8" "February 28, 2012" "BIND10" "BIND10"
+.TH "B10\-DDNS" "8" "June 18, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -29,33 +29,29 @@ The
\fBb10\-ddns\fR
daemon provides the BIND 10 Dynamic Update (DDNS) service, as specified in RFC 2136\&. Normally it is started by the
\fBbind10\fR(8)
-boss process\&. When the
-\fBb10\-auth\fR
-DNS server receives a DDNS update,
-\fBb10\-ddns\fR
-updates the zone in the BIND 10 zone data store\&.
-.if n \{\
-.sp
-.\}
-.RS 4
-.it 1 an-trap
-.nr an-no-space-flag 1
-.nr an-break-flag 1
-.br
-.ps +1
-\fBNote\fR
-.ps -1
-.br
+boss process\&.
.PP
-Currently installed is a dummy component\&. It does not provide any functionality\&. It is a skeleton implementation that will be expanded later\&.
-.sp .5v
-.RE
+When the
+\fBb10\-auth\fR
+authoritative DNS server receives an UPDATE request, it internally forwards the request to
+\fBb10\-ddns\fR, which handles the rest of the request processing\&. When the processing is completed
+\fBb10\-ddns\fR
+will send a response to the client with the RCODE set to the value as specified in RFC 2136\&. If the zone has been changed as a result, it will internally notify
+\fBb10\-auth\fR
+and
+\fBb10\-xfrout\fR
+so the new version of the zone will be served, and other secondary servers will be notified via the DNS notify protocol\&.
.PP
This daemon communicates with BIND 10 over a
\fBb10-msgq\fR(8)
C\-Channel connection\&. If this connection is not established,
\fBb10\-ddns\fR
-will exit\&.
+will exit\&. The
+\fBb10\-ddns\fR
+daemon also depends on some other BIND 10 components (either directly or indirectly):
+\fBb10-auth\fR(8),
+\fBb10-xfrout\fR(8), and
+\fBb10-zonemgr\fR(8)\&.
.PP
\fBb10\-ddns\fR
@@ -65,9 +61,16 @@ receives its configurations from
.PP
The arguments are as follows:
.PP
+\fB\-h\fR, \fB\-\-help\fR
+.RS 4
+Print the command line arguments and exit\&.
+.RE
+.PP
\fB\-v\fR, \fB\-\-verbose\fR
.RS 4
-This value is ignored at this moment, but is provided for compatibility with the bind10 Boss process
+This value is ignored at this moment, but is provided for compatibility with the
+\fBbind10\fR
+Boss process\&.
.RE
.SH "CONFIGURATION AND COMMANDS"
.PP
@@ -75,7 +78,13 @@ The configurable settings are:
.PP
\fIzones\fR
-The zones option is a named set of zones that can be updated with DDNS\&. Each entry has one element called update_acl, which is a list of access control rules that define update permissions\&. By default this is empty; DDNS must be explicitely enabled per zone\&.
+The zones option is a list of configuration items for specific zones that can be updated with DDNS\&. Each entry is a map that can contain the following items:
+\fIorigin\fR
+is a textual domain name of the zone;
+\fIclass\fR
+(text) is the RR class of the zone; and
+\fIupdate_acl\fR
+is an ACL that controls permission for updates\&. See the BIND 10 Guide for configuration details\&. Note that not listing a zone in this list does not directly mean update requests for the zone are rejected, but the end result is the same because the default ACL for updates is to deny all requests\&.
.PP
The module commands are:
.PP
@@ -91,13 +100,15 @@ argument to select the process ID to stop\&. (Note that the BIND 10 boss process
\fBb10-auth\fR(8),
\fBb10-cfgmgr\fR(8),
\fBb10-msgq\fR(8),
+\fBb10-xfrout\fR(8),
+\fBb10-zonemgr\fR(8),
\fBbind10\fR(8),
BIND 10 Guide\&.
.SH "HISTORY"
.PP
The
\fBb10\-ddns\fR
-daemon was first implemented in December 2011 for the ISC BIND 10 project\&.
+daemon was first implemented in December 2011 for the ISC BIND 10 project\&. The first functional version was released in June 2012\&.
.SH "COPYRIGHT"
.br
Copyright \(co 2011-2012 Internet Systems Consortium, Inc. ("ISC")
diff --git a/src/bin/ddns/b10-ddns.xml b/src/bin/ddns/b10-ddns.xml
index 15fcb1a029..fb895b9952 100644
--- a/src/bin/ddns/b10-ddns.xml
+++ b/src/bin/ddns/b10-ddns.xml
@@ -20,7 +20,7 @@
- February 28, 2012
+ June 18, 2012
@@ -58,23 +58,33 @@
Normally it is started by the
bind108
boss process.
- When the b10-auth DNS server receives
- a DDNS update, b10-ddns updates the zone
- in the BIND 10 zone data store.
-
- Currently installed is a dummy component. It does not provide
- any functionality. It is a skeleton implementation that
- will be expanded later.
-
-
+
+ When the b10-auth authoritative DNS server
+ receives an UPDATE request, it internally forwards the request
+ to b10-ddns, which handles the rest of the
+ request processing.
+ When the processing is completed b10-ddns
+ will send a response to the client with the RCODE set to the
+ value as specified in RFC 2136.
+ If the zone has been changed as a result, it will internally
+ notify b10-auth and
+ b10-xfrout so the new version of the zone will
+ be served, and other secondary servers will be notified via the
+ DNS notify protocol.
+
This daemon communicates with BIND 10 over a
b10-msgq8
C-Channel connection. If this connection is not established,
b10-ddns will exit.
+ The b10-ddns daemon also depends on some other
+ BIND 10 components (either directly or indirectly):
+ b10-auth8,
+ b10-xfrout8, and
+ b10-zonemgr8.
@@ -90,6 +100,17 @@
+
+
+ ,
+
+
+
+
+ Print the command line arguments and exit.
+
+
+
,
@@ -98,7 +119,7 @@
This value is ignored at this moment, but is provided for
- compatibility with the bind10 Boss process
+ compatibility with the bind10 Boss process.
@@ -112,10 +133,18 @@
zones
- The zones option is a named set of zones that can be updated with
- DDNS. Each entry has one element called update_acl, which is
- a list of access control rules that define update permissions.
- By default this is empty; DDNS must be explicitely enabled per zone.
+ The zones option is a list of configuration items for specific
+ zones that can be updated with DDNS. Each entry is a map that
+ can contain the following items:
+ origin is a textual domain name of the zone;
+ class (text) is the RR class of the zone; and
+ update_acl is an ACL that controls
+ permission for updates.
+ See the BIND 10 Guide for configuration details.
+ Note that not listing a zone in this list does not directly
+ mean update requests for the zone are rejected, but the end
+ result is the same because the default ACL for updates is to
+ deny all requests.
@@ -144,6 +173,12 @@
b10-msgq8,
+
+ b10-xfrout8
+ ,
+
+ b10-zonemgr8
+ ,
bind108,
@@ -156,6 +191,7 @@
The b10-ddns daemon was first implemented
in December 2011 for the ISC BIND 10 project.
+ The first functional version was released in June 2012.
+
OPTIONSThe argument is as follow:
diff --git a/src/bin/stats/b10-stats.8 b/src/bin/stats/b10-stats.8
index e72ec0ecc1..2b9df85a51 100644
--- a/src/bin/stats/b10-stats.8
+++ b/src/bin/stats/b10-stats.8
@@ -2,12 +2,12 @@
.\" Title: b10-stats
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.75.2
-.\" Date: March 1, 2012
+.\" Date: June 20, 2012
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-STATS" "8" "March 1, 2012" "BIND10" "BIND10"
+.TH "B10\-STATS" "8" "June 20, 2012" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
@@ -27,18 +27,21 @@ b10-stats \- BIND 10 statistics module
.PP
The
\fBb10\-stats\fR
-is a daemon forked by
-\fBbind10\fR\&. Stats module collects statistics data from each module and reports statistics information via
-\fBbindctl\fR\&. It communicates by using the Command Channel by
+daemon collects statistics data from each BIND 10 module\&. Its statistics information may be reported via
+\fBbindctl\fR
+or
+\fBb10\-stats\-httpd\fR\&. It is started by
+\fBbind10\fR
+and communicates by using the Command Channel by
\fBb10\-msgq\fR
with other modules like
\fBbind10\fR,
\fBb10\-auth\fR
-and so on\&. It waits for coming data from other modules, then other modules send data to stats module periodically\&. Other modules send stats data to stats module independently from implementation of stats module, so the frequency of sending data may not be constant\&. Stats module collects data and aggregates it\&.
+and so on\&. It waits for coming data from other modules, then other modules send data to stats module periodically\&. Other modules send stats data to stats module independently from implementation of stats module, so the frequency of sending data may not be constant\&. The stats module collects data and aggregates it\&.
\fBb10\-stats\fR
invokes an internal command for
\fBbind10\fR
-after its initial starting because it\'s sure to collect statistics data from
+after its initial starting to make sure it collects statistics data from
\fBbind10\fR\&.
.SH "OPTIONS"
.PP
@@ -46,9 +49,7 @@ The arguments are as follows:
.PP
\fB\-v\fR, \fB\-\-verbose\fR
.RS 4
-This
-\fBb10\-stats\fR
-switches to verbose mode\&. It sends verbose messages to STDOUT\&.
+This enables maximum debug logging\&.
.RE
.SH "CONFIGURATION AND COMMANDS"
.PP
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index b353f8f075..32b636f66c 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -20,7 +20,7 @@
- March 1, 2012
+ June 20, 2012
@@ -52,22 +52,22 @@
DESCRIPTION
- The b10-stats is a daemon forked by
- bind10. Stats module collects statistics data
- from each module and reports statistics information
- via bindctl. It communicates by using the
+ The b10-stats daemon collects statistics data
+ from each BIND 10 module. Its statistics information may be
+ reported via bindctl or
+ b10-stats-httpd. It is started by
+ bind10 and communicates by using the
Command Channel by b10-msgq with other
- modules
- like bind10, b10-auth and
- so on. It waits for coming data from other modules, then other
- modules send data to stats module periodically. Other modules
- send stats data to stats module independently from
- implementation of stats module, so the frequency of sending data
- may not be constant. Stats module collects data and aggregates
- it. b10-stats invokes an internal command
- for bind10 after its initial starting because it's
- sure to collect statistics data from bind10.
-
+ modules like bind10, b10-auth
+ and so on. It waits for coming data from other modules, then
+ other modules send data to stats module periodically. Other
+ modules send stats data to stats module independently from
+ implementation of stats module, so the frequency of sending
+ data may not be constant. The stats module collects data and
+ aggregates it. b10-stats invokes an internal
+ command for bind10 after its initial
+ starting to make sure it collects statistics data from
+ bind10.
@@ -79,8 +79,7 @@
,
- This b10-stats switches to verbose
- mode. It sends verbose messages to STDOUT.
+ This enables maximum debug logging.
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index fd59c3cd00..b2e341726d 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -495,7 +495,7 @@ if __name__ == "__main__":
parser = OptionParser()
parser.add_option(
"-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
+ help="enable maximum debug logging")
(options, args) = parser.parse_args()
if options.verbose:
isc.log.init("b10-stats", "DEBUG", 99)
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index 7e4da96d0c..8b13766cd6 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -826,7 +826,7 @@ if __name__ == "__main__":
parser = OptionParser()
parser.add_option(
"-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
+ help="enable maximum debug logging")
(options, args) = parser.parse_args()
if options.verbose:
isc.log.init("b10-stats-httpd", "DEBUG", 99)
diff --git a/src/bin/stats/stats_httpd_messages.mes b/src/bin/stats/stats_httpd_messages.mes
index dbd065078c..ad2e97f387 100644
--- a/src/bin/stats/stats_httpd_messages.mes
+++ b/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.
diff --git a/src/bin/stats/stats_messages.mes b/src/bin/stats/stats_messages.mes
index cfffb3adb8..3e75348c88 100644
--- a/src/bin/stats/stats_messages.mes
+++ b/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.
diff --git a/src/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index 01254d411b..b98996a6ba 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/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
diff --git a/src/bin/tests/process_rename_test.py.in b/src/bin/tests/process_rename_test.py.in
index f96c023841..1156b29292 100644
--- a/src/bin/tests/process_rename_test.py.in
+++ b/src/bin/tests/process_rename_test.py.in
@@ -25,7 +25,8 @@ class TestRename(unittest.TestCase):
def __scan(self, directory, script, fun):
# Scan one script if it contains call to the renaming function
filename = os.path.join(directory, script)
- data = ''.join(open(filename).readlines())
+ with open(filename) as f:
+ data = ''.join(f.readlines())
prettyname = 'src' + filename[filename.rfind('../') + 2:]
self.assertTrue(fun.search(data),
"Didn't find a call to isc.util.process.rename in " + prettyname)
@@ -53,8 +54,8 @@ class TestRename(unittest.TestCase):
# Find all Makefile and extract names of scripts
for (d, _, fs) in os.walk('@top_builddir@'):
if 'Makefile' in fs:
- makefile = ''.join(open(os.path.join(d,
- "Makefile")).readlines())
+ with open(os.path.join(d, "Makefile")) as f:
+ makefile = ''.join(f.readlines())
for (var, _) in lines.findall(re.sub(excluded_lines, '',
makefile)):
for (script, _) in scripts.findall(var):
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index b88d6a99b9..a1f4d28263 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -2127,7 +2127,8 @@ class TestXfrin(unittest.TestCase):
self.assertFalse(self.xfr._module_cc.stopped);
self.xfr.shutdown()
self.assertTrue(self.xfr._module_cc.stopped);
- sys.stderr= self.stderr_backup
+ sys.stderr.close()
+ sys.stderr = self.stderr_backup
def _do_parse_zone_name_class(self):
return self.xfr._parse_zone_name_and_class(self.args)
@@ -2577,7 +2578,7 @@ class TestXfrin(unittest.TestCase):
self.common_ixfr_setup('refresh', False)
self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
-class TextXfrinMemoryZones(unittest.TestCase):
+class TestXfrinMemoryZones(unittest.TestCase):
def setUp(self):
self.xfr = MockXfrin()
# Configuration snippet containing 2 memory datasources,
@@ -2736,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
@@ -2751,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.
@@ -2795,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):
@@ -2825,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):
"""
@@ -2861,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.
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 27b91a99de..652f870a5a 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -33,6 +33,7 @@ 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")
@@ -66,10 +67,10 @@ 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'
-ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
# Constants for debug levels.
DBG_XFRIN_TRACE = logger.DBGLVL_TRACE_BASIC
@@ -1246,6 +1247,15 @@ 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
@@ -1529,7 +1539,7 @@ class Xfrin:
def _set_db_file(self):
db_file, is_default =\
- self._module_cc.get_remote_config_value("Auth", "database_file")
+ self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
if is_default and "B10_FROM_BUILD" in os.environ:
# override the local database setting if it is default and we
# are running from the source tree
@@ -1539,7 +1549,7 @@ class Xfrin:
"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.
@@ -1548,6 +1558,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:
@@ -1566,8 +1577,9 @@ 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)
+ msg = create_command(notify_out.ZONE_XFRIN_FAILED, param)
# catch the exception, in case msgq has been killed.
try:
seq = self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 25a1fc1c06..ffea249c2e 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -15,6 +15,11 @@
# No namespace declaration - these constants go in the global namespace
# of the xfrin messages python module.
+% XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3
+There was a successful zone transfer, and the zone is served by b10-auth
+in the in-memory data source using sqlite3 as a backend. We send the
+"loadzone" command for the zone to b10-auth.
+
% XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
The serial fields of the first and last SOAs of AXFR (including AXFR-style
IXFR) are not the same. According to RFC 5936 these two SOAs must be the
@@ -113,6 +118,10 @@ There was a problem sending a message to the xfrout module or the
zone manager. This most likely means that the msgq daemon has quit or
was killed.
+% XFRIN_MSGQ_SEND_ERROR_AUTH error while contacting %1
+There was a problem sending a message to b10-auth. This most likely
+means that the msgq daemon has quit or was killed.
+
% XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER error while contacting %1
There was a problem sending a message to the zone manager. This most
likely means that the msgq daemon has quit or was killed.
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index b60535c49b..e4fc873e46 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -60,6 +60,9 @@ class MySocket():
self.sendqueue.extend(data);
return len(data)
+ def fileno(self):
+ return 42 # simply return a constant dummy value
+
def readsent(self):
if len(self.sendqueue) >= 2:
size = 2 + struct.unpack("!H", self.sendqueue[:2])[0]
@@ -1155,6 +1158,15 @@ class TestUnixSockServer(unittest.TestCase):
def setUp(self):
self.write_sock, self.read_sock = socket.socketpair()
self.unix = MyUnixSockServer()
+ # Some test below modify these module-wide attributes. We'll need
+ # to restore them at the end of each test, so we remember them here.
+ self.__select_bak = xfrout.select.select
+ self.__recv_fd_back = xfrout.recv_fd
+
+ def tearDown(self):
+ # Restore possibly faked module-wide attributes.
+ xfrout.select.select = self.__select_bak
+ xfrout.recv_fd = self.__recv_fd_back
def test_tsig_keyring(self):
"""
@@ -1201,6 +1213,7 @@ class TestUnixSockServer(unittest.TestCase):
self.assertEqual((socket.AF_INET, socket.SOCK_STREAM,
('127.0.0.1', 12345)),
self.unix._guess_remote(sock.fileno()))
+ sock.close()
if socket.has_ipv6:
# Don't check IPv6 address on hosts not supporting them
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
@@ -1208,6 +1221,7 @@ class TestUnixSockServer(unittest.TestCase):
self.assertEqual((socket.AF_INET6, socket.SOCK_STREAM,
('::1', 12345, 0, 0)),
self.unix._guess_remote(sock.fileno()))
+ sock.close()
# Try when pretending there's no IPv6 support
# (No need to pretend when there's really no IPv6)
xfrout.socket.has_ipv6 = False
@@ -1216,6 +1230,7 @@ class TestUnixSockServer(unittest.TestCase):
self.assertEqual((socket.AF_INET, socket.SOCK_STREAM,
('127.0.0.1', 12345)),
self.unix._guess_remote(sock.fileno()))
+ sock.close()
# Return it back
xfrout.socket.has_ipv6 = True
@@ -1375,19 +1390,13 @@ class TestUnixSockServer(unittest.TestCase):
self._remove_file(sock_file)
self.assertFalse(self.unix._sock_file_in_use(sock_file))
self._start_unix_sock_server(sock_file)
-
- old_stdout = sys.stdout
- sys.stdout = open(os.devnull, 'w')
self.assertTrue(self.unix._sock_file_in_use(sock_file))
- sys.stdout = old_stdout
def test_remove_unused_sock_file_in_use(self):
sock_file = 'temp.sock.file'
self._remove_file(sock_file)
self.assertFalse(self.unix._sock_file_in_use(sock_file))
self._start_unix_sock_server(sock_file)
- old_stdout = sys.stdout
- sys.stdout = open(os.devnull, 'w')
try:
self.unix._remove_unused_sock_file(sock_file)
except SystemExit:
@@ -1396,8 +1405,6 @@ class TestUnixSockServer(unittest.TestCase):
# This should never happen
self.assertTrue(False)
- sys.stdout = old_stdout
-
def test_remove_unused_sock_file_dir(self):
import tempfile
dir_name = tempfile.mkdtemp()
@@ -1411,9 +1418,46 @@ class TestUnixSockServer(unittest.TestCase):
# This should never happen
self.assertTrue(False)
+ sys.stdout.close()
sys.stdout = old_stdout
os.rmdir(dir_name)
+ def __fake_select(self, r, w, e):
+ '''select emulator used in select_loop_fail test.'''
+ # This simplified faked function assumes to be called at most once,
+ # and in that case just return a pre-configured "readable" sockets.
+ if self.__select_count > 0:
+ raise RuntimeError('select called unexpected number of times')
+ self.__select_count += 1
+ return (self.__select_return_redable, [], [])
+
+ def test_select_loop_fail(self):
+ '''Check failure events in the main loop.'''
+ # setup faked select() environments
+ self.unix._read_sock = MySocket(socket.AF_INET6, socket.SOCK_STREAM)
+ xfrout.select.select = self.__fake_select
+ self.__select_return_redable = [MySocket(socket.AF_INET6,
+ socket.SOCK_STREAM)]
+
+ # Check that loop terminates if recv_fd() fails.
+ for ret_code in [-1, FD_SYSTEM_ERROR]:
+ # fake recv_fd so it returns the faked failure code.
+ xfrout.recv_fd = lambda fileno: ret_code
+
+ # reset the counter, go to the loop.
+ self.__select_count = 0
+ self.unix._select_loop(self.__select_return_redable[0])
+ # select should have been called exactly once.
+ self.assertEqual(1, self.__select_count)
+
+ # Next, we test the case where recf_fd succeeds but receiving the
+ # request fails.
+ self.__select_count = 0
+ xfrout.recv_fd = lambda fileno: 1
+ self.unix._receive_query_message = lambda fd: None
+ self.unix._select_loop(self.__select_return_redable[0])
+ self.assertEqual(1, self.__select_count)
+
class TestInitialization(unittest.TestCase):
def setEnv(self, name, value):
if value is None:
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 4dd12cee17..46ae687323 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -678,30 +678,40 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
except socket.error:
logger.error(XFROUT_FETCH_REQUEST_ERROR)
return
+ self._select_loop(request)
+
+ def _select_loop(self, request_sock):
+ '''Main loop for a single session between xfrout and auth.
+
+ This is a dedicated subroutine of handle_request(), but is defined
+ as a separate "protected" method for the convenience of tests.
+ '''
# Check self._shutdown_event to ensure the real shutdown comes.
# Linux could trigger a spurious readable event on the _read_sock
# due to a bug, so we need perform a double check.
while not self._shutdown_event.is_set(): # Check if xfrout is shutdown
try:
- (rlist, wlist, xlist) = select.select([self._read_sock, request], [], [])
+ (rlist, wlist, xlist) = select.select([self._read_sock,
+ request_sock], [], [])
except select.error as e:
if e.args[0] == errno.EINTR:
(rlist, wlist, xlist) = ([], [], [])
continue
else:
- logger.error(XFROUT_SOCKET_SELECT_ERROR, str(e))
+ logger.error(XFROUT_SOCKET_SELECT_ERROR, e)
break
- # self.server._shutdown_event will be set by now, if it is not a false
- # alarm
+ # self.server._shutdown_event will be set by now, if it is not a
+ # false alarm
if self._read_sock in rlist:
continue
try:
- self.process_request(request)
+ if not self.process_request(request_sock):
+ break
except Exception as pre:
- logger.error(XFROUT_PROCESS_REQUEST_ERROR, str(pre))
+ logger.error(XFROUT_PROCESS_REQUEST_ERROR, pre)
break
def _handle_request_noblock(self):
@@ -713,26 +723,33 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
def process_request(self, request):
"""Receive socket fd and query message from auth, then
- start a new thread to process the request."""
+ start a new thread to process the request.
+
+ Return: True if everything is okay; otherwise False, in which case
+ the calling thread will terminate.
+
+ """
sock_fd = recv_fd(request.fileno())
if sock_fd < 0:
- # This may happen when one xfrout process try to connect to
- # xfrout unix socket server, to check whether there is another
- # xfrout running.
- if sock_fd == FD_SYSTEM_ERROR:
- logger.error(XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR)
- return
+ logger.warn(XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR)
+ return False
- # receive request msg
+ # receive request msg. If it fails we simply terminate the thread;
+ # it might be possible to recover from this state, but it's more likely
+ # that auth and xfrout are in inconsistent states. So it will make
+ # more sense to restart in a new session.
request_data = self._receive_query_message(request)
- if not request_data:
- return
+ if request_data is None:
+ # The specific exception type doesn't matter so we use session
+ # error.
+ raise XfroutSessionError('Failed to get complete xfr request')
t = threading.Thread(target=self.finish_request,
- args = (sock_fd, request_data))
+ args=(sock_fd, request_data))
if self.daemon_threads:
t.daemon = True
t.start()
+ return True
def _guess_remote(self, sock_fd):
"""Guess remote address and port of the socket.
@@ -747,12 +764,15 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
# to care about the SOCK_STREAM parameter at all (which it really is,
# except for testing)
if socket.has_ipv6:
- sock = socket.fromfd(sock_fd, socket.AF_INET6, socket.SOCK_STREAM)
+ sock_domain = socket.AF_INET6
else:
# To make it work even on hosts without IPv6 support
# (Any idea how to simulate this in test?)
- sock = socket.fromfd(sock_fd, socket.AF_INET, socket.SOCK_STREAM)
+ sock_domain = socket.AF_INET
+
+ sock = socket.fromfd(sock_fd, sock_domain, socket.SOCK_STREAM)
peer = sock.getpeername()
+ sock.close()
# Identify the correct socket family. Due to the above "trick",
# we cannot simply use sock.family.
@@ -761,6 +781,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
socket.inet_pton(socket.AF_INET6, peer[0])
except socket.error:
family = socket.AF_INET
+
return (family, socket.SOCK_STREAM, peer)
def finish_request(self, sock_fd, request_data):
@@ -805,8 +826,10 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
sock = socket.socket(socket.AF_UNIX)
sock.connect(sock_file)
except socket.error as err:
+ sock.close()
return False
else:
+ sock.close()
return True
def shutdown(self):
@@ -985,7 +1008,7 @@ class XfroutServer:
self.shutdown()
answer = create_answer(0)
- elif cmd == notify_out.ZONE_NEW_DATA_READY_CMD:
+ elif cmd == "notify":
zone_name = args.get('zone_name')
zone_class = args.get('zone_class')
if not zone_class:
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index 9996a5ad70..9f674a2cf8 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -23,22 +23,15 @@ a valid TSIG key.
There was a problem reading from the command and control channel. The
most likely cause is that the msgq daemon is not running.
-% XFROUT_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
-cause is that the configuration database contains a syntax error and xfrout
-failed to start at initialization. A detailed error message from the module
-will also be displayed.
-
-% XFROUT_CONFIG_ERROR error found in configuration data: %1
-The xfrout process encountered an error when installing the configuration at
-startup time. Details of the error are included in the log message.
-
% XFROUT_CC_SESSION_TIMEOUT_ERROR timeout waiting for cc response
There was a problem reading a response from another module over the
command and control channel. The most likely cause is that the
configuration manager b10-cfgmgr is not running.
+% XFROUT_CONFIG_ERROR error found in configuration data: %1
+The xfrout process encountered an error when installing the configuration at
+startup time. Details of the error are included in the log message.
+
% XFROUT_FETCH_REQUEST_ERROR socket error while fetching a request from the auth daemon
There was a socket error while contacting the b10-auth daemon to
fetch a transfer request. The auth daemon may have shutdown.
@@ -56,6 +49,52 @@ are missing on the system, or the PYTHONPATH variable is not correct.
The specific place where this library needs to be depends on your
system and your specific installation.
+% XFROUT_IXFR_MULTIPLE_SOA IXFR client %1: authority section has multiple SOAs
+An IXFR request was received with more than one SOA RRs in the authority
+section. The xfrout daemon rejects the request with an RCODE of
+FORMERR.
+
+% XFROUT_IXFR_NO_JOURNAL_SUPPORT IXFR client %1, %2: journaling not supported in the data source, falling back to AXFR
+An IXFR request was received but the underlying data source did
+not support journaling. The xfrout daemon fell back to AXFR-style
+IXFR.
+
+% XFROUT_IXFR_NO_SOA IXFR client %1: missing SOA
+An IXFR request was received with no SOA RR in the authority section.
+The xfrout daemon rejects the request with an RCODE of FORMERR.
+
+% XFROUT_IXFR_NO_VERSION IXFR client %1, %2: version (%3 to %4) not in journal, falling back to AXFR
+An IXFR request was received, but the requested range of differences
+were not found in the data source. The xfrout daemon fell back to
+AXFR-style IXFR.
+
+% XFROUT_IXFR_NO_ZONE IXFR client %1, %2: zone not found with journal
+The requested zone in IXFR was not found in the data source
+even though the xfrout daemon sucessfully found the SOA RR of the zone
+in the data source. This can happen if the administrator removed the
+zone from the data source within the small duration between these
+operations, but it's more likely to be a bug or broken data source.
+Unless you know why this message was logged, and especially if it
+happens often, it's advisable to check whether the data source is
+valid for this zone. The xfrout daemon considers it a possible,
+though unlikely, event, and returns a response with an RCODE of
+NOTAUTH.
+
+% XFROUT_IXFR_UPTODATE IXFR client %1, %2: client version is new enough (theirs=%3, ours=%4)
+An IXFR request was received, but the client's SOA version is the same as
+or newer than that of the server. The xfrout server responds to the
+request with the answer section being just one SOA of that version.
+Note: as of this wrting the 'newer version' cannot be identified due to
+the lack of support for the serial number arithmetic. This will soon
+be implemented.
+
+% XFROUT_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
+cause is that the configuration database contains a syntax error and xfrout
+failed to start at initialization. A detailed error message from the module
+will also be displayed.
+
% XFROUT_NEW_CONFIG Update xfrout configuration
New configuration settings have been sent from the configuration
manager. The xfrout daemon will now apply them.
@@ -76,11 +115,15 @@ In general, this should only occur for unexpected problems like
memory allocation failures, as the query should already have been
parsed by the b10-auth daemon, before it was passed here.
-% XFROUT_PROCESS_REQUEST_ERROR error processing transfer request: %2
-There was an error processing a transfer request. The error is included
-in the log message, but at this point no specific information other
-than that could be given. This points to incomplete exception handling
-in the code.
+% XFROUT_PROCESS_REQUEST_ERROR error processing transfer request: %1
+There was an error in receiving a transfer request from b10-auth.
+This is generally an unexpected event, but is possible when, for
+example, b10-auth terminates in the middle of forwarding the request.
+When this happens it's unlikely to be recoverable with the same
+communication session with b10-auth, so b10-xfrout drops it and
+waits for a new session. In any case, this error indicates that
+there's something very wrong in the system, so it's advisable to check
+the over all status of the BIND 10 system.
% XFROUT_QUERY_DROPPED %1 client %2: request to transfer %3 dropped
The xfrout process silently dropped a request to transfer zone to
@@ -88,12 +131,6 @@ given host. This is required by the ACLs. The %2 represents the IP
address and port of the peer requesting the transfer, and the %3
represents the zone name and class.
-% XFROUT_QUERY_REJECTED %1 client %2: request to transfer %3 rejected
-The xfrout process rejected (by REFUSED rcode) a request to transfer zone to
-given host. This is because of ACLs. The %2 represents the IP
-address and port of the peer requesting the transfer, and the %3
-represents the zone name and class.
-
% XFROUT_QUERY_QUOTA_EXCCEEDED %1 client %2: request denied due to quota (%3)
The xfr request was rejected because the server was already handling
the maximum number of allowable transfers as specified in the transfers_out
@@ -104,20 +141,28 @@ this parameter; if the server is being too busy due to requests from
unexpected clients you may want to restrict the legitimate clients
with ACL.
-% XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR error receiving the file descriptor for an XFR connection
-There was an error receiving the file descriptor for the transfer
-request. Normally, the request is received by b10-auth, and passed on
-to the xfrout daemon, so it can answer directly. However, there was a
-problem receiving this file descriptor. The request will be ignored.
+% XFROUT_QUERY_REJECTED %1 client %2: request to transfer %3 rejected
+The xfrout process rejected (by REFUSED rcode) a request to transfer zone to
+given host. This is because of ACLs. The %2 represents the IP
+address and port of the peer requesting the transfer, and the %3
+represents the zone name and class.
% XFROUT_RECEIVED_SHUTDOWN_COMMAND shutdown command received
The xfrout daemon received a shutdown command from the command channel
and will now shut down.
-% XFROUT_REMOVE_UNIX_SOCKET_FILE_ERROR error clearing unix socket file %1: %2
-When shutting down, the xfrout daemon tried to clear the unix socket
-file used for communication with the auth daemon. It failed to remove
-the file. The reason for the failure is given in the error message.
+% XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR error receiving the file descriptor for an XFR connection
+There was an error receiving the file descriptor for the transfer
+request from b10-auth. There can be several reasons for this, but
+the most likely cause is that b10-auth terminates for some reason
+(maybe it's a bug of b10-auth, maybe it's an intentional restart by
+the administrator), so depending on how this happens it may or may not
+be a serious error. But in any case this is not expected to happen
+frequently, and it's advisable to figure out how this happened if
+this message is logged. Even if this error happens xfrout will reset
+its internal state and will keep receiving further requests. So
+if it's just a temporary restart of b10-auth the administrator does
+not have to do anything.
% XFROUT_REMOVE_OLD_UNIX_SOCKET_FILE_ERROR error removing unix socket file %1: %2
The unix socket file xfrout needs for contact with the auth daemon
@@ -126,6 +171,11 @@ removing it. It is likely that we do not have permission to remove
this file. The specific error is show in the log message. The xfrout
daemon will shut down.
+% XFROUT_REMOVE_UNIX_SOCKET_FILE_ERROR error clearing unix socket file %1: %2
+When shutting down, the xfrout daemon tried to clear the unix socket
+file used for communication with the auth daemon. It failed to remove
+the file. The reason for the failure is given in the error message.
+
% XFROUT_SOCKET_SELECT_ERROR error while calling select() on request socket: %1
There was an error while calling select() on the socket that informs
the xfrout daemon that a new xfrout request has arrived. This should
@@ -151,6 +201,13 @@ on, but the file is in use. The most likely cause is that another
xfrout daemon process is still running. This xfrout daemon (the one
printing this message) will not start.
+% XFROUT_XFR_TRANSFER_CHECK_ERROR %1 client %2: check for transfer of %3 failed: %4
+Pre-response check for an incomding XFR request failed unexpectedly.
+The most likely cause of this is that some low level error in the data
+source, but it may also be other general (more unlikely) errors such
+as memory shortage. Some detail of the error is also included in the
+message. The xfrout server tries to return a SERVFAIL response in this case.
+
% XFROUT_XFR_TRANSFER_DONE %1 client %2: transfer of %3 complete
The transfer of the given zone has been completed successfully, or was
aborted due to a shutdown event.
@@ -161,13 +218,6 @@ an AXFR query. The error message of the exception is included in the
log message, but this error most likely points to incomplete exception
handling in the code.
-% XFROUT_XFR_TRANSFER_CHECK_ERROR %1 client %2: check for transfer of %3 failed: %4
-Pre-response check for an incomding XFR request failed unexpectedly.
-The most likely cause of this is that some low level error in the data
-source, but it may also be other general (more unlikely) errors such
-as memory shortage. Some detail of the error is also included in the
-message. The xfrout server tries to return a SERVFAIL response in this case.
-
% XFROUT_XFR_TRANSFER_FAILED %1 client %2: transfer of %3 failed, rcode: %4
A transfer out for the given zone failed. An error response is sent
to the client. The given rcode is the rcode that is set in the error
@@ -181,42 +231,3 @@ Xfrout/max_transfers_out, has been reached).
% XFROUT_XFR_TRANSFER_STARTED %1 client %2: transfer of zone %3 has started
A transfer out of the given zone has started.
-
-% XFROUT_IXFR_MULTIPLE_SOA IXFR client %1: authority section has multiple SOAs
-An IXFR request was received with more than one SOA RRs in the authority
-section. The xfrout daemon rejects the request with an RCODE of
-FORMERR.
-
-% XFROUT_IXFR_NO_SOA IXFR client %1: missing SOA
-An IXFR request was received with no SOA RR in the authority section.
-The xfrout daemon rejects the request with an RCODE of FORMERR.
-
-% XFROUT_IXFR_NO_JOURNAL_SUPPORT IXFR client %1, %2: journaling not supported in the data source, falling back to AXFR
-An IXFR request was received but the underlying data source did
-not support journaling. The xfrout daemon fell back to AXFR-style
-IXFR.
-
-% XFROUT_IXFR_UPTODATE IXFR client %1, %2: client version is new enough (theirs=%3, ours=%4)
-An IXFR request was received, but the client's SOA version is the same as
-or newer than that of the server. The xfrout server responds to the
-request with the answer section being just one SOA of that version.
-Note: as of this wrting the 'newer version' cannot be identified due to
-the lack of support for the serial number arithmetic. This will soon
-be implemented.
-
-% XFROUT_IXFR_NO_VERSION IXFR client %1, %2: version (%3 to %4) not in journal, falling back to AXFR
-An IXFR request was received, but the requested range of differences
-were not found in the data source. The xfrout daemon fell back to
-AXFR-style IXFR.
-
-% XFROUT_IXFR_NO_ZONE IXFR client %1, %2: zone not found with journal
-The requested zone in IXFR was not found in the data source
-even though the xfrout daemon sucessfully found the SOA RR of the zone
-in the data source. This can happen if the administrator removed the
-zone from the data source within the small duration between these
-operations, but it's more likely to be a bug or broken data source.
-Unless you know why this message was logged, and especially if it
-happens often, it's advisable to check whether the data source is
-valid for this zone. The xfrout daemon considers it a possible,
-though unlikely, event, and returns a response with an RCODE of
-NOTAUTH.
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 548d921a54..42ed679661 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -21,6 +21,7 @@ import os
import tempfile
from zonemgr import *
from isc.testutils.ccsession_mock import MockModuleCCSession
+from isc.notify import notify_out
ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
@@ -111,6 +112,7 @@ class TestZonemgrRefresh(unittest.TestCase):
def tearDown(self):
if os.path.exists(TEST_SQLITE3_DBFILE):
os.unlink(TEST_SQLITE3_DBFILE)
+ sys.stderr.close()
sys.stderr = self.stderr_backup
def test_random_jitter(self):
@@ -683,7 +685,7 @@ class TestZonemgr(unittest.TestCase):
self.assertEqual(answer1, self.zonemgr._parse_cmd_params(params1, ZONE_NOTIFY_COMMAND))
params2 = {"zone_name" : "example.com.", "zone_class" : "IN"}
answer2 = ZONE_NAME_CLASS3_IN
- self.assertEqual(answer2, self.zonemgr._parse_cmd_params(params2, ZONE_XFRIN_SUCCESS_COMMAND))
+ self.assertEqual(answer2, self.zonemgr._parse_cmd_params(params2, notify_out.ZONE_NEW_DATA_READY_CMD))
self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
params1 = {"zone_class" : "CH"}
self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 87589a84bc..8cb616d917 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -39,6 +39,7 @@ from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
import isc.util.process
from isc.log_messages.zonemgr_messages import *
+from isc.notify import notify_out
# Initialize logging for called modules.
isc.log.init("b10-zonemgr")
@@ -78,8 +79,6 @@ XFRIN_MODULE_NAME = 'Xfrin'
AUTH_MODULE_NAME = 'Auth'
# define command name
-ZONE_XFRIN_FAILED_COMMAND = 'zone_xfrin_failed'
-ZONE_XFRIN_SUCCESS_COMMAND = 'zone_new_data_ready'
ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr'
ZONE_NOTIFY_COMMAND = 'notify'
@@ -428,6 +427,8 @@ class ZonemgrRefresh:
self._thread.join()
# Wipe out what we do not need
self._thread = None
+ self._read_sock.close()
+ self._write_sock.close()
self._read_sock = None
self._write_sock = None
@@ -621,7 +622,7 @@ class Zonemgr:
def command_handler(self, command, args):
"""Handle command receivd from command channel.
ZONE_NOTIFY_COMMAND is issued by Auth process;
- ZONE_XFRIN_SUCCESS_COMMAND and ZONE_XFRIN_FAILED_COMMAND are issued by
+ ZONE_NEW_DATA_READY_CMD and ZONE_XFRIN_FAILED are issued by
Xfrin process;
shutdown is issued by a user or Boss process. """
answer = create_answer(0)
@@ -635,7 +636,7 @@ class Zonemgr:
# Send notification to zonemgr timer thread
self._master_socket.send(b" ")# make self._slave_socket readble
- elif command == ZONE_XFRIN_SUCCESS_COMMAND:
+ elif command == notify_out.ZONE_NEW_DATA_READY_CMD:
""" Handle xfrin success command"""
zone_name_class = self._parse_cmd_params(args, command)
logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS, zone_name_class[0], zone_name_class[1])
@@ -643,7 +644,7 @@ class Zonemgr:
self._zone_refresh.zone_refresh_success(zone_name_class)
self._master_socket.send(b" ")# make self._slave_socket readble
- elif command == ZONE_XFRIN_FAILED_COMMAND:
+ elif command == notify_out.ZONE_XFRIN_FAILED:
""" Handle xfrin fail command"""
zone_name_class = self._parse_cmd_params(args, command)
logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED, zone_name_class[0], zone_name_class[1])
diff --git a/src/bin/zonemgr/zonemgr_messages.mes b/src/bin/zonemgr/zonemgr_messages.mes
index c866b792fd..88f8dcf237 100644
--- a/src/bin/zonemgr/zonemgr_messages.mes
+++ b/src/bin/zonemgr/zonemgr_messages.mes
@@ -67,10 +67,6 @@ zone manager to record the master server for the zone and start a timer;
when the timer expires, the master will be polled to see if it contains
new data.
-% ZONEMGR_STARTED zonemgr started
-This informational message is output by zonemgr when all initialization
-has been completed and it is entering its main loop.
-
% ZONEMGR_RECEIVE_SHUTDOWN received SHUTDOWN command
This is a debug message indicating that the zone manager has received
a SHUTDOWN command over the command channel from the Boss process.
@@ -120,6 +116,10 @@ problem is that the daemon is not running.
% ZONEMGR_SHUTDOWN zone manager has shut down
A debug message, output when the zone manager has shut down completely.
+% ZONEMGR_STARTED zonemgr started
+This informational message is output by zonemgr when all initialization
+has been completed and it is entering its main loop.
+
% ZONEMGR_STARTING zone manager starting
A debug message output when the zone manager starts up.
diff --git a/src/lib/acl/tests/Makefile.am b/src/lib/acl/tests/Makefile.am
index 636951199b..6df91c7703 100644
--- a/src/lib/acl/tests/Makefile.am
+++ b/src/lib/acl/tests/Makefile.am
@@ -8,6 +8,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/lib/asiodns/tests/Makefile.am b/src/lib/asiodns/tests/Makefile.am
index 95094f01b7..a96a3e67da 100644
--- a/src/lib/asiodns/tests/Makefile.am
+++ b/src/lib/asiodns/tests/Makefile.am
@@ -12,6 +12,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc
index 63830a5b5e..2354521906 100644
--- a/src/lib/asiolink/io_endpoint.cc
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -14,10 +14,6 @@
#include
-#include // for some IPC/network system calls
-#include
-#include
-
#include
#include
@@ -26,6 +22,13 @@
#include
#include
+#include
+
+#include
+#include // for some IPC/network system calls
+#include
+#include
+
using namespace std;
namespace isc {
@@ -58,5 +61,18 @@ IOEndpoint::operator!=(const IOEndpoint& other) const {
return (!operator==(other));
}
+ostream&
+operator<<(ostream& os, const IOEndpoint& endpoint) {
+ if (endpoint.getFamily() == AF_INET6) {
+ os << "[" << endpoint.getAddress().toText() << "]";
+ } else {
+ // In practice this should be AF_INET, but it's not guaranteed by
+ // the interface. We'll use the result of textual address
+ // representation opaquely.
+ os << endpoint.getAddress().toText();
+ }
+ os << ":" << boost::lexical_cast(endpoint.getPort());
+ return (os);
+}
} // namespace asiolink
} // namespace isc
diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h
index 11ea97be03..973fc8b4dd 100644
--- a/src/lib/asiolink/io_endpoint.h
+++ b/src/lib/asiolink/io_endpoint.h
@@ -18,9 +18,6 @@
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
// See the description of the namespace below.
-#include // for some network system calls
-
-#include // for sockaddr
#include
#include
@@ -28,6 +25,12 @@
#include
#include
+# include
+
+#include // for some network system calls
+
+#include // for sockaddr
+
namespace isc {
namespace asiolink {
@@ -158,6 +161,27 @@ public:
const unsigned short port);
};
+/// \brief Insert the \c IOEndpoint as a string into stream.
+///
+/// This method converts \c endpoint into a string and inserts it into the
+/// output stream \c os.
+///
+/// This method converts the address and port of the endpoint in the textual
+/// format that other BIND 10 modules would use in logging, i.e.,
+/// - For IPv6 address: [<address>]:port (e.g., [2001:db8::5300]:53)
+/// - For IPv4 address: <address>:port (e.g., 192.0.2.53:5300)
+///
+/// If it's neither IPv6 nor IPv4, it converts the endpoint into text in the
+/// same format as that for IPv4, although in practice such a case is not
+/// really expected.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param endpoint A reference to an \c IOEndpoint object output by the
+/// operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const IOEndpoint& endpoint);
} // namespace asiolink
} // namespace isc
#endif // __IO_ENDPOINT_H
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 984cf07bfd..39b098d331 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -18,6 +18,9 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
@@ -33,11 +36,11 @@ run_unittests_SOURCES += udp_socket_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(GTEST_LDADD)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
index 948e7089b3..462a2fbac1 100644
--- a/src/lib/asiolink/tests/io_endpoint_unittest.cc
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.cc
@@ -13,18 +13,22 @@
// PERFORMANCE OF THIS SOFTWARE.
#include
+
+#include
+#include
+
#include
+#include
+
+#include
+#include
+
#include
#include
#include
#include
-#include
-
-#include
-#include
-
using namespace isc::asiolink;
namespace {
@@ -240,4 +244,51 @@ TEST(IOEndpointTest, getSockAddr) {
sockAddrMatch(ep->getSockAddr(), "2001:db8::5300", "35");
}
+// A faked IOEndpoint for an uncommon address family. It wouldn't be possible
+// to create via the normal factory, so we define a special derived class
+// for it.
+class TestIOEndpoint : public IOEndpoint {
+ virtual IOAddress getAddress() const {
+ return IOAddress("2001:db8::bad:add");
+ }
+ virtual uint16_t getPort() const { return (42); }
+ virtual short getProtocol() const { return (IPPROTO_UDP); }
+ virtual short getFamily() const { return (AF_UNSPEC); }
+ virtual const struct sockaddr& getSockAddr() const {
+ static struct sockaddr sa_placeholder;
+ return (sa_placeholder);
+ }
+};
+
+void
+checkEndpointText(const std::string& expected, const IOEndpoint& ep) {
+ std::ostringstream oss;
+ oss << ep;
+ EXPECT_EQ(expected, oss.str());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(IOEndpointTest, LeftShiftOperator) {
+ // UDP/IPv4
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 53210));
+ checkEndpointText("192.0.2.1:53210", *ep);
+
+ // UDP/IPv6
+ ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+ checkEndpointText("[2001:db8::53]:53", *ep);
+
+ // Same for TCP: shouldn't be different
+ ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 53210));
+ checkEndpointText("192.0.2.1:53210", *ep);
+ ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::53"), 53));
+ checkEndpointText("[2001:db8::53]:53", *ep);
+
+ // Uncommon address family. The actual behavior doesn't matter much
+ // in practice, but we check such input doesn't make it crash.
+ // We explicitly instantiate the test EP because otherwise some compilers
+ // would be confused and complain.
+ TestIOEndpoint test_ep;
+ checkEndpointText("2001:db8::bad:add:42", test_ep);
+}
}
diff --git a/src/lib/bench/tests/Makefile.am b/src/lib/bench/tests/Makefile.am
index 3f8a67863b..80695596fd 100644
--- a/src/lib/bench/tests/Makefile.am
+++ b/src/lib/bench/tests/Makefile.am
@@ -5,6 +5,9 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/lib/cache/cache_messages.mes b/src/lib/cache/cache_messages.mes
index 19102aec4a..51f2264cde 100644
--- a/src/lib/cache/cache_messages.mes
+++ b/src/lib/cache/cache_messages.mes
@@ -66,14 +66,14 @@ is created.
Debug message. The resolver cache is looking up the deepest known nameserver,
so the resolution doesn't have to start from the root.
+% CACHE_RESOLVER_INIT initializing resolver cache for class %1
+Debug message. The resolver cache is being created for this given class.
+
% CACHE_RESOLVER_INIT_INFO initializing resolver cache for class %1
Debug message, the resolver cache is being created for this given class. The
difference from CACHE_RESOLVER_INIT is only in different format of passed
information, otherwise it does the same.
-% CACHE_RESOLVER_INIT initializing resolver cache for class %1
-Debug message. The resolver cache is being created for this given class.
-
% CACHE_RESOLVER_LOCAL_MSG message for %1/%2 found in local zone data
Debug message. The resolver cache found a complete message for the user query
in the zone data.
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
index b638f55cbc..fe33d3c3bf 100644
--- a/src/lib/cache/tests/Makefile.am
+++ b/src/lib/cache/tests/Makefile.am
@@ -28,6 +28,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/lib/cache/tests/message_entry_unittest.cc b/src/lib/cache/tests/message_entry_unittest.cc
index d9709ed298..86cc89fefa 100644
--- a/src/lib/cache/tests/message_entry_unittest.cc
+++ b/src/lib/cache/tests/message_entry_unittest.cc
@@ -1,3 +1,5 @@
+// Copyright (C) 2010 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.
diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
index 08b7f33a5a..b891628e2b 100644
--- a/src/lib/cc/tests/Makefile.am
+++ b/src/lib/cc/tests/Makefile.am
@@ -16,6 +16,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
@@ -26,7 +29,7 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
# We need to put our libs first, in case gtest (or any dependency, really)
# is installed in the same location as a different version of bind10
-# Otherwise the linker may not use the source tree libs
+# Otherwise the linker may not use the source tree libs
run_unittests_LDADD = $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
index 500ff1223c..518d4979c1 100644
--- a/src/lib/config/Makefile.am
+++ b/src/lib/config/Makefile.am
@@ -17,6 +17,11 @@ libcfgclient_la_SOURCES += module_spec.h module_spec.cc
libcfgclient_la_SOURCES += ccsession.cc ccsession.h
libcfgclient_la_SOURCES += config_log.h config_log.cc
+libcfgclient_la_LIBADD = $(top_builddir)/src/lib/cc/libcc.la
+libcfgclient_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+libcfgclient_la_LDFLAGS = -no-undefined -version-info 1:0:1
+
nodist_libcfgclient_la_SOURCES = config_messages.h config_messages.cc
# The message file should be in the distribution.
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 63fa4cdcf9..d4c6653b6c 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -601,6 +601,11 @@ ModuleCCSession::checkCommand() {
ConstElementPtr cmd, routing, data;
if (session_.group_recvmsg(routing, data, true)) {
+ // In case the message is wanted asynchronously, it gets used.
+ if (checkAsyncRecv(routing, data)) {
+ return (0);
+ }
+
/* ignore result messages (in case we're out of sync, to prevent
* pingpongs */
if (data->getType() != Element::map || data->contains("result")) {
@@ -764,5 +769,95 @@ ModuleCCSession::sendStopping() {
session_.group_sendmsg(cmd, "ConfigManager");
}
+class ModuleCCSession::AsyncRecvRequest {
+public: // Everything is public here, as the definition is hidden anyway
+ AsyncRecvRequest(const AsyncRecvCallback& cb, const string& rcp, int sq,
+ bool reply) :
+ callback(cb),
+ recipient(rcp),
+ seq(sq),
+ is_reply(reply)
+ {}
+ const AsyncRecvCallback callback;
+ const string recipient;
+ const int seq;
+ const bool is_reply;
+};
+
+ModuleCCSession::AsyncRecvRequestID
+ModuleCCSession::groupRecvMsgAsync(const AsyncRecvCallback& callback,
+ bool is_reply, int seq,
+ const string& recipient) {
+ // This just stores the request, the handling is done in checkCommand()
+
+ // push_back would be simpler, but it does not return the iterator we need
+ return (async_recv_requests_.insert(async_recv_requests_.end(),
+ AsyncRecvRequest(callback, recipient,
+ seq, is_reply)));
+}
+
+bool
+ModuleCCSession::checkAsyncRecv(const ConstElementPtr& envelope,
+ const ConstElementPtr& msg)
+{
+ for (AsyncRecvRequestID request(async_recv_requests_.begin());
+ request != async_recv_requests_.end(); ++request) {
+ // Just go through all the requests and look for a matching one
+ if (requestMatch(*request, envelope)) {
+ // We want the request to be still alive at the time we
+ // call the callback. But we need to remove it on an exception
+ // too, so we use the class. If just C++ had the finally keyword.
+ class RequestDeleter {
+ public:
+ RequestDeleter(AsyncRecvRequests& requests,
+ AsyncRecvRequestID& request) :
+ requests_(requests),
+ request_(request)
+ { }
+ ~RequestDeleter() {
+ requests_.erase(request_);
+ }
+ private:
+ AsyncRecvRequests& requests_;
+ AsyncRecvRequestID& request_;
+ };
+ RequestDeleter deleter(async_recv_requests_, request);
+ // Call the callback
+ request->callback(envelope, msg, request);
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+ModuleCCSession::requestMatch(const AsyncRecvRequest& request,
+ const ConstElementPtr& envelope) const
+{
+ if (request.is_reply != envelope->contains("reply")) {
+ // Wrong type of message
+ return (false);
+ }
+ if (request.is_reply &&
+ (request.seq == -1 ||
+ request.seq == envelope->get("reply")->intValue())) {
+ // This is the correct reply
+ return (true);
+ }
+ if (!request.is_reply &&
+ (request.recipient.empty() ||
+ request.recipient == envelope->get("group")->stringValue())) {
+ // This is the correct command
+ return (true);
+ }
+ // If nothing from the above, we don't want it
+ return (false);
+}
+
+void
+ModuleCCSession::cancelAsyncRecv(const AsyncRecvRequestID& id) {
+ async_recv_requests_.erase(id);
+}
+
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 059968c71a..e96a33d44b 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -15,13 +15,16 @@
#ifndef __CCSESSION_H
#define __CCSESSION_H 1
-#include
-
#include
#include
+
#include
#include
+#include
+#include
+#include
+
namespace isc {
namespace config {
@@ -358,15 +361,140 @@ public:
return (session_.group_recvmsg(envelope, msg, nonblock, seq));
};
+ /// \brief Forward declaration of internal data structure.
+ ///
+ /// This holds information about one asynchronous request to receive
+ /// a message. It is declared as public to allow declaring other derived
+ /// types, but without showing the internal representation.
+ class AsyncRecvRequest;
+
+ /// \brief List of all requests for asynchronous reads.
+ typedef std::list AsyncRecvRequests;
+
+ /// \brief Identifier of single request for asynchronous read.
+ typedef AsyncRecvRequests::iterator AsyncRecvRequestID;
+
+ /// \brief Callback which is called when an asynchronous receive finishes.
+ ///
+ /// This is the callback used by groupRecvMsgAsync() function. It is called
+ /// when a matching message arrives. It receives following parameters when
+ /// called:
+ /// - The envelope of the message
+ /// - The message itself
+ /// - The ID of the request, as returned by corresponding groupRecvMsgAsync
+ /// call.
+ ///
+ /// It is possible to throw exceptions from the callback, but they will not
+ /// be caught and they will get propagated out through the checkCommand()
+ /// call. This, if not handled on higher level, will likely terminate the
+ /// application. However, the ModuleCCSession internals will be in
+ /// well-defined state after the call (both the callback and the message
+ /// will be removed from the queues as already called).
+ typedef boost::function3
+ AsyncRecvCallback;
+
+ /// \brief Receive a message from the CC session asynchronously.
+ ///
+ /// This registers a callback which is called when a matching message
+ /// is received. This message returns immediately.
+ ///
+ /// Once a matching message arrives, the callback is called with the
+ /// envelope of the message, the message itself and the result of this
+ /// function call (which might be useful for identifying which of many
+ /// events the recipient is waiting for this is). This makes the callback
+ /// used and is not called again even if a message that would match
+ /// arrives later (this is a single-shot callback).
+ ///
+ /// The callback is never called from within this function. Even if there
+ /// are queued messages, the callback would be called once checkCommand()
+ /// is invoked (possibly from start() or the constructor).
+ ///
+ /// The matching is as follows. If is_reply is true, only replies are
+ /// considered. In that case, if seq is -1, any reply is accepted. If
+ /// it is something else than -1, only the reply with matching seq is
+ /// taken. This may be used to receive replies to commands
+ /// asynchronously.
+ ///
+ /// In case the is_reply is false, the function looks for command messages.
+ /// The seq parameter is ignored, but the recipient one is considered. If
+ /// it is an empty string, any command is taken. If it is non-empty, only
+ /// commands addressed to the recipient channel (eg. group - instance is
+ /// ignored for now) are taken. This can be used to receive foreign commands
+ /// or notifications. In such case, it might be desirable to call the
+ /// groupRecvMsgAsync again from within the callback, to receive any future
+ /// commands or events of the same type.
+ ///
+ /// The interaction with other receiving functions is slightly complicated.
+ /// The groupRecvMsg call takes precedence. If the message matches its
+ /// parameters, it steals the message and no callback matching it as well
+ /// is called. Then, all the queued asynchronous receives are considered,
+ /// with the oldest active ones taking precedence (they work as FIFO).
+ /// If none of them matches, generic command and config handling takes
+ /// place. If it is not handled by that, the message is dropped. However,
+ /// it is better if there's just one place that wants to receive each given
+ /// message.
+ ///
+ /// \exception std::bad_alloc if there isn't enough memory to store the
+ /// callback.
+ /// \param callback is the function to be called when a matching message
+ /// arrives.
+ /// \param is_reply specifies if the desired message should be a reply or
+ /// a command.
+ /// \param seq specifies the reply sequence number in case a reply is
+ /// desired. The default -1 means any reply is OK.
+ /// \param recipient is the CC channel to which the command should be
+ /// addressed to match (in case is_reply is false). Empty means any
+ /// command is good one.
+ /// \return An identifier of the request. This will be passed to the
+ /// callback or can be used to cancel the request by cancelAsyncRecv.
+ /// \todo Decide what to do with instance and what was it meant for anyway.
+ AsyncRecvRequestID groupRecvMsgAsync(const AsyncRecvCallback& callback,
+ bool is_reply, int seq = -1,
+ const std::string& recipient =
+ std::string());
+
+ /// \brief Removes yet unused request for asynchronous receive.
+ ///
+ /// This function cancels a request previously queued by
+ /// groupRecvMsgAsync(). You may use it only before the callback was
+ /// already triggered. If you call it with an ID of callback that
+ /// already happened or was already canceled, the behaviour is undefined
+ /// (but something like a crash is very likely, as the function removes
+ /// an item from a list and this would be removing it from a list that
+ /// does not contain the item).
+ ///
+ /// It is important to cancel requests that are no longer going to happen
+ /// for some reason, as the request would occupy memory forever.
+ ///
+ /// \param id The id of request as returned by groupRecvMsgAsync.
+ void cancelAsyncRecv(const AsyncRecvRequestID& id);
+
private:
ModuleSpec readModuleSpecification(const std::string& filename);
void startCheck();
void sendStopping();
+ /// \brief Check if the message is wanted by asynchronous read
+ ///
+ /// It checks if any of the previously queued requests match
+ /// the message. If so, the callback is dispatched and removed.
+ ///
+ /// \param envelope The envelope of the message.
+ /// \param msg The actual message data.
+ /// \return True if the message was used for a callback, false
+ /// otherwise.
+ bool checkAsyncRecv(const data::ConstElementPtr& envelope,
+ const data::ConstElementPtr& msg);
+ /// \brief Checks if a message with this envelope matches the request
+ bool requestMatch(const AsyncRecvRequest& request,
+ const data::ConstElementPtr& envelope) const;
bool started_;
std::string module_name_;
isc::cc::AbstractSession& session_;
ModuleSpec module_specification_;
+ AsyncRecvRequests async_recv_requests_;
isc::data::ConstElementPtr handleConfigUpdate(
isc::data::ConstElementPtr new_config);
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index a9310708f0..98a991da69 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -136,7 +136,7 @@ check_statistics_item_list(ConstElementPtr spec) {
&& item->contains("item_default")) {
if(!check_format(item->get("item_default"),
item->get("item_format"))) {
- isc_throw(ModuleSpecError,
+ isc_throw(ModuleSpecError,
"item_default not valid type of item_format");
}
}
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index 2f1fc6fc1b..1d9bad833b 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -14,6 +14,9 @@ CLEANFILES = *.gcno *.gcda
noinst_LTLIBRARIES = libfake_session.la
libfake_session_la_SOURCES = fake_session.h fake_session.cc
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index abaff8e95d..3fca741bbb 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -27,11 +27,13 @@
#include
#include
+#include
using namespace isc::data;
using namespace isc::config;
using namespace isc::cc;
using namespace std;
+using namespace boost;
namespace {
std::string
@@ -497,10 +499,10 @@ TEST_F(CCSessionTest, remoteConfig) {
const size_t qsize(session.getMsgQueue()->size());
EXPECT_TRUE(session.getMsgQueue()->get(qsize - 2)->equals(*el(
"[ \"ConfigManager\", \"*\", { \"command\": ["
- "\"get_module_spec\", { \"module_name\": \"Spec2\" } ] } ]")));
+ "\"get_module_spec\", { \"module_name\": \"Spec2\" } ] }, -1 ]")));
EXPECT_TRUE(session.getMsgQueue()->get(qsize - 1)->equals(*el(
"[ \"ConfigManager\", \"*\", { \"command\": [ \"get_config\","
- "{ \"module_name\": \"Spec2\" } ] } ]")));
+ "{ \"module_name\": \"Spec2\" } ] }, -1 ]")));
EXPECT_EQ("Spec2", module_name);
// Since we returned an empty local config above, the default value
// for "item1", which is 1, should be used.
@@ -709,13 +711,286 @@ TEST_F(CCSessionTest, doubleStartWithAddRemoteConfig) {
FakeSession::DoubleRead);
}
-namespace {
+/// \brief Test fixture for asynchronous receiving of messages.
+///
+/// This is an extension to the CCSessionTest. It would be possible to add
+/// the functionality to the CCSessionTest, but it is going to be used
+/// only by few tests and is non-trivial, so it is placed to a separate
+/// sub-class.
+class AsyncReceiveCCSessionTest : public CCSessionTest {
+protected:
+ AsyncReceiveCCSessionTest() :
+ mccs_(ccspecfile("spec29.spec"), session, NULL, NULL, false, false),
+ msg_(el("{\"result\": [0]}")),
+ next_flag_(0)
+ {
+ // This is just to make sure the messages get through the fake
+ // session.
+ session.subscribe("test group");
+ session.subscribe("other group");
+ session.subscribe("");
+ // Get rid of all unrelated stray messages
+ while (session.getMsgQueue()->size() > 0) {
+ session.getMsgQueue()->remove(0);
+ }
+ }
+ /// \brief Convenience function to queue a request to get a command
+ /// message.
+ ModuleCCSession::AsyncRecvRequestID
+ registerCommand(const string& recipient)
+ {
+ return (mccs_.groupRecvMsgAsync(
+ bind(&AsyncReceiveCCSessionTest::callback, this, next_flag_ ++, _1,
+ _2, _3), false, -1, recipient));
+ }
+ /// \brief Convenience function to queue a request to get a reply
+ /// message.
+ ModuleCCSession::AsyncRecvRequestID
+ registerReply(int seq)
+ {
+ return (mccs_.groupRecvMsgAsync(
+ bind(&AsyncReceiveCCSessionTest::callback, this, next_flag_ ++, _1,
+ _2, _3), true, seq));
+ }
+ /// \brief Check the next called callback was with this flag
+ void called(int flag) {
+ ASSERT_FALSE(called_.empty());
+ EXPECT_EQ(flag, *called_.begin());
+ called_.pop_front();
+ }
+ /// \brief Checks that no more callbacks were called.
+ void nothingCalled() {
+ EXPECT_TRUE(called_.empty());
+ }
+ /// \brief The tested session.
+ ModuleCCSession mccs_;
+ /// \brief The value of message on the last called callback.
+ ConstElementPtr last_msg_;
+ /// \brief A message that can be used
+ ConstElementPtr msg_;
+ // Shared part of the simpleCommand and similar tests.
+ void commandTest(const string& group) {
+ // Push the message inside
+ session.addMessage(msg_, "test group", "");
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ registerCommand(group);
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // But when we call the checkCommand(), it should be called.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+ // And the message should be eaten
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ // The callback should have been eaten as well, inserting another
+ // message will not invoke it again
+ session.addMessage(msg_, "test group", "");
+ mccs_.checkCommand();
+ nothingCalled();
+ }
+ /// \brief Shared part of the simpleResponse and wildcardResponse tests.
+ void responseTest(int seq) {
+ // Push the message inside
+ session.addMessage(msg_, "", "", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ registerReply(seq);
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // But when we call the checkCommand(), it should be called.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+ // And the message should be eaten
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ // The callback should have been eaten as well, inserting another
+ // message will not invoke it again
+ session.addMessage(msg_, "test group", "");
+ mccs_.checkCommand();
+ nothingCalled();
+ }
+ /// \brief Shared part of the noMatch* tests
+ void noMatchTest(int seq, int wanted_seq, bool is_reply) {
+ // Push the message inside
+ session.addMessage(msg_, "other group", "", seq);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ if (is_reply) {
+ registerReply(wanted_seq);
+ } else {
+ registerCommand("test group");
+ }
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // And even not now, because it does not match.
+ mccs_.checkCommand();
+ nothingCalled();
+ // And the message should be eaten by the checkCommand
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ }
+private:
+ /// \brief The next flag to be handed out
+ int next_flag_;
+ /// \brief Flags of callbacks already called (as FIFO)
+ list called_;
+ /// \brief This is the callback registered to the tested groupRecvMsgAsync
+ /// function.
+ void callback(int store_flag, const ConstElementPtr&,
+ const ConstElementPtr& msg,
+ const ModuleCCSession::AsyncRecvRequestID&)
+ {
+ called_.push_back(store_flag);
+ last_msg_ = msg;
+ }
+};
+
+// Test we can receive a command, without anything fancy yet
+TEST_F(AsyncReceiveCCSessionTest, simpleCommand) {
+ commandTest("test group");
+}
+
+// Test we can receive a "wildcard" command - without specifying the
+// group to subscribe to. Very similar to simpleCommand test.
+TEST_F(AsyncReceiveCCSessionTest, wildcardCommand) {
+ commandTest("");
+}
+
+// Very similar to simpleCommand, but with a response message
+TEST_F(AsyncReceiveCCSessionTest, simpleResponse) {
+ responseTest(1);
+}
+
+// Matching a response message with wildcard
+TEST_F(AsyncReceiveCCSessionTest, wildcardResponse) {
+ responseTest(-1);
+}
+
+// Check that a wrong command message is not matched
+TEST_F(AsyncReceiveCCSessionTest, noMatchCommand) {
+ noMatchTest(-1, -1, false);
+}
+
+// Check that a wrong response message is not matched
+TEST_F(AsyncReceiveCCSessionTest, noMatchResponse) {
+ noMatchTest(2, 3, true);
+}
+
+// Check that a command will not match on a reply check and vice versa
+TEST_F(AsyncReceiveCCSessionTest, noMatchResponseAgainstCommand) {
+ // Send a command and check it is not matched as a response
+ noMatchTest(-1, -1, true);
+}
+
+TEST_F(AsyncReceiveCCSessionTest, noMatchCommandAgainstResponse) {
+ noMatchTest(2, -1, false);
+}
+
+// We check for command several times before the message actually arrives.
+TEST_F(AsyncReceiveCCSessionTest, delayedCallback) {
+ // First, register the callback
+ registerReply(1);
+ // And see it is not called, because the message is not there yet
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ for (size_t i(0); i < 100; ++ i) {
+ mccs_.checkCommand();
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ nothingCalled();
+ }
+ // Now the message finally arrives
+ session.addMessage(msg_, "", "", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // And now, the callback is happily triggered.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+}
+
+// See that if we put multiple messages inside, and request some callbacks,
+// the callbacks are called in the order of messages, not in the order they
+// were registered.
+TEST_F(AsyncReceiveCCSessionTest, outOfOrder) {
+ // First, put some messages there
+ session.addMessage(msg_, "", "", 1);
+ session.addMessage(msg_, "test group", "");
+ session.addMessage(msg_, "other group", "");
+ session.addMessage(msg_, "", "", 2);
+ session.addMessage(msg_, "", "", 3);
+ session.addMessage(msg_, "", "", 4);
+ // Now register some callbacks
+ registerReply(13); // Will not be called
+ registerCommand("other group"); // Matches 3rd message
+ registerReply(2); // Matches 4th message
+ registerCommand(""); // Matches the 2nd message
+ registerCommand("test group"); // Will not be called
+ registerReply(-1); // Matches the 1st message
+ registerReply(-1); // Matches the 5th message
+ // Process all messages there
+ while (mccs_.hasQueuedMsgs()) {
+ mccs_.checkCommand();
+ }
+ // These are the numbers of callbacks in the order of messages
+ called(5);
+ called(3);
+ called(1);
+ called(2);
+ called(6);
+ // The last message doesn't trigger anything, so nothing more is called
+ nothingCalled();
+}
+
+// We first add, then remove the callback again and check that nothing is
+// matched.
+TEST_F(AsyncReceiveCCSessionTest, cancel) {
+ // Add the callback
+ ModuleCCSession::AsyncRecvRequestID request(registerReply(1));
+ // Add corresponding message
+ session.addMessage(msg_, "", "", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // And now, remove the callback again
+ mccs_.cancelAsyncRecv(request);
+ // And see that Nothing Happens(TM)
+ mccs_.checkCommand();
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ nothingCalled();
+}
+
+// We add multiple requests and cancel only one of them to see the rest
+// is unaffected.
+TEST_F(AsyncReceiveCCSessionTest, cancelSome) {
+ // Register few callbacks
+ registerReply(1);
+ ModuleCCSession::AsyncRecvRequestID request(registerCommand(""));
+ registerCommand("test group");
+ // Put some messages there
+ session.addMessage(msg_, "test group", "");
+ session.addMessage(msg_, "", "", 1);
+ // Cancel the second callback. Therefore the first message will be matched
+ // by the third callback, not by the second.
+ mccs_.cancelAsyncRecv(request);
+ // Now, process the messages
+ mccs_.checkCommand();
+ mccs_.checkCommand();
+ // And see how they matched
+ called(2);
+ called(0);
+ nothingCalled();
+}
+
void doRelatedLoggersTest(const char* input, const char* expected) {
ConstElementPtr all_conf = isc::data::Element::fromJSON(input);
ConstElementPtr expected_conf = isc::data::Element::fromJSON(expected);
EXPECT_EQ(*expected_conf, *isc::config::getRelatedLoggers(all_conf));
}
-} // end anonymous namespace
TEST(LogConfigTest, relatedLoggersTest) {
// make sure logger configs for 'other' programs are ignored,
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index 177e62942c..157d4d658f 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -139,6 +139,9 @@ FakeSession::recvmsg(ConstElementPtr& env, ConstElementPtr& msg, bool nonblock,
ElementPtr new_env = Element::createMap();
new_env->set("group", c_m->get(0));
new_env->set("to", c_m->get(1));
+ if (c_m->get(3)->intValue() != -1) {
+ new_env->set("reply", c_m->get(3));
+ }
env = new_env;
msg = c_m->get(2);
to_remove = c_m;
@@ -207,7 +210,7 @@ FakeSession::reply(ConstElementPtr envelope, ConstElementPtr newmsg) {
bool
FakeSession::hasQueuedMsgs() const {
- return (false);
+ return (msg_queue_ && msg_queue_->size() > 0);
}
ConstElementPtr
@@ -228,12 +231,13 @@ FakeSession::getFirstMessage(std::string& group, std::string& to) const {
void
FakeSession::addMessage(ConstElementPtr msg, const std::string& group,
- const std::string& to)
+ const std::string& to, int seq)
{
ElementPtr m_el = Element::createList();
m_el->add(Element::create(group));
m_el->add(Element::create(to));
m_el->add(msg);
+ m_el->add(Element::create(seq));
if (!msg_queue_) {
msg_queue_ = Element::createList();
}
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 79ff174110..c91b5199e3 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -74,7 +74,7 @@ public:
isc::data::ConstElementPtr getFirstMessage(std::string& group,
std::string& to) const;
void addMessage(isc::data::ConstElementPtr, const std::string& group,
- const std::string& to);
+ const std::string& to, int seq = -1);
bool haveSubscription(const std::string& group,
const std::string& instance);
bool haveSubscription(const isc::data::ConstElementPtr group,
diff --git a/src/lib/cryptolink/crypto_hmac.cc b/src/lib/cryptolink/crypto_hmac.cc
index 277b0365cd..c1bbfa865b 100644
--- a/src/lib/cryptolink/crypto_hmac.cc
+++ b/src/lib/cryptolink/crypto_hmac.cc
@@ -23,6 +23,8 @@
#include
#include
+#include
+
namespace {
const char*
getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm) {
@@ -155,7 +157,7 @@ public:
if (output_size > len) {
output_size = len;
}
- memcpy(result, b_result.begin(), output_size);
+ std::memcpy(result, b_result.begin(), output_size);
} catch (const Botan::Exception& exc) {
isc_throw(isc::cryptolink::LibraryError, exc.what());
}
diff --git a/src/lib/cryptolink/tests/Makefile.am b/src/lib/cryptolink/tests/Makefile.am
index fbe1bad350..be2f4d6f05 100644
--- a/src/lib/cryptolink/tests/Makefile.am
+++ b/src/lib/cryptolink/tests/Makefile.am
@@ -10,6 +10,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/lib/datasrc/.gitignore b/src/lib/datasrc/.gitignore
index 05c761ec89..206ddca6d9 100644
--- a/src/lib/datasrc/.gitignore
+++ b/src/lib/datasrc/.gitignore
@@ -2,3 +2,4 @@
/datasrc_messages.h
/datasrc_config.h
/datasrc_config.h.pre
+/static.zone
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index 2cdb8ea378..9a4d733f91 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -12,8 +12,13 @@ pkglibdir = $(libexecdir)/@PACKAGE@/backends
datasrc_config.h: datasrc_config.h.pre
$(SED) -e "s|@@PKGLIBDIR@@|$(pkglibdir)|" datasrc_config.h.pre >$@
+static.zone: static.zone.pre
+ $(SED) -e "s|@@VERSION_STRING@@|$(PACKAGE_STRING)|" $(srcdir)/static.zone.pre >$@
+ $(SED) -e 's/\(.*\)/AUTHORS.BIND. 0 CH TXT "\1"/' $(top_srcdir)/AUTHORS >>$@
+
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
CLEANFILES += datasrc_config.h
+CLEANFILES += static.zone
lib_LTLIBRARIES = libdatasrc.la
libdatasrc_la_SOURCES = data_source.h data_source.cc
@@ -30,10 +35,11 @@ libdatasrc_la_SOURCES += logger.h logger.cc
libdatasrc_la_SOURCES += client.h iterator.h
libdatasrc_la_SOURCES += database.h database.cc
libdatasrc_la_SOURCES += factory.h factory.cc
+libdatasrc_la_SOURCES += client_list.h client_list.cc
nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libdatasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
-pkglib_LTLIBRARIES = sqlite3_ds.la memory_ds.la
+pkglib_LTLIBRARIES = sqlite3_ds.la memory_ds.la static_ds.la
sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
@@ -49,6 +55,12 @@ memory_ds_la_LDFLAGS = -module -avoid-version
memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
memory_ds_la_LIBADD += libdatasrc.la
+static_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
+static_ds_la_SOURCES += static_datasrc_link.cc
+static_ds_la_LDFLAGS = -module -avoid-version
+static_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+static_ds_la_LIBADD += libdatasrc.la
+
libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
@@ -59,4 +71,7 @@ BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc
datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/datasrc_messages.mes
-EXTRA_DIST = datasrc_messages.mes
+EXTRA_DIST = datasrc_messages.mes static.zone.pre
+
+zonedir = $(pkgdatadir)
+zone_DATA = static.zone
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 24c88508ce..dab081f38d 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -362,6 +362,22 @@ public:
virtual std::pair
getJournalReader(const isc::dns::Name& zone, uint32_t begin_serial,
uint32_t end_serial) const = 0;
+
+ /// Return the number of zones currently known to this datasource
+ ///
+ /// This is an optional convenience method, currently only implemented
+ /// by the InMemory datasource. By default, it throws NotImplemented
+ ///
+ /// \exception NotImplemented Thrown if this method is not supported
+ /// by the datasource
+ ///
+ /// \note This is a tentative API, and this method may likely to be
+ /// removed in the near future.
+ /// \return The number of zones known to this datasource
+ virtual unsigned int getZoneCount() const {
+ isc_throw(isc::NotImplemented,
+ "Data source doesn't support getZoneCount");
+ }
};
}
}
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
new file mode 100644
index 0000000000..549b2168fe
--- /dev/null
+++ b/src/lib/datasrc/client_list.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "client_list.h"
+#include "client.h"
+#include "factory.h"
+
+#include
+#include
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace datasrc {
+
+void
+ConfigurableClientList::configure(const Element& config, bool) {
+ // TODO: Implement the cache
+ // TODO: Implement recycling from the old configuration.
+ size_t i(0); // Outside of the try to be able to access it in the catch
+ try {
+ vector new_data_sources;
+ for (; i < config.size(); ++i) {
+ // Extract the parameters
+ const ConstElementPtr dconf(config.get(i));
+ const ConstElementPtr typeElem(dconf->get("type"));
+ if (typeElem == ConstElementPtr()) {
+ isc_throw(ConfigurationError, "Missing the type option in "
+ "data source no " << i);
+ }
+ const string type(typeElem->stringValue());
+ ConstElementPtr paramConf(dconf->get("params"));
+ if (paramConf == ConstElementPtr()) {
+ paramConf.reset(new NullElement());
+ }
+ // TODO: Special-case the master files type.
+ // Ask the factory to create the data source for us
+ const DataSourcePair ds(this->getDataSourceClient(type,
+ paramConf));
+ // And put it into the vector
+ new_data_sources.push_back(DataSourceInfo(ds.first, ds.second));
+ }
+ // If everything is OK up until now, we have the new configuration
+ // ready. So just put it there and let the old one die when we exit
+ // the scope.
+ data_sources_.swap(new_data_sources);
+ } catch (const TypeError& te) {
+ isc_throw(ConfigurationError, "Malformed configuration at data source "
+ "no. " << i << ": " << te.what());
+ }
+}
+
+ClientList::FindResult
+ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
+ bool) const
+{
+ // Nothing found yet.
+ //
+ // We have this class as a temporary storage, as the FindResult can't be
+ // assigned.
+ struct MutableResult {
+ MutableResult() :
+ datasrc_client(NULL),
+ matched_labels(0),
+ matched(false)
+ {}
+ DataSourceClient* datasrc_client;
+ ZoneFinderPtr finder;
+ uint8_t matched_labels;
+ bool matched;
+ operator FindResult() const {
+ // Conversion to the right result. If we return this, there was
+ // a partial match at best.
+ return (FindResult(datasrc_client, finder, false));
+ }
+ } candidate;
+
+ BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
+ // TODO: Once we have support for the caches, consider them too here
+ // somehow. This would probably get replaced by a function, that
+ // checks if there's a cache available, if it is, checks the loaded
+ // zones and zones expected to be in the real data source. If it is
+ // the cached one, provide the cached one. If it is in the external
+ // data source, use the datasource and don't provide the finder yet.
+ const DataSourceClient::FindResult result(
+ info.data_src_client_->findZone(name));
+ switch (result.code) {
+ case result::SUCCESS:
+ // If we found an exact match, we have no hope to getting
+ // a better one. Stop right here.
+
+ // TODO: In case we have only the datasource and not the finder
+ // and the need_updater parameter is true, get the zone there.
+ return (FindResult(info.data_src_client_, result.zone_finder,
+ true));
+ case result::PARTIALMATCH:
+ if (!want_exact_match) {
+ // In case we have a partial match, check if it is better
+ // than what we have. If so, replace it.
+ //
+ // We don't need the labels at the first partial match,
+ // we have nothing to compare with. So we don't get it
+ // (as a performance) and hope we will not need it at all.
+ const uint8_t labels(candidate.matched ?
+ result.zone_finder->getOrigin().getLabelCount() : 0);
+ if (candidate.matched && candidate.matched_labels == 0) {
+ // But if the hope turns out to be false, we need to
+ // compute it for the first match anyway.
+ candidate.matched_labels = candidate.finder->
+ getOrigin().getLabelCount();
+ }
+ if (labels > candidate.matched_labels ||
+ !candidate.matched) {
+ // This one is strictly better. Replace it.
+ candidate.datasrc_client = info.data_src_client_;
+ candidate.finder = result.zone_finder;
+ candidate.matched_labels = labels;
+ candidate.matched = true;
+ }
+ }
+ break;
+ default:
+ // Nothing found, nothing to do.
+ break;
+ }
+ }
+
+ // TODO: In case we have only the datasource and not the finder
+ // and the need_updater parameter is true, get the zone there.
+
+ // Return the partial match we have. In case we didn't want a partial
+ // match, this surely contains the original empty result.
+ return (candidate);
+}
+
+// NOTE: This function is not tested, it would be complicated. However, the
+// purpose of the function is to provide a very thin wrapper to be able to
+// replace the call to DataSourceClientContainer constructor in tests.
+ConfigurableClientList::DataSourcePair
+ConfigurableClientList::getDataSourceClient(const string& type,
+ const ConstElementPtr&
+ configuration)
+{
+ DataSourceClientContainerPtr
+ container(new DataSourceClientContainer(type, configuration));
+ return (DataSourcePair(&container->getInstance(), container));
+}
+
+}
+}
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
new file mode 100644
index 0000000000..599dca8217
--- /dev/null
+++ b/src/lib/datasrc/client_list.h
@@ -0,0 +1,289 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_CONTAINER_H
+#define DATASRC_CONTAINER_H
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace isc {
+namespace datasrc {
+
+class ZoneFinder;
+typedef boost::shared_ptr ZoneFinderPtr;
+class DataSourceClient;
+typedef boost::shared_ptr DataSourceClientPtr;
+class DataSourceClientContainer;
+typedef boost::shared_ptr
+ DataSourceClientContainerPtr;
+
+/// \brief The list of data source clients.
+///
+/// The purpose of this class is to hold several data source clients and search
+/// through them to find one containing a zone best matching a request.
+///
+/// All the data source clients should be for the same class. If you need
+/// to handle multiple classes, you need to create multiple separate lists.
+///
+/// This is an abstract base class. It is not expected we would use multiple
+/// implementation inside the servers (but it is not forbidden either), we
+/// have it to allow easy testing. It is possible to create a mock-up class
+/// instead of creating a full-blown configuration. The real implementation
+/// is the ConfigurableClientList.
+class ClientList : public boost::noncopyable {
+protected:
+ /// \brief Constructor.
+ ///
+ /// It is protected to prevent accidental creation of the abstract base
+ /// class.
+ ClientList() {}
+public:
+ /// \brief Virtual destructor
+ virtual ~ClientList() {}
+ /// \brief Structure holding the (compound) result of find.
+ ///
+ /// As this is read-only structure, we don't bother to create accessors.
+ /// Instead, all the member variables are defined as const and can be
+ /// accessed directly.
+ struct FindResult {
+ /// \brief Constructor.
+ ///
+ /// It simply fills in the member variables according to the
+ /// parameters. See the member descriptions for their meaning.
+ FindResult(DataSourceClient* dsrc_client, const ZoneFinderPtr& finder,
+ bool exact_match) :
+ dsrc_client_(dsrc_client),
+ finder_(finder),
+ exact_match_(exact_match)
+ {}
+
+ /// \brief Negative answer constructor.
+ ///
+ /// This conscructs a result for negative answer. Both pointers are
+ /// NULL, and exact_match_ is false.
+ FindResult() :
+ dsrc_client_(NULL),
+ exact_match_(false)
+ {}
+
+ /// \brief Comparison operator.
+ ///
+ /// It is needed for tests and it might be of some use elsewhere
+ /// too.
+ bool operator ==(const FindResult& other) const {
+ return (dsrc_client_ == other.dsrc_client_ &&
+ finder_ == other.finder_ &&
+ exact_match_ == other.exact_match_);
+ }
+
+ /// \brief The found data source client.
+ ///
+ /// The client of the data source containing the best matching zone.
+ /// If no such data source exists, this is NULL pointer.
+ ///
+ /// Note that the pointer is valid only as long the ClientList which
+ /// returned the pointer is alive and was not reconfigured. The
+ /// ownership is preserved within the ClientList.
+ DataSourceClient* const dsrc_client_;
+
+ /// \brief The finder for the requested zone.
+ ///
+ /// This is the finder corresponding to the best matching zone.
+ /// This may be NULL even in case the datasrc_ is something
+ /// else, depending on the find options.
+ ///
+ /// \see find
+ const ZoneFinderPtr finder_;
+
+ /// \brief If the result is an exact match.
+ const bool exact_match_;
+ };
+
+ /// \brief Search for a zone through the data sources.
+ ///
+ /// This searches the contained data source clients for a one that best
+ /// matches the zone name.
+ ///
+ /// There are two expected usage scenarios. One is answering queries. In
+ /// this case, the zone finder is needed and the best matching superzone
+ /// of the searched name is needed. Therefore, the call would look like:
+ ///
+ /// \code FindResult result(list->find(queried_name));
+ /// FindResult result(list->find(queried_name));
+ /// if (result.datasrc_) {
+ /// createTheAnswer(result.finder_);
+ /// } else {
+ /// createNotAuthAnswer();
+ /// } \endcode
+ ///
+ /// The other scenario is manipulating zone data (XfrOut, XfrIn, DDNS,
+ /// ...). In this case, the finder itself is not so important. However,
+ /// we need an exact match (if we want to manipulate zone data, we must
+ /// know exactly, which zone we are about to manipulate). Then the call
+ ///
+ /// \code FindResult result(list->find(zone_name, true, false));
+ /// FindResult result(list->find(zone_name, true, false));
+ /// if (result.datasrc_) {
+ /// ZoneUpdaterPtr updater(result.datasrc_->getUpdater(zone_name);
+ /// ...
+ /// } \endcode
+ ///
+ /// \param zone The name of the zone to look for.
+ /// \param want_exact_match If it is true, it returns only exact matches.
+ /// If the best possible match is partial, a negative result is
+ /// returned instead. It is possible the caller could check it and
+ /// act accordingly if the result would be partial match, but with this
+ /// set to true, the find might be actually faster under some
+ /// circumstances.
+ /// \param want_finder If this is false, the finder_ member of FindResult
+ /// might be NULL even if the corresponding data source is found. This
+ /// is because of performance, in some cases the finder is a side
+ /// result of the searching algorithm (therefore asking for it again
+ /// would be a waste), but under other circumstances it is not, so
+ /// providing it when it is not needed would also be wasteful.
+ ///
+ /// Other things are never the side effect of searching, therefore the
+ /// caller can get them explicitly (the updater, journal reader and
+ /// iterator).
+ /// \return A FindResult describing the data source and zone with the
+ /// longest match against the zone parameter.
+ virtual FindResult find(const dns::Name& zone,
+ bool want_exact_match = false,
+ bool want_finder = true) const = 0;
+};
+
+/// \brief Shared pointer to the list.
+typedef boost::shared_ptr ClientListPtr;
+/// \brief Shared const pointer to the list.
+typedef boost::shared_ptr ConstClientListPtr;
+
+/// \Concrete implementation of the ClientList, which is constructed based on
+/// configuration.
+///
+/// This is the implementation which is expected to be used in the servers.
+/// However, it is expected most of the code will use it as the ClientList,
+/// only the creation is expected to be direct.
+///
+/// While it is possible to inherit this class, it is not expected to be
+/// inherited except for tests.
+class ConfigurableClientList : public ClientList {
+public:
+ /// \brief Exception thrown when there's an error in configuration.
+ class ConfigurationError : public Exception {
+ public:
+ ConfigurationError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+ };
+
+ /// \brief Sets the configuration.
+ ///
+ /// This fills the ClientList with data source clients corresponding to the
+ /// configuration. The data source clients are newly created or recycled
+ /// from previous configuration.
+ ///
+ /// If any error is detected, an exception is thrown and the current
+ /// configuration is preserved.
+ ///
+ /// \param configuration The JSON element describing the configuration to
+ /// use.
+ /// \param allow_cache If it is true, the 'cache' option of the
+ /// configuration is used and some zones are cached into an In-Memory
+ /// data source according to it. If it is false, it is ignored and
+ /// no In-Memory data sources are created.
+ /// \throw DataSourceError if there's a problem creating a data source
+ /// client.
+ /// \throw ConfigurationError if the configuration is invalid in some
+ /// sense.
+ void configure(const data::Element& configuration, bool allow_cache);
+
+ /// \brief Implementation of the ClientList::find.
+ virtual FindResult find(const dns::Name& zone,
+ bool want_exact_match = false,
+ bool want_finder = true) const;
+
+ /// \brief This holds one data source client and corresponding information.
+ ///
+ /// \todo The content yet to be defined.
+ struct DataSourceInfo {
+ /// \brief Default constructor.
+ ///
+ /// Don't use directly. It is here so the structure can live in
+ /// a vector.
+ DataSourceInfo() :
+ data_src_client_(NULL)
+ {}
+ DataSourceInfo(DataSourceClient* data_src_client,
+ const DataSourceClientContainerPtr& container) :
+ data_src_client_(data_src_client),
+ container_(container)
+ {}
+ DataSourceClient* data_src_client_;
+ DataSourceClientContainerPtr container_;
+ };
+
+ /// \brief The collection of data sources.
+ typedef std::vector DataSources;
+protected:
+ /// \brief The data sources held here.
+ ///
+ /// All our data sources are stored here. It is protected to let the
+ /// tests in. You should consider it private if you ever want to
+ /// derive this class (which is not really recommended anyway).
+ DataSources data_sources_;
+
+ /// \brief Convenience type alias.
+ ///
+ /// \see getDataSource
+ typedef std::pair
+ DataSourcePair;
+
+ /// \brief Create a data source client of given type and configuration.
+ ///
+ /// This is a thin wrapper around the DataSourceClientContainer
+ /// constructor. The function is here to make it possible for tests
+ /// to replace the DataSourceClientContainer with something else.
+ /// Also, derived classes could want to create the data source clients
+ /// in a different way, though inheriting this class is not recommended.
+ ///
+ /// The parameters are the same as of the constructor.
+ /// \return Pair containing both the data source client and the container.
+ /// The container might be NULL in the derived class, it is
+ /// only stored so the data source client is properly destroyed when
+ /// not needed. However, in such case, it is the caller's
+ /// responsibility to ensure the data source client is deleted when
+ /// needed.
+ virtual DataSourcePair getDataSourceClient(const std::string& type,
+ const data::ConstElementPtr&
+ configuration);
+public:
+ /// \brief Access to the data source clients.
+ ///
+ /// It can be used to examine the loaded list of data sources clients
+ /// directly. It is not known if it is of any use other than testing, but
+ /// it might be, so it is just made public (there's no real reason to
+ /// hide it).
+ const DataSources& getDataSources() const { return (data_sources_); }
+};
+
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_CONTAINER_H
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 7b271f1327..61dab39db9 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -450,7 +450,8 @@ DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
const size_t remove_labels = name.getLabelCount() - origin_label_count;
// Go through all superdomains from the origin down searching for nodes
- // that indicate a delegation (.e. NS or DNAME).
+ // that indicate a delegation (.e. NS or DNAME). Note that we only check
+ // pure superdomains; delegation on an exact match will be detected later.
for (int i = remove_labels; i > 0; --i) {
const Name superdomain(name.split(i));
@@ -810,12 +811,14 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
const FoundIterator cni(found.second.find(RRType::CNAME()));
const FoundIterator wti(found.second.find(type));
- if (!is_origin && (options & FIND_GLUE_OK) == 0 &&
+ if (!is_origin && (options & FIND_GLUE_OK) == 0 && type != RRType::DS() &&
nsi != found.second.end()) {
// A NS RRset was found at the domain we were searching for. As it is
// not at the origin of the zone, it is a delegation and indicates that
// this zone is not authoritative for the data. Just return the
- // delegation information.
+ // delegation information, except:
+ // - when we are looking for glue records (FIND_GLUE_OK), or
+ // - when the query type is DS (which cancels the delegation)
return (logAndCreateResult(name, wildname, type, DELEGATION,
nsi->second,
wild ? DATASRC_DATABASE_WILDCARD_NS :
@@ -839,8 +842,6 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
flags));
} else if (wti != found.second.end()) {
bool any(type == RRType::ANY());
- isc::log::MessageID lid(wild ? DATASRC_DATABASE_WILDCARD_MATCH :
- DATASRC_DATABASE_FOUND_RRSET);
if (any) {
// An ANY query, copy everything to the target instead of returning
// directly.
@@ -851,15 +852,32 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
target->push_back(it->second);
}
}
- lid = wild ? DATASRC_DATABASE_WILDCARD_ANY :
- DATASRC_DATABASE_FOUND_ANY;
+ if (wild) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_ANY).
+ arg(accessor_->getDBName()).arg(name);
+ } else {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_ANY).
+ arg(accessor_->getDBName()).arg(name);
+ }
+ } else {
+ if (wild) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_MATCH).
+ arg(accessor_->getDBName()).arg(*wildname).
+ arg(wti->second);
+ } else {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_RRSET).
+ arg(accessor_->getDBName()).arg(wti->second);
+ }
}
// Found an RR matching the query, so return it. (Note that this
// includes the case where we were explicitly querying for a CNAME and
// found it. It also includes the case where we were querying for an
// NS RRset and found it at the apex of the zone.)
- return (logAndCreateResult(name, wildname, type, SUCCESS,
- wti->second, lid, flags));
+ return (ResultContext(SUCCESS, wti->second, flags));
}
// If we get here, we have found something at the requested name but not
@@ -1108,9 +1126,9 @@ DatabaseClient::Finder::findPreviousName(const Name& name) const {
name.reverse().toText()));
try {
return (Name(str));
- }
- catch (const isc::dns::NameParserException&) {
- isc_throw(DataSourceError, "Bad name " + str + " from findPreviousName");
+ } catch (const isc::dns::NameParserException&) {
+ isc_throw(DataSourceError, "Bad name " + str +
+ " from findPreviousName");
}
}
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 8083322edf..8ad1c5b86f 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -743,8 +743,9 @@ public:
/// \brief It returns the previous name in DNSSEC order.
///
- /// This is used in DatabaseClient::findPreviousName and does more
- /// or less the real work, except for working on strings.
+ /// Gets the previous name in the DNSSEC order. This can be used
+ /// to find the correct NSEC records for proving nonexistence
+ /// of domains.
///
/// \param rname The name to ask for previous of, in reversed form.
/// We use the reversed form (see isc::dns::Name::reverse),
@@ -904,10 +905,6 @@ public:
std::vector& target,
const FindOptions options = FIND_DEFAULT);
- /// \brief Implementation of ZoneFinder::findPreviousName method.
- virtual isc::dns::Name findPreviousName(const isc::dns::Name& query)
- const;
-
/// Look for NSEC3 for proving (non)existence of given name.
///
/// See documentation in \c Zone.
@@ -1108,6 +1105,10 @@ public:
bool probed_;
};
+ /// \brief A simple wrapper for identifying the previous name
+ /// of the given name in the underlying database.
+ isc::dns::Name findPreviousName(const isc::dns::Name& name) const;
+
/// \brief Search result of \c findDelegationPoint().
///
/// This is a tuple combining the result of the search - a status code
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index a9870d6fa0..7e3d5c2bdd 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -79,9 +79,12 @@ in the answer as a result.
Debug information. A search in an database data source for NSEC3 that
matches or covers the given name is being started.
-% DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+% DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1 at label count %2: %3
Debug information. An NSEC3 that covers the given name is found and
-being returned. The found NSEC3 RRset is also displayed.
+being returned. The found NSEC3 RRset is also displayed. 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.
% DATASRC_DATABASE_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
Debug information. An NSEC3 that matches (a possibly superdomain of)
@@ -157,7 +160,7 @@ A search in the database for RRs for the specified name, type and class has
located RRs that match the name and class but not the type. DNSSEC information
has been requested and returned.
-% DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %5
+% DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %2
The data returned by the database backend contained data for the given domain
name, and it either matches the type or has a relevant type. The RRset that is
returned is printed.
@@ -276,7 +279,7 @@ nonterminal (e.g. there's nothing at *.example.com but something like
subdomain.*.example.org, do exist: so *.example.org exists in the
namespace but has no RRs assopciated with it). This will produce NXRRSET.
-% DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %5 with RRset %6
+% DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
The database doesn't contain directly matching name. When searching
for a wildcard match, a wildcard record matching the name and type of
the query was found. The data at this point is returned.
diff --git a/src/lib/datasrc/factory.cc b/src/lib/datasrc/factory.cc
index 35a79fe61c..82b4df97eb 100644
--- a/src/lib/datasrc/factory.cc
+++ b/src/lib/datasrc/factory.cc
@@ -23,6 +23,8 @@
#include
+#include
+
#include
#include
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index f3ca397598..2731f58775 100644
--- a/src/lib/datasrc/factory.h
+++ b/src/lib/datasrc/factory.h
@@ -15,14 +15,14 @@
#ifndef __DATA_SOURCE_FACTORY_H
#define __DATA_SOURCE_FACTORY_H 1
-#include
-
#include
#include
-#include
#include
+#include
+#include
+
namespace isc {
namespace datasrc {
@@ -134,6 +134,13 @@ private:
///
/// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
/// \endcode
+///
+/// \note This class is relatively recent, and its design is not yet fully
+/// formed. We may want to split this into an abstract base container
+/// class, and a derived 'dyload' class, and perhaps then add non-dynamic
+/// derived classes as well. Currently, the class is actually derived in some
+/// of the tests, which is rather unclean (as this class as written is really
+/// intended to be used directly).
class DataSourceClientContainer : boost::noncopyable {
public:
/// \brief Constructor
@@ -157,13 +164,13 @@ public:
isc::data::ConstElementPtr config);
/// \brief Destructor
- ~DataSourceClientContainer();
+ virtual ~DataSourceClientContainer();
/// \brief Accessor to the instance
///
/// \return Reference to the DataSourceClient instance contained in this
/// container.
- DataSourceClient& getInstance() { return (*instance_); }
+ virtual DataSourceClient& getInstance() { return (*instance_); }
private:
DataSourceClient* instance_;
@@ -171,6 +178,12 @@ private:
LibraryContainer ds_lib_;
};
+///
+/// Shared pointer type for datasource client containers
+///
+typedef boost::shared_ptr
+ DataSourceClientContainerPtr;
+
} // end namespace datasrc
} // end namespace isc
#endif // DATA_SOURCE_FACTORY_H
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index ea35cfab65..c5122bf832 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -212,11 +212,66 @@ public:
// Identify the RBTree node that best matches the given name.
// See implementation notes below.
+ //
+ // The caller should pass an empty node_path, and it will contain the
+ // search context (all ancestor nodes that the underlying RBTree search
+ // traverses, and how the search stops) for possible later use at the
+ // caller side.
template
ResultType findNode(const Name& name,
+ RBTreeNodeChain& node_path,
ZoneFinder::FindOptions options) const;
+
+ // A helper method for NSEC-signed zones. It searches the zone for
+ // the "closest" NSEC corresponding to the search context stored in
+ // node_path (it should contain sufficient information to identify the
+ // previous name of the query name in the zone). In some cases the
+ // immediate closest name may not have NSEC (when it's under a zone cut
+ // for glue records, or even when the zone is partly broken), so this
+ // method continues the search until it finds a name that has NSEC,
+ // and returns the one found first. Due to the prerequisite (see below),
+ // it should always succeed.
+ //
+ // node_path must store valid search context (in practice, it's expected
+ // to be set by findNode()); otherwise the underlying RBTree implementation
+ // throws.
+ //
+ // If the zone is not considered NSEC-signed or DNSSEC records were not
+ // required in the original search context (specified in options), this
+ // method doesn't bother to find NSEC, and simply returns NULL. So, by
+ // definition of "NSEC-signed", when it really tries to find an NSEC it
+ // should succeed; there should be one at least at the zone origin.
+ ConstRBNodeRRsetPtr
+ getClosestNSEC(RBTreeNodeChain& node_path,
+ ZoneFinder::FindOptions options) const;
};
+ConstRBNodeRRsetPtr
+ZoneData::getClosestNSEC(RBTreeNodeChain& node_path,
+ ZoneFinder::FindOptions options) const
+{
+ if (!nsec_signed_ || (options & ZoneFinder::FIND_DNSSEC) == 0) {
+ return (ConstRBNodeRRsetPtr());
+ }
+
+ const DomainNode* prev_node;
+ while ((prev_node = domains_.previousNode(node_path)) != NULL) {
+ if (!prev_node->isEmpty()) {
+ const Domain::const_iterator found =
+ prev_node->getData()->find(RRType::NSEC());
+ if (found != prev_node->getData()->end()) {
+ return (found->second);
+ }
+ }
+ }
+ // This must be impossible and should be an internal bug.
+ // See the description at the method declaration.
+ assert(false);
+ // Even though there is an assert here, strict compilers
+ // will still need some return value.
+ return (ConstRBNodeRRsetPtr());
+}
+
/// Maintain intermediate data specific to the search context used in
/// \c find().
///
@@ -359,9 +414,10 @@ bool cutCallback(const DomainNode& node, FindState* state) {
// the zone.
template
ResultType
-ZoneData::findNode(const Name& name, ZoneFinder::FindOptions options) const {
+ZoneData::findNode(const Name& name, RBTreeNodeChain& node_path,
+ ZoneFinder::FindOptions options) const
+{
DomainNode* node = NULL;
- RBTreeNodeChain node_path;
FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
const DomainTree::Result result =
@@ -387,9 +443,10 @@ ZoneData::findNode(const Name& name, ZoneFinder::FindOptions options) const {
NameComparisonResult::SUPERDOMAIN) { // empty node, so NXRRSET
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).arg(name);
return (ResultType(ZoneFinder::NXRRSET, node,
- ConstRBNodeRRsetPtr()));
+ getClosestNSEC(node_path, options)));
}
- if (node->getFlag(domain_flag::WILD)) { // maybe a wildcard
+ if (node->getFlag(domain_flag::WILD) && // maybe a wildcard, check only
+ (options & ZoneFinder::NO_WILDCARD) == 0) { // if not disabled.
if (node_path.getLastComparisonResult().getRelation() ==
NameComparisonResult::COMMONANCESTOR &&
node_path.getLastComparisonResult().getCommonLabels() > 1) {
@@ -403,12 +460,17 @@ ZoneData::findNode(const Name& name, ZoneFinder::FindOptions options) const {
LOG_DEBUG(logger, DBG_TRACE_DATA,
DATASRC_MEM_WILDCARD_CANCEL).arg(name);
return (ResultType(ZoneFinder::NXDOMAIN, NULL,
- ConstRBNodeRRsetPtr()));
+ getClosestNSEC(node_path, options)));
}
// Now the wildcard should be the best match.
const Name wildcard(Name("*").concatenate(
node_path.getAbsoluteName()));
- DomainTree::Result result = domains_.find(wildcard, &node);
+
+ // Clear the node_path so that we don't keep incorrect (NSEC)
+ // context
+ node_path.clear();
+ DomainTree::Result result(domains_.find(wildcard, &node,
+ node_path));
// Otherwise, why would the domain_flag::WILD be there if
// there was no wildcard under it?
assert(result == DomainTree::EXACTMATCH);
@@ -418,7 +480,8 @@ ZoneData::findNode(const Name& name, ZoneFinder::FindOptions options) const {
}
// Nothing really matched.
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).arg(name);
- return (ResultType(ZoneFinder::NXDOMAIN, node, state.rrset_));
+ return (ResultType(ZoneFinder::NXDOMAIN, node,
+ getClosestNSEC(node_path, options)));
} else {
// If the name is neither an exact or partial match, it is
// out of bailiwick, which is considered an error.
@@ -1199,6 +1262,24 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
}
}
+ // A helper function for the NXRRSET case in find(). If the zone is
+ // NSEC-signed and DNSSEC records are requested, try to find NSEC
+ // on the given node, and return it if found; return NULL for all other
+ // cases.
+ ConstRBNodeRRsetPtr getNSECForNXRRSET(FindOptions options,
+ const DomainNode& node) const
+ {
+ if (zone_data_->nsec_signed_ &&
+ (options & ZoneFinder::FIND_DNSSEC) != 0) {
+ const Domain::const_iterator found =
+ node.getData()->find(RRType::NSEC());
+ if (found != node.getData()->end()) {
+ return (found->second);
+ }
+ }
+ return (ConstRBNodeRRsetPtr());
+ }
+
// Set up FindContext object as a return value of find(), taking into
// account wildcard matches and DNSSEC information. We set the NSEC/NSEC3
// flag when applicable regardless of the find option; the caller would
@@ -1236,8 +1317,10 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
// Get the node. All other cases than an exact match are handled
// in findNode(). We simply construct a result structure and return.
+ RBTreeNodeChain node_path; // findNode will fill in this
const ZoneData::FindNodeResult node_result =
- zone_data_->findNode(name, options);
+ zone_data_->findNode(name, node_path,
+ options);
if (node_result.code != SUCCESS) {
return (createFindResult(node_result.code, node_result.rrset));
}
@@ -1253,7 +1336,10 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
if (node->isEmpty()) {
LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
arg(name);
- return (createFindResult(NXRRSET, ConstRBNodeRRsetPtr(), rename));
+ return (createFindResult(NXRRSET,
+ zone_data_->getClosestNSEC(node_path,
+ options),
+ rename));
}
Domain::const_iterator found;
@@ -1309,10 +1395,9 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
rename));
}
}
- // No exact match or CNAME. Return NXRRSET.
- LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NXRRSET).arg(type).
- arg(name);
- return (createFindResult(NXRRSET, ConstRBNodeRRsetPtr(), rename));
+ // No exact match or CNAME. Get NSEC if necessary and return NXRRSET.
+ return (createFindResult(NXRRSET, getNSECForNXRRSET(options, *node),
+ rename));
}
};
@@ -1474,6 +1559,7 @@ addAdditional(RBNodeRRset* rrset, ZoneData* zone_data,
{
RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
bool match_wild = false; // will be true if wildcard match is found
+ RBTreeNodeChain node_path; // placeholder for findNode()
for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
// For each domain name that requires additional section processing
// in each RDATA, search the tree for the name and remember it if
@@ -1486,13 +1572,14 @@ addAdditional(RBNodeRRset* rrset, ZoneData* zone_data,
// if the name is not in or below this zone, skip it
const NameComparisonResult::NameRelation reln =
name.compare(zone_data->origin_data_->getName()).getRelation();
- if (reln != NameComparisonResult::SUBDOMAIN &&
- reln != NameComparisonResult::EQUAL) {
+ if (reln != NameComparisonResult::SUBDOMAIN &&
+ reln != NameComparisonResult::EQUAL) {
continue;
}
+ node_path.clear();
const ZoneData::FindMutableNodeResult result =
zone_data->findNode(
- name, ZoneFinder::FIND_GLUE_OK);
+ name, node_path, ZoneFinder::FIND_GLUE_OK);
if (result.code != ZoneFinder::SUCCESS) {
// We are not interested in anything but a successful match.
continue;
@@ -1660,7 +1747,7 @@ generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) {
}
void
-InMemoryZoneFinder::load(const string& filename) {
+InMemoryZoneFinder::load(const std::string& filename) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
arg(filename);
@@ -1687,12 +1774,6 @@ InMemoryZoneFinder::getFileName() const {
return (impl_->file_name_);
}
-isc::dns::Name
-InMemoryZoneFinder::findPreviousName(const isc::dns::Name&) const {
- isc_throw(NotImplemented, "InMemory data source doesn't support DNSSEC "
- "yet, can't find previous name");
-}
-
/// Implementation details for \c InMemoryClient hidden from the public
/// interface.
///
@@ -1762,8 +1843,7 @@ public:
{
// Find the first node (origin) and preserve the node chain for future
// searches
- DomainTree::Result result(tree_.find(origin, &node_, chain_,
- NULL, NULL));
+ DomainTree::Result result(tree_.find(origin, &node_, chain_));
// It can't happen that the origin is not in there
if (result != DomainTree::EXACTMATCH) {
isc_throw(Unexpected,
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index c687d1bcd6..be545d42df 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -65,11 +65,7 @@ public:
/// \brief Returns the class of the zone.
virtual isc::dns::RRClass getClass() const;
- /// \brief Looks up an RRset in the zone.
- ///
- /// See documentation in \c Zone.
- ///
- /// It returns NULL pointer in case of NXDOMAIN and NXRRSET.
+ /// \brief Find an RRset in the datasource
virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
const isc::dns::RRType& type,
const FindOptions options =
@@ -91,12 +87,6 @@ public:
virtual FindNSEC3Result
findNSEC3(const isc::dns::Name& name, bool recursive);
- /// \brief Imelementation of the ZoneFinder::findPreviousName method
- ///
- /// This one throws NotImplemented exception, as InMemory doesn't
- /// support DNSSEC currently.
- virtual isc::dns::Name findPreviousName(const isc::dns::Name& query) const;
-
/// \brief Inserts an rrset into the zone.
///
/// It puts another RRset into the zone.
@@ -286,7 +276,7 @@ public:
/// This method never throws an exception.
///
/// \return The number of zones stored in the client.
- unsigned int getZoneCount() const;
+ virtual unsigned int getZoneCount() const;
/// Add a zone (in the form of \c ZoneFinder) to the \c InMemoryClient.
///
diff --git a/src/lib/datasrc/memory_datasrc_link.cc b/src/lib/datasrc/memory_datasrc_link.cc
index a0b4bf66c3..cbbc6db4c1 100644
--- a/src/lib/datasrc/memory_datasrc_link.cc
+++ b/src/lib/datasrc/memory_datasrc_link.cc
@@ -17,9 +17,13 @@
#include
#include
+#include
#include
+#include
+
#include
+#include
#include
@@ -29,6 +33,14 @@ using namespace isc::data;
namespace isc {
namespace datasrc {
+/// This exception is raised if there is an error in the configuration
+/// that has been passed; missing information, duplicate values, etc.
+class InMemoryConfigError : public isc::Exception {
+public:
+ InMemoryConfigError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
namespace {
// convencience function to add an error message to a list of those
// (TODO: move functions like these to some util lib?)
@@ -49,14 +61,14 @@ checkConfigElementString(ConstElementPtr config, const std::string& name,
"Config for memory backend does not contain a '"
+name+
"' value");
- return false;
+ return (false);
} else if (!config->get(name) ||
config->get(name)->getType() != Element::string) {
addError(errors, "value of " + name +
" in memory backend config is not a string");
- return false;
+ return (false);
} else {
- return true;
+ return (true);
}
}
@@ -112,24 +124,26 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
result = false;
}
}
- if (!checkConfigElementString(config, "class", errors)) {
- result = false;
- } else {
- try {
- RRClass rrc(config->get("class")->stringValue());
- } catch (const isc::Exception& rrce) {
- addError(errors,
- "Error parsing class config for memory backend: " +
- std::string(rrce.what()));
+ if (config->contains("class")) {
+ if (!checkConfigElementString(config, "class", errors)) {
result = false;
+ } else {
+ try {
+ RRClass rrc(config->get("class")->stringValue());
+ } catch (const isc::Exception& rrce) {
+ addError(errors,
+ "Error parsing class config for memory backend: " +
+ std::string(rrce.what()));
+ result = false;
+ }
}
}
if (!config->contains("zones")) {
- addError(errors, "No 'zones' element in memory backend config");
- result = false;
+ // Assume empty list of zones
} else if (!config->get("zones") ||
config->get("zones")->getType() != Element::list) {
- addError(errors, "'zones' element in memory backend config is not a list");
+ addError(errors,
+ "'zones' element in memory backend config is not a list");
result = false;
} else {
BOOST_FOREACH(ConstElementPtr zone_config,
@@ -144,6 +158,91 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
return (result);
}
+// Apply the given config to the just-initialized client
+// client must be freshly allocated, and config_value should have been
+// checked by the caller
+void
+applyConfig(isc::datasrc::InMemoryClient& client,
+ isc::data::ConstElementPtr config_value)
+{
+ // XXX: We have lost the context to get to the default values here,
+ // as a temporary workaround we hardcode the IN class here.
+ isc::dns::RRClass rrclass = RRClass::IN();
+ if (config_value->contains("class")) {
+ rrclass = RRClass(config_value->get("class")->stringValue());
+ }
+ 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 std::string origin_txt = origin ? origin->stringValue() : "";
+ if (origin_txt.empty()) {
+ isc_throw(InMemoryConfigError, "Missing zone origin");
+ }
+ ConstElementPtr file = zone_config->get("file");
+ const std::string file_txt = file ? file->stringValue() : "";
+ if (file_txt.empty()) {
+ isc_throw(InMemoryConfigError, "Missing zone file for zone: "
+ << origin_txt);
+ }
+
+ // We support the traditional text type and SQLite3 backend. For the
+ // latter we create a client for the underlying SQLite3 data source,
+ // and build the in-memory zone using an iterator of the underlying
+ // zone.
+ ConstElementPtr filetype = zone_config->get("filetype");
+ const std::string filetype_txt = filetype ? filetype->stringValue() :
+ "text";
+ boost::scoped_ptr container;
+ if (filetype_txt == "sqlite3") {
+ container.reset(new DataSourceClientContainer(
+ "sqlite3",
+ Element::fromJSON("{\"database_file\": \"" +
+ file_txt + "\"}")));
+ } else if (filetype_txt != "text") {
+ isc_throw(InMemoryConfigError, "Invalid filetype for zone "
+ << origin_txt << ": " << filetype_txt);
+ }
+
+ // 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(InMemoryConfigError, "unable to parse zone's origin: " <<
+ ex.what());
+ }
+
+ boost::shared_ptr zone_finder(imzf);
+ const result::Result result = client.addZone(zone_finder);
+ if (result == result::EXIST) {
+ isc_throw(InMemoryConfigError, "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.
+ */
+ if (filetype_txt == "text") {
+ zone_finder->load(file_txt);
+ } else {
+ zone_finder->load(*container->getInstance().getIterator(
+ Name(origin_txt)));
+ }
+ }
+}
+
} // end unnamed namespace
DataSourceClient *
@@ -154,7 +253,12 @@ createInstance(isc::data::ConstElementPtr config, std::string& error) {
return (NULL);
}
try {
- return (new isc::datasrc::InMemoryClient());
+ std::auto_ptr client(new isc::datasrc::InMemoryClient());
+ applyConfig(*client, config);
+ return (client.release());
+ } catch (const isc::Exception& isce) {
+ error = isce.what();
+ return (NULL);
} catch (const std::exception& exc) {
error = std::string("Error creating memory datasource: ") + exc.what();
return (NULL);
diff --git a/src/lib/datasrc/rbnode_rrset.h b/src/lib/datasrc/rbnode_rrset.h
index 3e5d20a803..3161cdb00e 100644
--- a/src/lib/datasrc/rbnode_rrset.h
+++ b/src/lib/datasrc/rbnode_rrset.h
@@ -81,7 +81,7 @@ struct AdditionalNodeInfo;
/// can refer to its definition, and only for that purpose. Otherwise this is
/// essentially a private class of the in-memory data source implementation,
/// and an application shouldn't directly refer to this class.
-///
+///
// Note: non-Doxygen-documented methods are documented in the base class.
class RBNodeRRset : public isc::dns::AbstractRRset {
diff --git a/src/lib/datasrc/rbtree.h b/src/lib/datasrc/rbtree.h
index dbf05914c0..39646ac45c 100644
--- a/src/lib/datasrc/rbtree.h
+++ b/src/lib/datasrc/rbtree.h
@@ -237,7 +237,7 @@ private:
/// Return if callback is enabled at the node.
//@}
-private:
+
/// \brief Define rbnode color
enum RBNodeColor {BLACK, RED};
/// This is a factory class method of a special singleton null node.
@@ -263,6 +263,37 @@ private:
/// This method never throws an exception.
const RBNode* successor() const;
+ /// \brief return the next node which is smaller than current node
+ /// in the same subtree
+ ///
+ /// The predecessor for this node is the next smaller node in terms of
+ /// the DNSSEC order relation within the same single subtree.
+ /// Note that it may NOT be the next smaller node in the entire RBTree;
+ /// RBTree is a tree in tree, and the real next node may reside in
+ /// an upper or lower subtree of the subtree where this node belongs.
+ /// For example, if the predecessor node has a sub domain, the real next
+ /// node is the largest node in the sub domain tree.
+ ///
+ /// If this node is the smallest node within the subtree, this method
+ /// returns \c NULL_NODE().
+ ///
+ /// This method never throws an exception.
+ const RBNode* predecessor() const;
+
+ /// \brief private shared implementation of successor and predecessor
+ ///
+ /// As the two mentioned functions are merely mirror images of each other,
+ /// it makes little sense to keep both versions. So this is the body of the
+ /// functions and we call it with the correct pointers.
+ ///
+ /// Not to be called directly, not even by friends.
+ ///
+ /// The overhead of the member pointers should be optimised out, as this
+ /// will probably get completely inlined into predecessor and successor
+ /// methods.
+ const RBNode* abstractSuccessor(RBNode* RBNode::*left,
+ RBNode* RBNode::*right) const;
+
/// \name Data to maintain the rbtree structure.
//@{
RBNode* parent_;
@@ -283,7 +314,7 @@ private:
/// \par Adding down pointer to \c RBNode has two purposes:
/// \li Accelerate the search process, with sub domain tree, it splits the
/// big flat tree into several hierarchy trees.
- /// \li It saves memory useage as it allows storing only relative names,
+ /// \li It saves memory usage as it allows storing only relative names,
/// avoiding storage of the same domain labels multiple times.
RBNode* down_;
@@ -333,30 +364,48 @@ RBNode::~RBNode() {
template
const RBNode*
-RBNode::successor() const {
+RBNode::abstractSuccessor(RBNode* RBNode::*left, RBNode*
+ RBNode::*right) const
+{
+ // This function is written as a successor. It becomes predecessor if
+ // the left and right pointers are swapped. So in case of predecessor,
+ // the left pointer points to right and vice versa. Don't get confused
+ // by the idea, just imagine the pointers look into a mirror.
+
const RBNode* current = this;
// If it has right node, the successor is the left-most node of the right
// subtree.
- if (right_ != NULL_NODE()) {
- current = right_;
- while (current->left_ != NULL_NODE()) {
- current = current->left_;
+ if (current->*right != RBNode::NULL_NODE()) {
+ current = current->*right;
+ while (current->*left != RBNode::NULL_NODE()) {
+ current = current->*left;
}
return (current);
}
-
// Otherwise go up until we find the first left branch on our path to
// root. If found, the parent of the branch is the successor.
// Otherwise, we return the null node
const RBNode* parent = current->parent_;
- while (parent != NULL_NODE() && current == parent->right_) {
+ while (parent != RBNode::NULL_NODE() && current == parent->*right) {
current = parent;
parent = parent->parent_;
}
return (parent);
}
+template
+const RBNode*
+RBNode::successor() const {
+ return (abstractSuccessor(&RBNode::left_, &RBNode::right_));
+}
+
+template
+const RBNode*
+RBNode::predecessor() const {
+ // Swap the left and right pointers for the abstractSuccessor
+ return (abstractSuccessor(&RBNode::right_, &RBNode::left_));
+}
/// \brief RBTreeNodeChain stores detailed information of \c RBTree::find()
/// result.
@@ -364,8 +413,7 @@ RBNode::successor() const {
/// - The \c RBNode that was last compared with the search name, and
/// the comparison result at that point in the form of
/// \c isc::dns::NameComparisonResult.
-/// - A sequence of nodes that forms a path to the found node (which is
-/// not yet implemented).
+/// - A sequence of nodes that forms a path to the found node.
///
/// The comparison result can be used to handle some rare cases such as
/// empty node processing.
@@ -396,7 +444,7 @@ RBNode::successor() const {
template
class RBTreeNodeChain {
/// RBTreeNodeChain is initialized by RBTree, only RBTree has
- /// knowledge to manipuate it.
+ /// knowledge to manipulate it.
friend class RBTree;
public:
/// \name Constructors and Assignment Operator.
@@ -498,10 +546,10 @@ public:
private:
// the following private functions check invariants about the internal
// state using assert() instead of exception. The state of a chain
- // can only be modified operations within this file, so if any of the
+ // can only be modified by operations within this file, so if any of the
// assumptions fails it means an internal bug.
- /// \brief return whther node chain has node in it.
+ /// \brief return whether node chain has node in it.
///
/// \exception None
bool isEmpty() const { return (node_count_ == 0); }
@@ -655,7 +703,7 @@ public:
/// By default, nodes that don't have data (see RBNode::isEmpty) are
/// ignored and the result can be NOTFOUND even if there's a node whose
/// name matches. If the \c RBTree is constructed with its
- /// \c returnEmptyNode parameter being \c true, an empty node will also
+ /// \c returnEmptyNode parameter being \c true, empty nodes will also
/// be match candidates.
///
/// \note Even when \c returnEmptyNode is \c true, not all empty nodes
@@ -673,7 +721,7 @@ public:
/// if it throws, the exception will be propagated to the caller.
///
/// The \c name parameter says what should be found. The node parameter
- /// is output only and in case of EXACTMATCH and PARTIALMATCH, it is set
+ /// is output-only, and in case of EXACTMATCH or PARTIALMATCH, it is set
/// to a pointer to the found node.
///
/// They return:
@@ -710,6 +758,30 @@ public:
return (ret);
}
+ /// \brief Simple find, with node_path tracking
+ ///
+ /// Acts as described in the \ref find section.
+ Result find(const isc::dns::Name& name, RBNode** node,
+ RBTreeNodeChain& node_path) const
+ {
+ return (find(name, node, node_path, NULL, NULL));
+ }
+
+ /// \brief Simple find returning immutable node, with node_path tracking
+ ///
+ /// Acts as described in the \ref find section, but returns immutable node
+ /// pointer.
+ Result find(const isc::dns::Name& name, const RBNode** node,
+ RBTreeNodeChain& node_path) const
+ {
+ RBNode *target_node = NULL;
+ Result ret = (find(name, &target_node, node_path, NULL, NULL));
+ if (ret != NOTFOUND) {
+ *node = target_node;
+ }
+ return (ret);
+ }
+
/// \brief Find with callback and node chain.
/// \anchor callback
///
@@ -720,13 +792,16 @@ public:
///
/// This version of \c find() calls the callback whenever traversing (on
/// the way from root down the tree) a marked node on the way down through
- /// the domain namespace (see \c RBNode::enableCallback and related
- /// functions).
+ /// the domain namespace (see \c RBNode::FLAG_CALLBACK).
///
/// If you return true from the callback, the search is stopped and a
/// PARTIALMATCH is returned with the given node. Note that this node
/// doesn't really need to be the one with longest possible match.
///
+ /// The callback is not called for the node which matches exactly
+ /// (EXACTMATCH is returned). This is typically the last node in the
+ /// traversal during a successful search.
+ ///
/// This callback mechanism was designed with zone cut (delegation)
/// processing in mind. The marked nodes would be the ones at delegation
/// points. It is not expected that any other applications would need
@@ -741,38 +816,36 @@ public:
/// which is an object of class \c RBTreeNodeChain.
/// The passed parameter must be empty.
///
- /// \note The rest of the description isn't yet implemented. It will be
- /// handled in Trac ticket #517.
- ///
- /// On success, the node sequence stoed in \c node_path will contain all
+ /// On success, the node sequence stored in \c node_path will contain all
/// the ancestor nodes from the found node towards the root.
/// For example, if we look for o.w.y.d.e.f in the example \ref diagram,
/// \c node_path will contain w.y and d.e.f; the \c top() node of the
- /// chain will be o, w.f and d.e.f will be stored below it.
+ /// chain will be o, w.y and d.e.f will be stored below it.
///
/// This feature can be used to get the absolute name for a node;
/// to do so, we need to travel upside from the node toward the root,
/// concatenating all ancestor names. With the current implementation
/// it's not possible without a node chain, because there is a no pointer
/// from the root of a subtree to the parent subtree (this may change
- /// in a future version). A node chain can also be used to find the next
- /// node of a given node in the entire RBTree; the \c nextNode() method
- /// takes a node chain as a parameter.
+ /// in a future version). A node chain can also be used to find the
+ /// next and previous nodes of a given node in the entire RBTree;
+ /// the \c nextNode() and \c previousNode() methods take a node
+ /// chain as a parameter.
///
- /// \exception isc::BadValue node_path is not empty (not yet implemented).
+ /// \exception isc::BadValue node_path is not empty.
///
/// \param name Target to be found
/// \param node On success (either \c EXACTMATCH or \c PARTIALMATCH)
/// it will store a pointer to the matching node
/// \param node_path Other search details will be stored (see the
/// description)
- /// \param callback If non \c NULL, a call back function to be called
- /// at marked nodes (see above).
+ /// \param callback If non- \c NULL, a call back function to be called
+ /// at marked nodes (see the description).
/// \param callback_arg A caller supplied argument to be passed to
/// \c callback.
///
- /// \return As described above, but in case of callback returning true,
- /// it returns immediately with the current node.
+ /// \return As in the description, but in case of callback returning
+ /// \c true, it returns immediately with the current node.
template
Result find(const isc::dns::Name& name,
RBNode** node,
@@ -826,6 +899,30 @@ public:
/// the largest, \c NULL will be returned.
const RBNode* nextNode(RBTreeNodeChain& node_path) const;
+ /// \brief return the next smaller node in DNSSEC order from a node
+ /// searched by RBTree::find().
+ ///
+ /// This acts similarly to \c nextNode(), but it walks in the other
+ /// direction. But unlike \c nextNode(), this can start even if the
+ /// node requested by \c find() was not found. In that case, it will
+ /// identify the node that is previous to the queried name.
+ ///
+ /// \note \c previousNode() will iterate over all the nodes in RBTree
+ /// including empty nodes. If empty node isn't desired, it's easy to add
+ /// logic to check return node and keep invoking \c previousNode() until the
+ /// non-empty node is retrieved.
+ ///
+ /// \exception isc::BadValue node_path is empty.
+ ///
+ /// \param node_path A node chain that stores all the nodes along the path
+ /// from root to node and the result of \c find(). This will get modified.
+ /// You should not use the node_path again except for repetitive calls
+ /// of this method.
+ ///
+ /// \return An \c RBNode that is next smaller than \c node; if \c node is
+ /// the smallest, \c NULL will be returned.
+ const RBNode* previousNode(RBTreeNodeChain& node_path) const;
+
/// \brief Get the total number of nodes in the tree
///
/// It includes nodes internally created as a result of adding a domain
@@ -848,8 +945,8 @@ public:
//@{
/// \brief Insert the domain name into the tree.
///
- /// It either finds an already existing node of the given name or inserts
- /// a new one, if none exists yet. In any case, the inserted_node parameter
+ /// It either finds an already existing node of the given name, or inserts
+ /// a new one if none exists yet. In any case, the \c inserted_node parameter
/// is set to point to that node. You can fill data into it or modify it.
/// So, if you don't know if a node exists or not and you need to modify
/// it, just call insert and act by the result.
@@ -1059,15 +1156,7 @@ RBTree::nextNode(RBTreeNodeChain& node_path) const {
return (left_most);
}
- // node_path go to up level
- node_path.pop();
- // otherwise found the successor node in current level
- const RBNode* successor = node->successor();
- if (successor != NULLNODE) {
- node_path.push(successor);
- return (successor);
- }
-
+ // try to find a successor.
// if no successor found move to up level, the next successor
// is the successor of up node in the up level tree, if
// up node doesn't have successor we gonna keep moving to up
@@ -1084,6 +1173,143 @@ RBTree::nextNode(RBTreeNodeChain& node_path) const {
return (NULL);
}
+template
+const RBNode*
+RBTree::previousNode(RBTreeNodeChain& node_path) const {
+ if (getNodeCount() == 0) {
+ // Special case for empty trees. It would look every time like
+ // we didn't search, because the last compared is empty. This is
+ // a slight hack and not perfect, but this is better than throwing
+ // on empty tree. And we probably won't meet an empty tree in practice
+ // anyway.
+ return (NULL);
+ }
+ if (node_path.last_compared_ == NULL) {
+ isc_throw(isc::BadValue,
+ "RBTree::previousNode() called before find()");
+ }
+
+ // If the relation isn't EQUAL, it means the find was called previously
+ // and didn't find the exact node. Therefore we need to locate the place
+ // to start iterating the chain of domains.
+ //
+ // The logic here is not too complex, we just need to take care to handle
+ // all the cases and decide where to go from there.
+ switch (node_path.last_comparison_.getRelation()) {
+ case dns::NameComparisonResult::COMMONANCESTOR:
+ // We compared with a leaf in the tree and wanted to go to one of
+ // the children. But the child was not there. It now depends on the
+ // direction in which we wanted to go.
+ if (node_path.last_comparison_.getOrder() < 0) {
+ // We wanted to go left. So the one we compared with is
+ // the one higher than we wanted. If we just put it into
+ // the node_path, then the following algorithm below will find
+ // the smaller one.
+ //
+ // This is exactly the same as with superdomain below.
+ // Therefore, we just fall through to the next case.
+ } else {
+ // We wanted to go right. That means we want to output the
+ // one which is the largest in the tree defined by the
+ // compared one (it is either the compared one, or some
+ // subdomain of it). There probably is not an easy trick
+ // for this, so we just find the correct place.
+ const RBNode* current(node_path.last_compared_);
+ while (current != NULLNODE) {
+ node_path.push(current);
+ // Go a level down and as much right there as possible
+ current = current->down_;
+ while (current->right_ != NULLNODE) {
+ // A small trick. The current may be NULLNODE, but
+ // such node has the right_ pointer and it is equal
+ // to NULLNODE.
+ current = current->right_;
+ }
+ }
+ // Now, the one on top of the path is the one we want. We
+ // return it now and leave it there, so we can search for
+ // previous of it the next time we'are called.
+ node_path.last_comparison_ =
+ dns::NameComparisonResult(0, 0,
+ dns::NameComparisonResult::EQUAL);
+ return (node_path.top());
+ }
+ // No break; here - we want to fall through. See above.
+ case dns::NameComparisonResult::SUPERDOMAIN:
+ // This is the case there's a "compressed" node and we looked for
+ // only part of it. The node itself is larger than we wanted, but
+ // if we put it to the node_path and then go one step left from it,
+ // we get the correct result.
+ node_path.push(node_path.last_compared_);
+ // Correct the comparison result, so we won't trigger this case
+ // next time previousNode is called. We already located the correct
+ // place to start. The value is partly nonsense, but that doesn't
+ // matter any more.
+ node_path.last_comparison_ =
+ dns::NameComparisonResult(0, 0,
+ dns::NameComparisonResult::EQUAL);
+ break;
+ case dns::NameComparisonResult::SUBDOMAIN:
+ // A subdomain means we returned the one above the searched one
+ // already and it is on top of the stack. This is was smaller
+ // than the one already, but we want to return yet smaller one.
+ // So we act as if it was EQUAL.
+ break;
+ case dns::NameComparisonResult::EQUAL:
+ // The find gave us an exact match or the previousNode was called
+ // already, which located the exact node. The rest of the function
+ // goes one domain left and returns it for us.
+ break;
+ }
+
+ // So, the node_path now contains the path to a node we want previous for.
+ // We just need to go one step left.
+
+ if (node_path.isEmpty()) {
+ // We got past the first one. So, we're returning NULL from
+ // now on.
+ return (NULL);
+ }
+
+ const RBNode* node(node_path.top());
+
+ // Try going left in this tree
+ node = node->predecessor();
+ if (node == NULLNODE) {
+ // We are the smallest ones in this tree. We go one level
+ // up. That one is the smaller one than us.
+
+ node_path.pop();
+ if (node_path.isEmpty()) {
+ // We're past the first one
+ return (NULL);
+ } else {
+ return (node_path.top());
+ }
+ }
+
+ // Exchange the node at the top of the path, as we move horizontaly
+ // through the domain tree
+ node_path.pop();
+ node_path.push(node);
+
+ // Try going as deep as possible, keeping on the right side of the trees
+ while (node->down_ != NULLNODE) {
+ // Move to the tree below
+ node = node->down_;
+ // And get as much to the right of the tree as possible
+ while (node->right_ != NULLNODE) {
+ node = node->right_;
+ }
+ // Now, we found the right-most node in the sub-tree, we need to
+ // include it in the path
+ node_path.push(node);
+ }
+
+ // Now, if the current node has no down_ pointer any more, it's the
+ // correct one.
+ return (node);
+}
template
typename RBTree::Result
diff --git a/src/lib/datasrc/static.zone.pre b/src/lib/datasrc/static.zone.pre
new file mode 100644
index 0000000000..16a7379ea8
--- /dev/null
+++ b/src/lib/datasrc/static.zone.pre
@@ -0,0 +1,12 @@
+;; This is the content of the BIND./CH zone. It contains the version and
+;; authors (called VERSION.BIND. and AUTHORS.BIND.). You can add more or
+;; modify the zone. Then you can reload the zone by issuing the command
+;;
+;; loadzone CH BIND
+;;
+;; in the bindctl.
+
+BIND. 0 CH SOA bind. authors.bind. 0 28800 7200 604800 86400
+
+VERSION.BIND. 0 CH TXT "@@VERSION_STRING@@"
+;; HOSTNAME.BIND 0 CH TXT "localhost"
diff --git a/src/lib/datasrc/static_datasrc_link.cc b/src/lib/datasrc/static_datasrc_link.cc
new file mode 100644
index 0000000000..789580d057
--- /dev/null
+++ b/src/lib/datasrc/static_datasrc_link.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "client.h"
+#include "memory_datasrc.h"
+
+#include
+#include
+
+#include
+#include
+
+using namespace isc::data;
+using namespace isc::dns;
+using namespace boost;
+using namespace std;
+
+namespace isc {
+namespace datasrc {
+
+DataSourceClient*
+createInstance(ConstElementPtr config, string& error) {
+ try {
+ // Create the data source
+ auto_ptr client(new InMemoryClient());
+ // Hardcode the origin and class
+ shared_ptr
+ finder(new InMemoryZoneFinder(RRClass::CH(), Name("BIND")));
+ // Fill it with data
+ const string path(config->stringValue());
+ finder->load(path);
+ // And put the zone inside
+ client->addZone(finder);
+ return (client.release());
+ }
+ catch (const std::exception& e) {
+ error = e.what();
+ }
+ catch (...) {
+ error = "Unknown exception";
+ }
+ return (NULL);
+}
+
+void
+destroyInstance(DataSourceClient* instance) {
+ delete instance;
+}
+
+}
+}
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 90fb3e4bf1..e72fd71f8b 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -17,6 +17,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ libtool --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
noinst_PROGRAMS =
if HAVE_GTEST
@@ -31,9 +34,7 @@ common_sources = run_unittests.cc
common_sources += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
common_sources += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
-common_ldadd = $(GTEST_LDADD)
-common_ldadd += $(SQLITE_LIBS)
-common_ldadd += $(top_builddir)/src/lib/datasrc/libdatasrc.la
+common_ldadd = $(top_builddir)/src/lib/datasrc/libdatasrc.la
common_ldadd += $(top_builddir)/src/lib/dns/libdns++.la
common_ldadd += $(top_builddir)/src/lib/util/libutil.la
common_ldadd += $(top_builddir)/src/lib/log/liblog.la
@@ -41,6 +42,7 @@ common_ldadd += $(top_builddir)/src/lib/exceptions/libexceptions.la
common_ldadd += $(top_builddir)/src/lib/cc/libcc.la
common_ldadd += $(top_builddir)/src/lib/testutils/libtestutils.la
common_ldadd += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+common_ldadd += $(GTEST_LDADD) $(SQLITE_LIBS)
# The general tests
run_unittests_SOURCES = $(common_sources)
@@ -60,6 +62,7 @@ run_unittests_SOURCES += memory_datasrc_unittest.cc
run_unittests_SOURCES += rbnode_rrset_unittest.cc
run_unittests_SOURCES += zone_finder_context_unittest.cc
run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
+run_unittests_SOURCES += client_list_unittest.cc
# We need the actual module implementation in the tests (they are not part
# of libdatasrc)
@@ -114,3 +117,4 @@ EXTRA_DIST += testdata/test.sqlite3
EXTRA_DIST += testdata/new_minor_schema.sqlite3
EXTRA_DIST += testdata/newschema.sqlite3
EXTRA_DIST += testdata/oldschema.sqlite3
+EXTRA_DIST += testdata/static.zone
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
new file mode 100644
index 0000000000..ae22470e78
--- /dev/null
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -0,0 +1,472 @@
+// 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
+#include
+#include
+
+#include
+
+#include
+
+#include
+
+using namespace isc::datasrc;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace boost;
+using namespace std;
+
+namespace {
+
+// A test data source. It pretends it has some zones.
+class MockDataSourceClient : public DataSourceClient {
+public:
+ class Finder : public ZoneFinder {
+ public:
+ Finder(const Name& origin) :
+ origin_(origin)
+ {}
+ Name getOrigin() const { return (origin_); }
+ // The rest is not to be called, so just have them
+ RRClass getClass() const {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ shared_ptr find(const Name&, const RRType&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ shared_ptr findAll(const Name&,
+ vector&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ FindNSEC3Result findNSEC3(const Name&, bool) {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ private:
+ Name origin_;
+ };
+ // Constructor from a list of zones.
+ MockDataSourceClient(const char* zone_names[]) {
+ for (const char** zone(zone_names); *zone; ++zone) {
+ zones.insert(Name(*zone));
+ }
+ }
+ // Constructor from configuration. The list of zones will be empty, but
+ // it will keep the configuration inside for further inspection.
+ MockDataSourceClient(const string& type,
+ const ConstElementPtr& configuration) :
+ type_(type),
+ configuration_(configuration)
+ {}
+ virtual FindResult findZone(const Name& name) const {
+ if (zones.empty()) {
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+ set::const_iterator it(zones.upper_bound(name));
+ if (it == zones.begin()) {
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+ --it;
+ NameComparisonResult compar(it->compare(name));
+ const ZoneFinderPtr finder(new Finder(*it));
+ switch (compar.getRelation()) {
+ case NameComparisonResult::EQUAL:
+ return (FindResult(result::SUCCESS, finder));
+ case NameComparisonResult::SUPERDOMAIN:
+ return (FindResult(result::PARTIALMATCH, finder));
+ default:
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+ }
+ // These methods are not used. They just need to be there to have
+ // complete vtable.
+ virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ virtual pair
+ getJournalReader(const Name&, uint32_t, uint32_t) const
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ const string type_;
+ const ConstElementPtr configuration_;
+private:
+ set zones;
+};
+
+
+// The test version is the same as the normal version. We, however, add
+// some methods to dig directly in the internals, for the tests.
+class TestedList : public ConfigurableClientList {
+public:
+ DataSources& getDataSources() { return (data_sources_); }
+ // Overwrite the list's method to get a data source with given type
+ // and configuration. We mock the data source and don't create the
+ // container. This is just to avoid some complexity in the tests.
+ virtual DataSourcePair getDataSourceClient(const string& type,
+ const ConstElementPtr&
+ configuration)
+ {
+ if (type == "error") {
+ isc_throw(DataSourceError, "The error data source type");
+ }
+ shared_ptr
+ ds(new MockDataSourceClient(type, configuration));
+ // Make sure it is deleted when the test list is deleted.
+ to_delete_.push_back(ds);
+ return (DataSourcePair(ds.get(), DataSourceClientContainerPtr()));
+ }
+private:
+ // Hold list of data sources created internally, so they are preserved
+ // until the end of the test and then deleted.
+ vector > to_delete_;
+};
+
+const char* ds_zones[][3] = {
+ {
+ "example.org.",
+ "example.com.",
+ NULL
+ },
+ {
+ "sub.example.org.",
+ NULL, NULL
+ },
+ {
+ NULL, NULL, NULL
+ },
+ {
+ "sub.example.org.",
+ NULL, NULL
+ }
+};
+
+const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones));
+
+class ListTest : public ::testing::Test {
+public:
+ ListTest() :
+ // The empty list corresponds to a list with no elements inside
+ list_(new TestedList()),
+ config_elem_(Element::fromJSON("["
+ "{"
+ " \"type\": \"test_type\","
+ " \"cache\": \"off\","
+ " \"params\": {}"
+ "}]"))
+ {
+ for (size_t i(0); i < ds_count; ++ i) {
+ shared_ptr
+ ds(new MockDataSourceClient(ds_zones[i]));
+ ds_.push_back(ds);
+ ds_info_.push_back(ConfigurableClientList::DataSourceInfo(ds.get(),
+ DataSourceClientContainerPtr()));
+ }
+ }
+ // Check the positive result is as we expect it.
+ void positiveResult(const ClientList::FindResult& result,
+ const shared_ptr& dsrc,
+ const Name& name, bool exact,
+ const char* test)
+ {
+ SCOPED_TRACE(test);
+ EXPECT_EQ(dsrc.get(), result.dsrc_client_);
+ ASSERT_NE(ZoneFinderPtr(), result.finder_);
+ EXPECT_EQ(name, result.finder_->getOrigin());
+ EXPECT_EQ(exact, result.exact_match_);
+ }
+ // Configure the list with multiple data sources, according to
+ // some configuration. It uses the index as parameter, to be able to
+ // loop through the configurations.
+ void multiConfiguration(size_t index) {
+ list_->getDataSources().clear();
+ switch (index) {
+ case 2:
+ list_->getDataSources().push_back(ds_info_[2]);
+ // The ds_[2] is empty. We just check that it doesn't confuse
+ // us. Fall through to the case 0.
+ case 0:
+ list_->getDataSources().push_back(ds_info_[0]);
+ list_->getDataSources().push_back(ds_info_[1]);
+ break;
+ case 1:
+ // The other order
+ list_->getDataSources().push_back(ds_info_[1]);
+ list_->getDataSources().push_back(ds_info_[0]);
+ break;
+ case 3:
+ list_->getDataSources().push_back(ds_info_[1]);
+ list_->getDataSources().push_back(ds_info_[0]);
+ // It is the same as ds_[1], but we take from the first one.
+ // The first one to match is the correct one.
+ list_->getDataSources().push_back(ds_info_[3]);
+ break;
+ default:
+ FAIL() << "Unknown configuration index " << index;
+ }
+ }
+ void checkDS(size_t index, const string& type, const string& params) const
+ {
+ ASSERT_GT(list_->getDataSources().size(), index);
+ MockDataSourceClient* ds(dynamic_cast(
+ list_->getDataSources()[index].data_src_client_));
+
+ // Comparing with NULL does not work
+ ASSERT_NE(ds, static_cast(NULL));
+ EXPECT_EQ(type, ds->type_);
+ EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_));
+ }
+ shared_ptr list_;
+ const ClientList::FindResult negativeResult_;
+ vector > ds_;
+ vector ds_info_;
+ const ConstElementPtr config_elem_;
+};
+
+// Test the test itself
+TEST_F(ListTest, selfTest) {
+ EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
+ EXPECT_EQ(result::PARTIALMATCH,
+ ds_[0]->findZone(Name("sub.example.org")).code);
+ EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("org")).code);
+ EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code);
+ EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
+ EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
+}
+
+// Test the list we create with empty configuration is, in fact, empty
+TEST_F(ListTest, emptyList) {
+ EXPECT_TRUE(list_->getDataSources().empty());
+}
+
+// Check the values returned by a find on an empty list. It should be
+// a negative answer (nothing found) no matter if we want an exact or inexact
+// match.
+TEST_F(ListTest, emptySearch) {
+ // No matter what we try, we don't get an answer.
+
+ // Note: we don't have operator<< for the result class, so we cannot use
+ // EXPECT_EQ. Same for other similar cases.
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), false,
+ false));
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), false,
+ true));
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), true,
+ false));
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), true,
+ true));
+}
+
+// Put a single data source inside the list and check it can find an
+// exact match if there's one.
+TEST_F(ListTest, singleDSExactMatch) {
+ list_->getDataSources().push_back(ds_info_[0]);
+ // This zone is not there
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("org."), true));
+ // But this one is, so check it.
+ positiveResult(list_->find(Name("example.org"), true), ds_[0],
+ Name("example.org"), true, "Exact match");
+ // When asking for a sub zone of a zone there, we get nothing
+ // (we want exact match, this would be partial one)
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("sub.example.org."),
+ true));
+}
+
+// When asking for a partial match, we get all that the exact one, but more.
+TEST_F(ListTest, singleDSBestMatch) {
+ list_->getDataSources().push_back(ds_info_[0]);
+ // This zone is not there
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("org.")));
+ // But this one is, so check it.
+ positiveResult(list_->find(Name("example.org")), ds_[0],
+ Name("example.org"), true, "Exact match");
+ // When asking for a sub zone of a zone there, we get the parent
+ // one.
+ positiveResult(list_->find(Name("sub.example.org.")), ds_[0],
+ Name("example.org"), false, "Subdomain match");
+}
+
+const char* const test_names[] = {
+ "Sub second",
+ "Sub first",
+ "With empty",
+ "With a duplicity"
+};
+
+TEST_F(ListTest, multiExactMatch) {
+ // Run through all the multi-configurations
+ for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) {
+ SCOPED_TRACE(test_names[i]);
+ multiConfiguration(i);
+ // Something that is nowhere there
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("org."), true));
+ // This one is there exactly.
+ positiveResult(list_->find(Name("example.org"), true), ds_[0],
+ Name("example.org"), true, "Exact match");
+ // This one too, but in a different data source.
+ positiveResult(list_->find(Name("sub.example.org."), true), ds_[1],
+ Name("sub.example.org"), true, "Subdomain match");
+ // But this one is in neither data source.
+ EXPECT_TRUE(negativeResult_ ==
+ list_->find(Name("sub.example.com."), true));
+ }
+}
+
+TEST_F(ListTest, multiBestMatch) {
+ // Run through all the multi-configurations
+ for (size_t i(0); i < 4; ++ i) {
+ SCOPED_TRACE(test_names[i]);
+ multiConfiguration(i);
+ // Something that is nowhere there
+ EXPECT_TRUE(negativeResult_ == list_->find(Name("org.")));
+ // This one is there exactly.
+ positiveResult(list_->find(Name("example.org")), ds_[0],
+ Name("example.org"), true, "Exact match");
+ // This one too, but in a different data source.
+ positiveResult(list_->find(Name("sub.example.org.")), ds_[1],
+ Name("sub.example.org"), true, "Subdomain match");
+ // But this one is in neither data source. But it is a subdomain
+ // of one of the zones in the first data source.
+ positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
+ Name("example.com."), false, "Subdomain in com");
+ }
+}
+
+// Check the configuration is empty when the list is empty
+TEST_F(ListTest, configureEmpty) {
+ ConstElementPtr elem(new ListElement);
+ list_->configure(*elem, true);
+ EXPECT_TRUE(list_->getDataSources().empty());
+}
+
+// Check we can get multiple data sources and they are in the right order.
+TEST_F(ListTest, configureMulti) {
+ ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"type1\","
+ " \"cache\": \"off\","
+ " \"params\": {}"
+ "},"
+ "{"
+ " \"type\": \"type2\","
+ " \"cache\": \"off\","
+ " \"params\": {}"
+ "}]"
+ ));
+ list_->configure(*elem, true);
+ EXPECT_EQ(2, list_->getDataSources().size());
+ checkDS(0, "type1", "{}");
+ checkDS(1, "type2", "{}");
+}
+
+// Check we can pass whatever we want to the params
+TEST_F(ListTest, configureParams) {
+ const char* params[] = {
+ "true",
+ "false",
+ "null",
+ "\"hello\"",
+ "42",
+ "[]",
+ "{}",
+ NULL
+ };
+ for (const char** param(params); *param; ++param) {
+ SCOPED_TRACE(*param);
+ ConstElementPtr elem(Element::fromJSON(string("["
+ "{"
+ " \"type\": \"t\","
+ " \"cache\": \"off\","
+ " \"params\": ") + *param +
+ "}]"));
+ list_->configure(*elem, true);
+ EXPECT_EQ(1, list_->getDataSources().size());
+ checkDS(0, "t", *param);
+ }
+}
+
+TEST_F(ListTest, wrongConfig) {
+ const char* configs[] = {
+ // A lot of stuff missing from there
+ "[{\"type\": \"test_type\", \"params\": 13}, {}]",
+ // Some bad types completely
+ "{}",
+ "true",
+ "42",
+ "null",
+ "[{\"type\": \"test_type\", \"params\": 13}, true]",
+ "[{\"type\": \"test_type\", \"params\": 13}, []]",
+ "[{\"type\": \"test_type\", \"params\": 13}, 42]",
+ // Bad type of type
+ "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": 42}]",
+ "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": true}]",
+ "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]",
+ "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]",
+ "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]",
+ // TODO: Once cache is supported, add some invalid cache values
+ NULL
+ };
+ // Put something inside to see it survives the exception
+ list_->configure(*config_elem_, true);
+ checkDS(0, "test_type", "{}");
+ for (const char** config(configs); *config; ++config) {
+ SCOPED_TRACE(*config);
+ ConstElementPtr elem(Element::fromJSON(*config));
+ EXPECT_THROW(list_->configure(*elem, true),
+ ConfigurableClientList::ConfigurationError);
+ // Still untouched
+ checkDS(0, "test_type", "{}");
+ EXPECT_EQ(1, list_->getDataSources().size());
+ }
+}
+
+// The param thing defaults to null. Cache is not used yet.
+TEST_F(ListTest, defaults) {
+ ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"type1\""
+ "}]"));
+ list_->configure(*elem, true);
+ EXPECT_EQ(1, list_->getDataSources().size());
+ checkDS(0, "type1", "null");
+}
+
+// Check we can call the configure multiple times, to change the configuration
+TEST_F(ListTest, reconfigure) {
+ ConstElementPtr empty(new ListElement);
+ list_->configure(*config_elem_, true);
+ checkDS(0, "test_type", "{}");
+ list_->configure(*empty, true);
+ EXPECT_TRUE(list_->getDataSources().empty());
+ list_->configure(*config_elem_, true);
+ checkDS(0, "test_type", "{}");
+}
+
+// Make sure the data source error exception from the factory is propagated
+TEST_F(ListTest, dataSrcError) {
+ ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"error\""
+ "}]"));
+ list_->configure(*config_elem_, true);
+ checkDS(0, "test_type", "{}");
+ EXPECT_THROW(list_->configure(*elem, true), DataSourceError);
+ checkDS(0, "test_type", "{}");
+}
+
+}
diff --git a/src/lib/datasrc/tests/client_unittest.cc b/src/lib/datasrc/tests/client_unittest.cc
index 64ad25f3df..87ab5e05db 100644
--- a/src/lib/datasrc/tests/client_unittest.cc
+++ b/src/lib/datasrc/tests/client_unittest.cc
@@ -56,4 +56,8 @@ TEST_F(ClientTest, defaultIterator) {
EXPECT_THROW(client_.getIterator(Name(".")), isc::NotImplemented);
}
+TEST_F(ClientTest, defaultGetZoneCount) {
+ EXPECT_THROW(client_.getZoneCount(), isc::NotImplemented);
+}
+
}
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 110b3f0734..c96a1d28a8 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -142,9 +142,11 @@ const char* const TEST_RECORDS[][5] = {
{"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
{"delegation.example.org.", "NS", "3600", "",
"ns.delegation.example.org."},
- {"delegation.example.org.", "DS", "3600", "", "1 RSAMD5 2 abcd"},
+ {"delegation.example.org.", "DS", "3600", "", "1 1 2 abcd"},
{"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
"20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"delegation.example.org.", "RRSIG", "3600", "", "DS 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
{"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
{"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
@@ -156,6 +158,16 @@ const char* const TEST_RECORDS[][5] = {
{"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
+ // Insecure delegation (i.e., no DS at the delegation point)
+ {"insecdelegation.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"insecdelegation.example.org.", "NSEC", "3600", "",
+ "dummy.example.org. NS NSEC"},
+ // and a DS under the zone cut. Such an RR shouldn't exist in a sane zone,
+ // but it could by error or some malicious attempt. It shouldn't confuse
+ // the implementation)
+ {"child.insecdelegation.example.org.", "DS", "3600", "", "DS 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
// Broken NS
{"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
{"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
@@ -214,6 +226,15 @@ const char* const TEST_RECORDS[][5] = {
{NULL, NULL, NULL, NULL, NULL},
};
+// NSEC3PARAM at the zone origin and its RRSIG. These will be added
+// separately for some NSEC3 related tests.
+const char* TEST_NSEC3PARAM_RECORDS[][5] = {
+ {"example.org.", "NSEC3PARAM", "3600", "", "1 0 12 aabbccdd"},
+ {"example.org.", "RRSIG", "3600", "", "NSEC3PARAM 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {NULL, NULL, NULL, NULL, NULL}
+};
+
// FIXME: Taken from a different test. Fill with proper data when creating a test.
const char* TEST_NSEC3_RECORDS[][5] = {
{apex_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
@@ -1065,24 +1086,15 @@ public:
// tests. Note that the NSEC3 namespace is available in other tests, but
// it should not be accessed at that time.
void enableNSEC3() {
- // We place the signature first, so it's in the block with the other
- // signatures
- vector signature;
- signature.push_back("RRSIG");
- signature.push_back("3600");
- signature.push_back("");
- signature.push_back("NSEC3PARAM 5 3 3600 20000101000000 20000201000000 "
- "12345 example.org. FAKEFAKEFAKE");
- signature.push_back("exmaple.org.");
- (*readonly_records_)["example.org."].push_back(signature);
- // Now the NSEC3 param itself
- vector param;
- param.push_back("NSEC3PARAM");
- param.push_back("3600");
- param.push_back("");
- param.push_back("1 0 12 aabbccdd");
- param.push_back("example.org.");
- (*readonly_records_)["example.org."].push_back(param);
+ for (int i = 0; TEST_NSEC3PARAM_RECORDS[i][0] != NULL; ++i) {
+ vector param;
+ param.push_back(TEST_NSEC3PARAM_RECORDS[i][1]); // RRtype
+ param.push_back(TEST_NSEC3PARAM_RECORDS[i][2]); // TTL
+ param.push_back(""); // sigtype, unused
+ param.push_back(TEST_NSEC3PARAM_RECORDS[i][4]); // RDATA
+ param.push_back(TEST_NSEC3PARAM_RECORDS[i][0]); // owner name
+ (*readonly_records_)[param.back()].push_back(param);
+ }
}
};
@@ -1324,6 +1336,36 @@ public:
addRecordToZone(columns);
}
+ // We don't add NSEC3s until we are explicitly told we need them
+ // in enableNSEC3(); these would break some non NSEC3 tests.
+ commit();
+ }
+
+ void enableNSEC3() {
+ startUpdateZone("example.org.", false);
+
+ // Add NSECPARAM at the zone origin
+ for (int i = 0; TEST_NSEC3PARAM_RECORDS[i][0] != NULL; ++i) {
+ const string param_columns[ADD_COLUMN_COUNT] = {
+ TEST_NSEC3PARAM_RECORDS[i][0], // name
+ Name(param_columns[ADD_NAME]).reverse().toText(), // revname
+ TEST_NSEC3PARAM_RECORDS[i][2], // TTL
+ TEST_NSEC3PARAM_RECORDS[i][1], // RR type
+ TEST_NSEC3PARAM_RECORDS[i][3], // sigtype
+ TEST_NSEC3PARAM_RECORDS[i][4] }; // RDATA
+ addRecordToZone(param_columns);
+ }
+
+ // Add NSEC3s
+ for (int i = 0; TEST_NSEC3_RECORDS[i][0] != NULL; ++i) {
+ const string nsec3_columns[ADD_NSEC3_COLUMN_COUNT] = {
+ Name(TEST_NSEC3_RECORDS[i][0]).split(0, 1).toText(true),
+ TEST_NSEC3_RECORDS[i][2], // TTL
+ TEST_NSEC3_RECORDS[i][1], // RR type
+ TEST_NSEC3_RECORDS[i][4] }; // RDATA
+ addNSEC3RecordToZone(nsec3_columns);
+ }
+
commit();
}
};
@@ -2171,6 +2213,48 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
DataSourceError);
}
+TYPED_TEST(DatabaseClientTest, findDS) {
+ // Type DS query is an exception to the general delegation case; the NS
+ // should be ignored and it should be treated just like normal
+ // authoritative data.
+
+ boost::shared_ptr finder(this->getFinder());
+
+ // DS exists at the delegation point. It should be returned with result
+ // code of SUCCESS.
+ this->expected_rdatas_.push_back("1 1 2 abcd"),
+ this->expected_sig_rdatas_.push_back("DS 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(*finder, Name("delegation.example.org."),
+ RRType::DS(), RRType::DS(), this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_DEFAULT);
+
+ // DS doesn't exist at the delegation point. The result should be
+ // NXRRSET, and if DNSSEC is requested and the zone is NSEC-signed,
+ // the corresponding NSEC should be returned (normally with its RRSIG,
+ // but in this simplified test setup it's omitted in the test data).
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("dummy.example.org. NS NSEC");
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, Name("insecdelegation.example.org."),
+ RRType::DS(), RRType::NSEC(), this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_NSEC_SIGNED,
+ Name("insecdelegation.example.org."), ZoneFinder::FIND_DNSSEC);
+
+ // Some insane case: DS under a zone cut. It's included in the DB, but
+ // shouldn't be visible via finder.
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com");
+ doFindTest(*finder, Name("child.insecdelegation.example.org"),
+ RRType::DS(), RRType::NS(), this->rrttl_,
+ ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->empty_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ Name("insecdelegation.example.org."), ZoneFinder::FIND_DNSSEC);
+}
+
TYPED_TEST(DatabaseClientTest, emptyDomain) {
boost::shared_ptr finder(this->getFinder());
@@ -3538,33 +3622,6 @@ TYPED_TEST(DatabaseClientTest, compoundUpdate) {
this->empty_rdatas_);
}
-TYPED_TEST(DatabaseClientTest, previous) {
- boost::shared_ptr finder(this->getFinder());
-
- EXPECT_EQ(Name("www.example.org."),
- finder->findPreviousName(Name("www2.example.org.")));
- // Check a name that doesn't exist there
- EXPECT_EQ(Name("www.example.org."),
- finder->findPreviousName(Name("www1.example.org.")));
- if (this->is_mock_) { // We can't really force the DB to throw
- // Check it doesn't crash or anything if the underlying DB throws
- DataSourceClient::FindResult
- zone(this->client_->findZone(Name("bad.example.org")));
- finder =
- dynamic_pointer_cast(zone.zone_finder);
-
- EXPECT_THROW(finder->findPreviousName(Name("bad.example.org")),
- isc::NotImplemented);
- } else {
- // No need to test this on mock one, because we test only that
- // the exception gets through
-
- // A name before the origin
- EXPECT_THROW(finder->findPreviousName(Name("example.com")),
- isc::NotImplemented);
- }
-}
-
TYPED_TEST(DatabaseClientTest, invalidRdata) {
boost::shared_ptr finder(this->getFinder());
@@ -3592,13 +3649,6 @@ TEST_F(MockDatabaseClientTest, missingNSEC) {
this->expected_rdatas_, this->expected_sig_rdatas_);
}
-TEST_F(MockDatabaseClientTest, badName) {
- boost::shared_ptr finder(this->getFinder());
-
- EXPECT_THROW(finder->findPreviousName(Name("brokenname.example.org.")),
- DataSourceError);
-}
-
/*
* Test correct use of the updater with a journal.
*/
@@ -3975,11 +4025,11 @@ TEST_F(MockDatabaseClientTest, journalWithBadData) {
}
/// Let us test a little bit of NSEC3.
-TEST_F(MockDatabaseClientTest, findNSEC3) {
+TYPED_TEST(DatabaseClientTest, findNSEC3) {
// Set up the faked hash calculator.
- setNSEC3HashCreator(&test_nsec3_hash_creator_);
+ setNSEC3HashCreator(&this->test_nsec3_hash_creator_);
- DataSourceClient::FindResult
+ const DataSourceClient::FindResult
zone(this->client_->findZone(Name("example.org")));
ASSERT_EQ(result::SUCCESS, zone.code);
boost::shared_ptr finder(
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
index e98f9bcc68..2031d50842 100644
--- a/src/lib/datasrc/tests/factory_unittest.cc
+++ b/src/lib/datasrc/tests/factory_unittest.cc
@@ -28,6 +28,8 @@ using namespace isc::datasrc;
using namespace isc::data;
std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
+const std::string STATIC_DS_FILE = TEST_DATA_DIR "/static.zone";
+const std::string ROOT_ZONE_FILE = TEST_DATA_DIR "/root.zone";
namespace {
@@ -188,8 +190,8 @@ TEST(FactoryTest, memoryClient) {
DataSourceError);
config->set("type", Element::create("memory"));
- ASSERT_THROW(DataSourceClientContainer("memory", config),
- DataSourceError);
+ // no config at all should result in a default empty memory client
+ ASSERT_NO_THROW(DataSourceClientContainer("memory", config));
config->set("class", ElementPtr());
ASSERT_THROW(DataSourceClientContainer("memory", config),
@@ -204,8 +206,7 @@ TEST(FactoryTest, memoryClient) {
DataSourceError);
config->set("class", Element::create("IN"));
- ASSERT_THROW(DataSourceClientContainer("memory", config),
- DataSourceError);
+ ASSERT_NO_THROW(DataSourceClientContainer("memory", config));
config->set("zones", ElementPtr());
ASSERT_THROW(DataSourceClientContainer("memory", config),
@@ -236,5 +237,59 @@ TEST(FactoryTest, badType) {
DataSourceError);
}
+// Check the static data source can be loaded.
+TEST(FactoryTest, staticDS) {
+ // The only configuration is the file to load.
+ const ConstElementPtr config(new StringElement(STATIC_DS_FILE));
+ // Get the data source
+ DataSourceClientContainer dsc("static", config);
+ // And try getting something out to see if it really works.
+ DataSourceClient::FindResult
+ result(dsc.getInstance().findZone(isc::dns::Name("BIND")));
+ ASSERT_EQ(result::SUCCESS, result.code);
+ EXPECT_EQ(isc::dns::Name("BIND"), result.zone_finder->getOrigin());
+ EXPECT_EQ(isc::dns::RRClass::CH(), result.zone_finder->getClass());
+ const isc::dns::ConstRRsetPtr
+ version(result.zone_finder->find(isc::dns::Name("VERSION.BIND"),
+ isc::dns::RRType::TXT())->rrset);
+ ASSERT_NE(isc::dns::ConstRRsetPtr(), version);
+ EXPECT_EQ(isc::dns::Name("VERSION.BIND"), version->getName());
+ EXPECT_EQ(isc::dns::RRClass::CH(), version->getClass());
+ EXPECT_EQ(isc::dns::RRType::TXT(), version->getType());
+}
+
+// Check that file not containing BIND./CH is rejected
+//
+// FIXME: This test is disabled because the InMemoryZoneFinder::load does
+// not check if the data loaded correspond with the origin. The static
+// factory is not the place to fix that.
+TEST(FactoryTest, DISABLED_staticDSBadFile) {
+ // The only configuration is the file to load.
+ const ConstElementPtr config(new StringElement(STATIC_DS_FILE));
+ // See it does not want the file
+ EXPECT_THROW(DataSourceClientContainer("static", config), DataSourceError);
+}
+
+// Check that some bad configs are rejected
+TEST(FactoryTest, staticDSBadConfig) {
+ const char* configs[] = {
+ // The file does not exist
+ "\"/does/not/exist\"",
+ // Bad types
+ "null",
+ "42",
+ "{}",
+ "[]",
+ "true",
+ NULL
+ };
+ for (const char** config(configs); *config; ++config) {
+ SCOPED_TRACE(*config);
+ EXPECT_THROW(DataSourceClientContainer("static",
+ Element::fromJSON(*config)),
+ DataSourceError);
+ }
+}
+
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/faked_nsec3.cc b/src/lib/datasrc/tests/faked_nsec3.cc
index 0a1823b1be..1e37b8ebbc 100644
--- a/src/lib/datasrc/tests/faked_nsec3.cc
+++ b/src/lib/datasrc/tests/faked_nsec3.cc
@@ -130,7 +130,7 @@ performNSEC3Test(ZoneFinder &finder) {
EXPECT_THROW(finder.findNSEC3(Name("example.com"), false), OutOfZone);
EXPECT_THROW(finder.findNSEC3(Name("org"), true), OutOfZone);
- Name origin("example.org");
+ const Name origin("example.org");
const string apex_nsec3_text = string(apex_hash) + ".example.org." +
string(nsec3_common);
const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index 07d1fb9605..580f7ff5e7 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -373,18 +373,30 @@ protected:
// expected_flags is set to either RESULT_NSEC_SIGNED or
// RESULT_NSEC3_SIGNED when it's NSEC/NSEC3 signed respectively and
// find() is expected to set the corresponding flags.
+ // find_options should be set to FIND_DNSSEC for NSEC-signed case when
+ // NSEC is expected to be returned.
void findCheck(ZoneFinder::FindResultFlags expected_flags =
- ZoneFinder::RESULT_DEFAULT);
+ ZoneFinder::RESULT_DEFAULT,
+ ZoneFinder::FindOptions find_options =
+ ZoneFinder::FIND_DEFAULT);
void emptyNodeCheck(ZoneFinder::FindResultFlags expected_flags =
ZoneFinder::RESULT_DEFAULT);
void wildcardCheck(ZoneFinder::FindResultFlags expected_flags =
- ZoneFinder::RESULT_DEFAULT);
+ ZoneFinder::RESULT_DEFAULT,
+ ZoneFinder::FindOptions find_options =
+ ZoneFinder::FIND_DEFAULT);
void doCancelWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
- ZoneFinder::RESULT_DEFAULT);
+ ZoneFinder::RESULT_DEFAULT,
+ ZoneFinder::FindOptions find_options =
+ ZoneFinder::FIND_DEFAULT);
void anyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
ZoneFinder::RESULT_DEFAULT);
void emptyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
ZoneFinder::RESULT_DEFAULT);
+ void findNSECENTCheck(const Name& ent_name,
+ ConstRRsetPtr expected_nsec,
+ ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_DEFAULT);
public:
InMemoryZoneFinderTest() :
@@ -441,8 +453,23 @@ public:
{"0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM.example.org. 300 IN "
"NSEC3 1 1 12 aabbccdd 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG",
&rr_nsec3_},
- {"example.org. 300 IN NSEC cname.example.org. A NS NSEC",
- &rr_nsec_},
+ {"example.org. 300 IN NSEC wild.*.foo.example.org. "
+ "NS SOA RRSIG NSEC DNSKEY", &rr_nsec_},
+ // Together with the apex NSEC, these next NSECs make a complete
+ // chain in the case of the zone for the emptyNonterminal tests
+ // (We may want to clean up this generator code and/or masterLoad
+ // so that we can prepare conflicting datasets better)
+ {"wild.*.foo.example.org. 3600 IN NSEC ns.example.org. "
+ "A RRSIG NSEC", &rr_ent_nsec2_},
+ {"ns.example.org. 3600 IN NSEC foo.wild.example.org. A RRSIG NSEC",
+ &rr_ent_nsec3_},
+ {"foo.wild.example.org. 3600 IN NSEC example.org. A RRSIG NSEC",
+ &rr_ent_nsec4_},
+ // And these are NSECs used in different tests
+ {"ns.example.org. 300 IN NSEC *.nswild.example.org. A AAAA NSEC",
+ &rr_ns_nsec_},
+ {"*.wild.example.org. 300 IN NSEC foo.wild.example.org. A NSEC",
+ &rr_wild_nsec_},
{NULL, NULL}
};
@@ -508,6 +535,11 @@ public:
RRsetPtr rr_not_wild_another_;
RRsetPtr rr_nsec3_;
RRsetPtr rr_nsec_;
+ RRsetPtr rr_ent_nsec2_;
+ RRsetPtr rr_ent_nsec3_;
+ RRsetPtr rr_ent_nsec4_;
+ RRsetPtr rr_ns_nsec_;
+ RRsetPtr rr_wild_nsec_;
// A faked NSEC3 hash calculator for convenience.
// Tests that need to use the faked hashed values should call
@@ -650,14 +682,6 @@ public:
}
};
-/**
- * \brief Check that findPreviousName throws as it should now.
- */
-TEST_F(InMemoryZoneFinderTest, findPreviousName) {
- EXPECT_THROW(zone_finder_.findPreviousName(Name("www.example.org")),
- isc::NotImplemented);
-}
-
/**
* \brief Test InMemoryZoneFinder::InMemoryZoneFinder constructor.
*
@@ -977,7 +1001,9 @@ TEST_F(InMemoryZoneFinderTest, glue) {
* directly there, it just tells it doesn't exist.
*/
void
-InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags) {
+InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags,
+ ZoneFinder::FindOptions find_options)
+{
// Fill some data inside
// Now put all the data we have there. It should throw nothing
EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_)));
@@ -996,17 +1022,44 @@ InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags) {
findTest(rr_ns_a_->getName(), RRType::A(), ZoneFinder::SUCCESS, true,
rr_ns_a_);
- // These domain exist but don't have the provided RRType
- findTest(origin_, RRType::AAAA(), ZoneFinder::NXRRSET, true,
- ConstRRsetPtr(), expected_flags);
- findTest(rr_ns_a_->getName(), RRType::NS(), ZoneFinder::NXRRSET, true,
- ConstRRsetPtr(), expected_flags);
+ // These domains don't exist. (and one is out of the zone). In an
+ // NSEC-signed zone with DNSSEC records requested, it should return the
+ // covering NSEC for the query name (the actual NSEC in the test data may
+ // not really "cover" it, but for the purpose of this test it's okay).
+ ConstRRsetPtr expected_nsec; // by default it's NULL
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 &&
+ (find_options & ZoneFinder::FIND_DNSSEC) != 0) {
+ expected_nsec = rr_nsec_;
+ }
- // These domains don't exist (and one is out of the zone)
- findTest(Name("nothere.example.org"), RRType::A(), ZoneFinder::NXDOMAIN,
- true, ConstRRsetPtr(), expected_flags);
+ // There's no other name between this one and the origin, so when NSEC
+ // is to be returned it should be the origin NSEC.
+ findTest(Name("nothere.example.org"), RRType::A(),
+ ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+ NULL, find_options);
+
+ // The previous name in the zone is "ns.example.org", but it doesn't
+ // have an NSEC. It should be skipped and the origin NSEC will be
+ // returned as the "closest NSEC".
+ findTest(Name("nxdomain.example.org"), RRType::A(),
+ ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+ NULL, find_options);
EXPECT_THROW(zone_finder_.find(Name("example.net"), RRType::A()),
OutOfZone);
+
+ // These domain exist but don't have the provided RRType. For the latter
+ // one we now add its NSEC (which was delayed so that it wouldn't break
+ // other cases above).
+ findTest(origin_, RRType::AAAA(), ZoneFinder::NXRRSET, true,
+ expected_nsec, expected_flags, NULL, find_options);
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_nsec_));
+ if ((find_options & ZoneFinder::FIND_DNSSEC) != 0) {
+ expected_nsec = rr_ns_nsec_;
+ }
+ }
+ findTest(rr_ns_a_->getName(), RRType::NS(), ZoneFinder::NXRRSET, true,
+ expected_nsec, expected_flags, NULL, find_options);
}
TEST_F(InMemoryZoneFinderTest, find) {
@@ -1017,10 +1070,74 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3Signed) {
findCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
}
+TEST_F(InMemoryZoneFinderTest, findNSEC3SignedWithDNSSEC) {
+ // For NSEC3-signed zones, specifying the DNSSEC option shouldn't change
+ // anything (the NSEC3_SIGNED flag is always set, and no records are
+ // returned for negative cases regardless).
+ findCheck(ZoneFinder::RESULT_NSEC3_SIGNED, ZoneFinder::FIND_DNSSEC);
+}
+
TEST_F(InMemoryZoneFinderTest, findNSECSigned) {
+ // NSEC-signed zone, without requesting DNSSEC (no NSEC should be provided)
findCheck(ZoneFinder::RESULT_NSEC_SIGNED);
}
+// Generalized test for Empty Nonterminal (ENT) cases with NSEC
+void
+InMemoryZoneFinderTest::findNSECENTCheck(const Name& ent_name,
+ ConstRRsetPtr expected_nsec,
+ ZoneFinder::FindResultFlags expected_flags)
+{
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_emptywild_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_under_wild_));
+
+ // Sanity check: Should result in NXRRSET
+ findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(), expected_flags);
+ // Sanity check: No NSEC added yet
+ findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(), expected_flags,
+ NULL, ZoneFinder::FIND_DNSSEC);
+
+ // Now add the NSEC rrs making it a 'complete' zone (in terms of NSEC,
+ // there are no sigs)
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ent_nsec2_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ent_nsec3_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ent_nsec4_));
+
+ // Should result in NXRRSET, and RESULT_NSEC_SIGNED
+ findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+ ConstRRsetPtr(),
+ expected_flags | ZoneFinder::RESULT_NSEC_SIGNED);
+
+ // And check for the NSEC if DNSSEC_OK
+ findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+ expected_nsec, expected_flags | ZoneFinder::RESULT_NSEC_SIGNED,
+ NULL, ZoneFinder::FIND_DNSSEC);
+}
+
+TEST_F(InMemoryZoneFinderTest,findNSECEmptyNonterminal) {
+ // Non-wildcard case
+ findNSECENTCheck(Name("wild.example.org"), rr_ent_nsec3_);
+}
+
+TEST_F(InMemoryZoneFinderTest,findNSECEmptyNonterminalWildcard) {
+ // Wildcard case, above actual wildcard
+ findNSECENTCheck(Name("foo.example.org"), rr_nsec_);
+}
+
+TEST_F(InMemoryZoneFinderTest,findNSECEmptyNonterminalAtWildcard) {
+ // Wildcard case, at actual wildcard
+ findNSECENTCheck(Name("bar.foo.example.org"), rr_nsec_,
+ ZoneFinder::RESULT_WILDCARD);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSECSignedWithDNSSEC) {
+ // NSEC-signed zone, requesting DNSSEC (NSEC should be provided)
+ findCheck(ZoneFinder::RESULT_NSEC_SIGNED, ZoneFinder::FIND_DNSSEC);
+}
+
void
InMemoryZoneFinderTest::emptyNodeCheck(
ZoneFinder::FindResultFlags expected_flags)
@@ -1183,7 +1300,8 @@ TEST_F(InMemoryZoneFinderTest, loadFromIterator) {
*/
void
InMemoryZoneFinderTest::wildcardCheck(
- ZoneFinder::FindResultFlags expected_flags)
+ ZoneFinder::FindResultFlags expected_flags,
+ ZoneFinder::FindOptions find_options)
{
/*
* example.org.
@@ -1195,7 +1313,6 @@ InMemoryZoneFinderTest::wildcardCheck(
// If the zone is "signed" (detecting it by the NSEC/NSEC3 signed flags),
// add RRSIGs to the records.
- ZoneFinder::FindOptions find_options = ZoneFinder::FIND_DEFAULT;
if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 ||
(expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
// Convenience shortcut. The RDATA is not really validatable, but
@@ -1225,10 +1342,31 @@ InMemoryZoneFinderTest::wildcardCheck(
// be in the wildcard (so check the wildcard isn't matched at the parent)
{
SCOPED_TRACE("Search at parent");
- findTest(Name("wild.example.org"), RRType::A(), ZoneFinder::NXRRSET,
- true, ConstRRsetPtr(), expected_flags, NULL, find_options);
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ findTest(Name("wild.example.org"), RRType::A(),
+ ZoneFinder::NXRRSET, true, rr_nsec_, expected_flags,
+ NULL, find_options);
+ } else {
+ findTest(Name("wild.example.org"), RRType::A(),
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+ expected_flags, NULL, find_options);
+ }
}
+ // For the test setup of "NSEC-signed" zone, we might expect it will
+ // be returned with a negative result, either because wildcard match is
+ // disabled by the search option or because wildcard match is canceled
+ // per protocol.
+ ConstRRsetPtr expected_nsec; // by default it's NULL
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 &&
+ (find_options & ZoneFinder::FIND_DNSSEC) != 0) {
+ expected_nsec = rr_nsec_;
+ }
+ // Explicitly converting the following to const pointers; some compilers
+ // would complain about mixed use of const and non const in ?: below.
+ const ConstRRsetPtr rr_wild = rr_wild_;
+ const ConstRRsetPtr rr_cnamewild = rr_cnamewild_;
+
// Search the original name of wildcard
{
SCOPED_TRACE("Search directly at *");
@@ -1236,45 +1374,70 @@ InMemoryZoneFinderTest::wildcardCheck(
true, rr_wild_, ZoneFinder::RESULT_DEFAULT, NULL,
find_options);
}
+
+ // Below some of the test cases will normally result in a wildcard match;
+ // if NO_WILDCARD is specified, it should result in NXDOMAIN instead,
+ // and, when available and requested, the covering NSEC will be returned.
+ // The following are shortcut parameters to unify these cases.
+ const bool wild_ok = ((find_options & ZoneFinder::NO_WILDCARD) == 0);
+ const ZoneFinder::FindResultFlags wild_expected_flags =
+ wild_ok ? (ZoneFinder::RESULT_WILDCARD | expected_flags) :
+ expected_flags;
+
// Search "created" name.
{
SCOPED_TRACE("Search at created child");
- findTest(Name("a.wild.example.org"), RRType::A(), ZoneFinder::SUCCESS,
- false, rr_wild_,
- ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
- find_options, true);
- // Wildcard match, but no data
- findTest(Name("a.wild.example.org"), RRType::AAAA(),
- ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
- ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
- find_options);
+ findTest(Name("a.wild.example.org"), RRType::A(),
+ wild_ok ? ZoneFinder::SUCCESS : ZoneFinder::NXDOMAIN, false,
+ wild_ok ? rr_wild : expected_nsec,
+ wild_expected_flags, NULL, find_options, wild_ok);
}
// Search name that has CNAME.
{
SCOPED_TRACE("Matching CNAME");
findTest(Name("a.cnamewild.example.org"), RRType::A(),
- ZoneFinder::CNAME, false, rr_cnamewild_,
- ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
- find_options, true);
+ wild_ok ? ZoneFinder::CNAME : ZoneFinder::NXDOMAIN, false,
+ wild_ok ? rr_cnamewild : expected_nsec,
+ wild_expected_flags, NULL, find_options, wild_ok);
}
// Search another created name, this time little bit lower
{
SCOPED_TRACE("Search at created grand-child");
findTest(Name("a.b.wild.example.org"), RRType::A(),
- ZoneFinder::SUCCESS, false, rr_wild_,
- ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
- find_options, true);
+ wild_ok ? ZoneFinder::SUCCESS : ZoneFinder::NXDOMAIN, false,
+ wild_ok ? rr_wild : expected_nsec,
+ wild_expected_flags, NULL, find_options, wild_ok);
}
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_under_wild_));
{
SCOPED_TRACE("Search under non-wildcard");
findTest(Name("bar.foo.wild.example.org"), RRType::A(),
- ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags,
+ ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
NULL, find_options);
}
+
+ // Wildcard match, but no data. We add the additional NSEC at the wildcard
+ // at this point so that it wouldn't break other tests above. Note also
+ // that in the NO_WILDCARD case the resulting NSEC is the same. Ideally
+ // we could use a more tricky setup so we can distinguish these cases,
+ // but for this purpose it's not bad; what we'd like to test here is that
+ // wildcard substitution doesn't happen for either case, and the
+ // NO_WILDCARD effect itself can be checked by the result code (NXDOMAIN).
+ ConstRRsetPtr expected_wild_nsec; // by default it's NULL
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_nsec_));
+ expected_wild_nsec = rr_wild_nsec_;
+ }
+ {
+ SCOPED_TRACE("Search at wildcard, no data");
+ findTest(Name("a.wild.example.org"), RRType::AAAA(),
+ wild_ok ? ZoneFinder::NXRRSET : ZoneFinder::NXDOMAIN, true,
+ wild_ok ? expected_wild_nsec : expected_wild_nsec,
+ wild_expected_flags, NULL, find_options);
+ }
}
TEST_F(InMemoryZoneFinderTest, wildcard) {
@@ -1288,10 +1451,22 @@ TEST_F(InMemoryZoneFinderTest, wildcardNSEC3) {
}
TEST_F(InMemoryZoneFinderTest, wildcardNSEC) {
- // Similar to the previous one, but the zone signed with NSEC
+ // Similar to the previous one, but the zone is signed with NSEC
wildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
}
+TEST_F(InMemoryZoneFinderTest, wildcardDisabledWithNSEC) {
+ // Wildcard is disabled. In practice, this is used as part of query
+ // processing for an NSEC-signed zone, so we test that case specifically.
+ wildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED, ZoneFinder::NO_WILDCARD);
+}
+
+TEST_F(InMemoryZoneFinderTest, wildcardDisabledWithoutNSEC) {
+ // Similar to the previous once, but check the behavior for a non signed
+ // zone just in case.
+ wildcardCheck(ZoneFinder::RESULT_DEFAULT, ZoneFinder::NO_WILDCARD);
+}
+
/*
* Test that we don't match a wildcard if we get under delegation.
* By 4.3.3 of RFC1034:
@@ -1495,15 +1670,29 @@ TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
// situations
void
InMemoryZoneFinderTest::doCancelWildcardCheck(
- ZoneFinder::FindResultFlags expected_flags)
+ ZoneFinder::FindResultFlags expected_flags,
+ ZoneFinder::FindOptions find_options)
{
// These should be canceled
{
SCOPED_TRACE("Canceled under foo.wild.example.org");
+
+ // For an NSEC-signed zone with DNSSEC requested, the covering NSEC
+ // should be returned. The expected NSEC is actually just the only
+ // NSEC in the test data, but in this context it doesn't matter;
+ // it's sufficient just to check any NSEC is returned (or not).
+ ConstRRsetPtr expected_nsec; // by default it's NULL
+ if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 &&
+ (find_options & ZoneFinder::FIND_DNSSEC)) {
+ expected_nsec = rr_nsec_;
+ }
+
findTest(Name("aaa.foo.wild.example.org"), RRType::A(),
- ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+ ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+ NULL, find_options);
findTest(Name("zzz.foo.wild.example.org"), RRType::A(),
- ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+ ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+ NULL, find_options);
}
// This is existing, non-wildcard domain, shouldn't wildcard at all
@@ -1571,6 +1760,7 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcard) {
}
}
+// Same tests as cancelWildcard for NSEC3-signed zone
TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC3) {
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_));
@@ -1587,6 +1777,29 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC3) {
}
}
+// Same tests as cancelWildcard for NSEC-signed zone. Check both cases with
+// or without FIND_DNSSEC option. NSEC should be returned only when the option
+// is given.
+TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC) {
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_));
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec_));
+
+ {
+ SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+ doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED,
+ ZoneFinder::FIND_DNSSEC);
+ doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+ }
+ EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_another_));
+ {
+ SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+ doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED,
+ ZoneFinder::FIND_DNSSEC);
+ doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+ }
+}
+
TEST_F(InMemoryZoneFinderTest, loadBadWildcard) {
// We reject loading the zone if it contains a wildcard name for
// NS or DNAME.
diff --git a/src/lib/datasrc/tests/rbtree_unittest.cc b/src/lib/datasrc/tests/rbtree_unittest.cc
index b26a22bc5f..a11bff517d 100644
--- a/src/lib/datasrc/tests/rbtree_unittest.cc
+++ b/src/lib/datasrc/tests/rbtree_unittest.cc
@@ -45,8 +45,8 @@ const size_t Name::MAX_LABELS;
* c | g.h
* | |
* w.y i
- * / | \
- * x | z
+ * / | \ \
+ * x | z k
* | |
* p j
* / \
@@ -59,7 +59,7 @@ protected:
RBTreeTest() : rbtree_expose_empty_node(true), crbtnode(NULL) {
const char* const domain_names[] = {
"c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f",
- "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f"};
+ "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "k.g.h"};
int name_count = sizeof(domain_names) / sizeof(domain_names[0]);
for (int i = 0; i < name_count; ++i) {
rbtree.insert(Name(domain_names[i]), &rbtnode);
@@ -79,7 +79,7 @@ protected:
TEST_F(RBTreeTest, getNodeCount) {
- EXPECT_EQ(13, rbtree.getNodeCount());
+ EXPECT_EQ(14, rbtree.getNodeCount());
}
TEST_F(RBTreeTest, setGetData) {
@@ -91,46 +91,46 @@ TEST_F(RBTreeTest, insertNames) {
EXPECT_EQ(RBTree::ALREADYEXISTS, rbtree.insert(Name("d.e.f"),
&rbtnode));
EXPECT_EQ(Name("d.e.f"), rbtnode->getName());
- EXPECT_EQ(13, rbtree.getNodeCount());
+ EXPECT_EQ(14, rbtree.getNodeCount());
//insert not exist node
EXPECT_EQ(RBTree::SUCCESS, rbtree.insert(Name("."), &rbtnode));
EXPECT_EQ(Name("."), rbtnode->getName());
- EXPECT_EQ(14, rbtree.getNodeCount());
+ EXPECT_EQ(15, rbtree.getNodeCount());
EXPECT_EQ(RBTree