BIND 10 trac2023, updated. b5fbf8a408a047a2552e89ef435a609f5df58d8c Merge branch 'master' into trac2023

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Jun 20 04:49:24 UTC 2012


The branch, trac2023 has been updated
       via  b5fbf8a408a047a2552e89ef435a609f5df58d8c (commit)
       via  34521105b0e83da5f2c6682481330a97beb082f5 (commit)
       via  2d9a5fab35616e4bde53e1767e2ddbbcca5b3a93 (commit)
       via  e3d07f9d5f238cacb5941ead19923fa386399ff9 (commit)
       via  4c6ec1df4afb3f59a22ec4c9d1a824089aa23aa4 (commit)
       via  b1d760f18c2c638fd08e106dfb834b904a4a8e6b (commit)
       via  6bfaf8bdba3974940ffa616c698081143ad74be0 (commit)
       via  0a9a34b62af75cd20a4a1f1f14879fc10dcab633 (commit)
       via  7a9f2f5427e022aab4b46cc0ba2cdd0119099c71 (commit)
       via  9fb39bb3707eb343b7ccd31759fd37022e11fa03 (commit)
       via  782670fae6e1f59854410f78533b94520ea91f04 (commit)
       via  95a03bbefb559615f3f6e529d408b749964d390a (commit)
       via  abe48da05aa1625a942ff093058889fb7e822169 (commit)
       via  dac5152826d9bdd56252d70228880a9108255455 (commit)
       via  03f10b9d6ba493489d68e0652749b1dc0a74d492 (commit)
       via  d0dba48458ac80578538a4ca234a43ba21f1e992 (commit)
       via  5b2f4e7ba0a8c0967719cc9f3e840950ffc7048e (commit)
       via  54baeb376adb8518e7cf60cf15c3d0dbdf945d29 (commit)
       via  1f195b1c22296d159cd39de24ff32334d22a0f9a (commit)
       via  c0fa5a1b01ac6a86b96160f3b669fe3509ed0976 (commit)
       via  887ba8b3bd14b45d4eccde8f55a69d16fb6495c6 (commit)
       via  f50985476336a7a41dd30de96d7ed6d3c5313d0e (commit)
       via  af892b5382f052891aaa94f056e8c337f4f9ec06 (commit)
       via  fb7423508e86f8e938f129c9d4406ecf7ec75e35 (commit)
       via  feca3314862c72dca95f993310d56f07911ef008 (commit)
       via  32751469f3c21db28a5f922423962f2394102248 (commit)
       via  5f1c1f8ccf2e0af14706f963b4d764fd308a1bdd (commit)
       via  aab679cd7faf500112dbcab789899b45b5b63eca (commit)
       via  f9771a16778f1ecfae1eccded1db9b0b61b09038 (commit)
       via  03871e57d90245cca56b2fd438aa081f270e9e7a (commit)
       via  60e8cc24f35b0a7821bfb368b89c6bff1440756d (commit)
       via  e8d6a1ad9b75f12c4d8647b4b90a51fc3cd2abe5 (commit)
       via  1fafcf498b18734dd6529396eef317d985adde52 (commit)
       via  498eb58a8836eceea9e8233688de9863ed6b89bc (commit)
       via  7406cfc6f01f9388c5edf7793117687f340f8266 (commit)
       via  7d35486ac5c76b6a28db0967881fbb18c8900e01 (commit)
       via  92e3f4158853be65dcc1852fbd3d8443ef5d8613 (commit)
       via  dfe666442be832504586e4102da22c29d78aab60 (commit)
       via  2fad24672daed63e82dbb92234cedea9fe28ca67 (commit)
       via  38cf088a4f48930a8b4c67c6c89e8166e4573bfc (commit)
       via  97c1c1f96301a48eb6ed3fd10201e188b6cef6ba (commit)
       via  5cbaf8b762a4a90e483715c87568c1f9d1b44264 (commit)
       via  0557f306a4d0accdd5b6510466749f99073a78f5 (commit)
       via  f57a34bf713fc0f11898f7ec79eb4ea895bb3f73 (commit)
       via  333138055635992ef6e04484010d4e491edd888f (commit)
       via  5e57606033faf7a39ffa791225f5364aa0506257 (commit)
       via  464682a2180c672f1ed12d8a56fd0a5ab3eb96ed (commit)
       via  8e1c45388cdecec2658bc3d8ba69efd728f9806c (commit)
       via  9a24a3143e6eb00ca6e242cccef6b76306c96761 (commit)
       via  28603760451a1f515e68fb196ec5f3ad007edbc5 (commit)
       via  5d434e6cbe595de340b39348013acacaaccc588d (commit)
       via  479e328c6685788093ff7fdece472629f863fd39 (commit)
       via  56a203c83779dd8695712b2df56198eb5d5fad69 (commit)
       via  7dd6d4083029074dd029f2e4be1875dc477a71e1 (commit)
       via  afb888c27e574c98bdcac814bd5260d4faa599c7 (commit)
       via  4f1848c0b300fba25d8b26800fccd21b3b1506b1 (commit)
       via  0a4607b2a469aa91bc60ccfab2212eb10a96c5a2 (commit)
       via  f2cd12cb26e8e2908de811d179c48660cd000d8d (commit)
       via  ba4c6615bbe0060d847f57f1f0f269811559450a (commit)
       via  9253e25378df231b5153a0b35e7fcb025463dd7d (commit)
       via  c54635af6106f737f791558f1a7b8a415647e6bf (commit)
       via  fa2055dc9dc9da332361f402408669c3d9203374 (commit)
       via  dbc27ca22d408ac333583edfce29c7651268f8f3 (commit)
       via  ef5d56d8dbbf4a5e01d6256828d599d51fc74cf9 (commit)
       via  dcafee23dccbdf2f4b77ffc57d4a485ab4334455 (commit)
       via  a897869e543b1bc56558adccc5ca67d1d0a1aeaf (commit)
       via  35eab8559632ab2cd0f1f32cb1c2db594b050e24 (commit)
       via  fb03a64781c661a06242741378592c59f45f432f (commit)
       via  9ca86731ef187433a58f8489ff983d536a3ae005 (commit)
       via  e295b40a31dedb3671705ab44c2683624f3d541c (commit)
       via  c37cc2d18c1b4b99432d0436603631a99377a0e6 (commit)
       via  c4b20d7ad5fce1557edfb53bfab66c26e1933d20 (commit)
       via  6ad0070daba5438c84dd869afe260538bae2c55e (commit)
       via  1fff16e186c477efe4c179c034849f97af6e3f33 (commit)
       via  d84cc75031f45e6a11c06775443f860d46282567 (commit)
       via  58abcc59fb63e4f06f8b5b1406b8f1c4674fe814 (commit)
       via  7dcf7f71a999d1e20185fef0e55d4c1447aea24b (commit)
       via  bf59ff6ae4f426559e3fec86fcc4fe15ae98f485 (commit)
       via  da15f58fb83ca24cc09812dec9c02b272d730caa (commit)
       via  b66ca2c6cc969be893e40184de90e12a33602b67 (commit)
       via  80d9f838002501ee277ecdac7b583c0dfffe6aaf (commit)
       via  74b4b2f998f9512b2a326e81062da9099d1eeaf4 (commit)
       via  ff9dd7da0902101332312526c4b52d11f18650ba (commit)
       via  8e741d81178c34d688f1c9cc3fecf15ac82c5f2c (commit)
       via  7d741f7ea2ddc3ab22e37b12132b7e029e6da573 (commit)
       via  d295c9ec3b875e61d4453d69a569ffeabf132a69 (commit)
       via  74dbc765acd11e59d8a144033e9202349a03157f (commit)
       via  9040c49022e84e7b1398e84a14abd54f8d99ec8c (commit)
       via  bbeee5330ca6f6619b371b11ce8e54b705abc4ed (commit)
       via  86b4a931b5857952c7036fb3c043b59d015269b0 (commit)
       via  57449046356fda9a21f3cf2dc1e5faadaaf42d7c (commit)
       via  d96e91d14a55046b741dbc49eaa1b5fac5efc8d0 (commit)
       via  3dd46b79138e68f3054430ae9f37a38a80ba0913 (commit)
       via  9ac04cd9c7f56f2ff12344f218a1bac8ab671f14 (commit)
       via  52a26b21697e8986400ab4af46429e3d00c895c5 (commit)
       via  1ece2eadd41752ca89bc9c6932942b9c39dc91ac (commit)
       via  82d8a2a27293589bdde01e1385f3e9b05538ab4f (commit)
       via  06a3082082feea8f608272753a24a3f014c494c8 (commit)
       via  6e0c6bea6666fd57a484c2b754665b8b9b8533b0 (commit)
       via  cda7cbf264721ada0ab3c959fa3a994cd9597364 (commit)
       via  ca65e4d61722b212b7ad05ec44ecda7cdc4d105d (commit)
       via  fbb8d8ac7159dee46405864cb0c2bd32e4ece312 (commit)
       via  ee56de187f8e00673996c16fa1481714ca388239 (commit)
       via  14181ee2dbd7e5e2624dc7ffc72d8266012f92cd (commit)
       via  e8df14346ff0c45f89a7fba35a7998f8175ecd77 (commit)
       via  dd8a0ae62b2fe2b93376855f0a0a0cdbb990a998 (commit)
       via  7087a74ba48a4093179df460b29831c0349564c6 (commit)
       via  6f6c79d890e93dfed8960ae5b99275db5b94d3fc (commit)
       via  3039e3cd0ae47a6434b18423a45497b7c45e7164 (commit)
       via  61814fef6b44445aa95f568dd643dd70243a15b5 (commit)
       via  9e0c33a521c1aee0f6f553de7ddca3ebbc51837d (commit)
       via  05cfa4f1a83966748dcc8a7b5aa0287f68d5e126 (commit)
       via  4b56c8149ebf215d09c3c38a520d64b6ef8a89dc (commit)
       via  a7d656ede2059f6ea2f1124d69e963cc07c79d33 (commit)
       via  3317bd6b1575ce0114a6797c5eb727a0978770de (commit)
       via  d45ad9c3684ef7bfc9b032444377b6335d3e205a (commit)
       via  818dd92ad577ce2b5029cf8a838fab067bcab109 (commit)
       via  a66e8c4e5d8278302fecf7609b76d5aa944e723c (commit)
       via  090ccd31e4107b58dd8d65bae744e0b158f999ff (commit)
       via  07d1252a747959053f499d03e40e590c21ea0bd5 (commit)
       via  4cd359475e5426fca0393246badb07ea686e3283 (commit)
       via  3800f59cbac8ac2c3adff1387485d2bf4de0f369 (commit)
       via  8479ff275b5a09dd94c8c9311219c0385ff685f8 (commit)
       via  d6b2edc832ea90d2470e953a7b4e034c795ff303 (commit)
       via  950d0e317681530727eab631b830280e4890eb96 (commit)
       via  29ce263555c5e12a0357fd4bc8b57d5eab070610 (commit)
       via  c4c3340828ab1232f95562217ca1f98044c20273 (commit)
       via  6adafccee8a94c34a4d542a2e42fce0a861dbf40 (commit)
       via  eb9d328b4bb20f8fe04771e20b045827eb3a8667 (commit)
       via  abf827ef79067d74865bc4e6da349788e71fca7d (commit)
       via  a11c2a4ee291fe85a64c059cc94622c05de3c2fa (commit)
       via  c4906480aa8458ecee8194e501c17fc753bc0dad (commit)
       via  a64c77257ca5c110e408f846286da909e8548b9c (commit)
       via  0bb55bab34119c6aadce1221f75ec148b28401a1 (commit)
       via  933d2b6f17ab8a634f392e94747074d2515fceb0 (commit)
       via  8e4cd528b8c10c707616b0dedbb3cc61573da7a6 (commit)
       via  88cbe7028f70266945fe6b53a46e235b4c9eca83 (commit)
       via  115179c9044864a193f2b3c78f0826dd03ed8cad (commit)
       via  f661e074e4b4b09acc21ffc90155a48e64c11de4 (commit)
       via  65ffa0d23485dd6101cbf41d4cc1bd2fd89afee6 (commit)
       via  453e7ac4d32888e5aca1363048afb4ada7611279 (commit)
       via  dc2fb18aebbf5d72e7e40517c21cc43d5c7e5b0f (commit)
       via  2bedd44b4a58222083dc72a85bed101804ee7858 (commit)
       via  4ddcc3bd007c6d131b8fb6e0380550a1b8be85b4 (commit)
       via  9ede3992a795cecab1644795895f17391c22fe11 (commit)
       via  e654de7dea5adaa224dd56dc06201a9f902248d3 (commit)
       via  c6a9d7749573a23bfff82784b124f0602a29f786 (commit)
       via  739ce253a1702927a5991ff36672df92f9063e65 (commit)
       via  f1af1f3ba715af99d1fbade8eea193deabb405a5 (commit)
       via  d95ff3f155636aa484dbba780ed5264335387905 (commit)
       via  15a17147855caf469e55b6fc3d3827f496bd0025 (commit)
       via  170c72d67a741e958611e7267826679b0629286a (commit)
       via  128271198f60e6b1f045d6080ef4040760b6ec58 (commit)
       via  57e92cc0aff7f42885c0e100116ec85e9eb21dc7 (commit)
       via  1a56963435c6a771d76c4ab44814564150ba8247 (commit)
       via  aef9cc2d584e6eda16792f126a6c018e61fd5d5e (commit)
       via  6725498ecb8a8305608fd826116cfa09ac81a1bd (commit)
       via  542d531257cec2b7368d5cde0fdfb7a5595aff8f (commit)
       via  fdb3dfbe8b71bb9a4d5414f7feb3307a8fd1cda6 (commit)
       via  eae421b4814a766ff14199a72e489768287654db (commit)
       via  a9d5033669d79cf4b786d471112b312977642fe5 (commit)
      from  4286da121e15202be075acfc827bf536fb683984 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit b5fbf8a408a047a2552e89ef435a609f5df58d8c
Merge: 4286da1 3452110
Author: Mukund Sivaraman <muks at isc.org>
Date:   Wed Jun 20 10:11:10 2012 +0530

    Merge branch 'master' into trac2023

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                          |   17 +
 Makefile.am                                        |   20 +
 configure.ac                                       |   10 +
 doc/guide/Makefile.am                              |    7 +-
 doc/guide/bind10-guide.xml                         |  349 +++++++++++++-
 src/Makefile.am                                    |    5 +
 src/bin/auth/tests/Makefile.am                     |    3 +
 src/bin/bind10/bind10_messages.mes                 |    5 +
 src/bin/bind10/bind10_src.py.in                    |   10 +-
 src/bin/bind10/tests/bind10_test.py.in             |   24 +-
 src/bin/bindctl/tests/bindctl_test.py              |   30 +-
 src/bin/cmdctl/tests/cmdctl_test.py                |    5 +
 src/bin/ddns/b10-ddns.8                            |   55 ++-
 src/bin/ddns/b10-ddns.xml                          |   66 ++-
 src/bin/ddns/ddns.py.in                            |    9 +-
 src/bin/ddns/ddns_messages.mes                     |   49 +-
 src/bin/ddns/tests/ddns_test.py                    |    7 +
 src/bin/dhcp4/tests/Makefile.am                    |    3 +
 src/bin/dhcp6/tests/Makefile.am                    |    3 +
 src/bin/msgq/msgq.py.in                            |    1 +
 src/bin/msgq/tests/msgq_test.py                    |   12 +
 src/bin/resolver/tests/Makefile.am                 |    3 +
 src/bin/sockcreator/tests/Makefile.am              |    3 +
 src/bin/tests/process_rename_test.py.in            |    7 +-
 src/bin/xfrin/tests/xfrin_test.py                  |    3 +-
 src/bin/xfrout/tests/xfrout_test.py.in             |   60 ++-
 src/bin/xfrout/xfrout.py.in                        |   61 ++-
 src/bin/xfrout/xfrout_messages.mes                 |   27 +-
 src/bin/zonemgr/tests/zonemgr_test.py              |    1 +
 src/bin/zonemgr/zonemgr.py.in                      |    2 +
 src/lib/acl/tests/Makefile.am                      |    3 +
 src/lib/asiodns/tests/Makefile.am                  |    3 +
 src/lib/asiolink/tests/Makefile.am                 |    3 +
 src/lib/bench/tests/Makefile.am                    |    3 +
 src/lib/cache/tests/Makefile.am                    |    3 +
 src/lib/cc/tests/Makefile.am                       |    3 +
 src/lib/config/tests/Makefile.am                   |    3 +
 src/lib/cryptolink/tests/Makefile.am               |    3 +
 src/lib/datasrc/Makefile.am                        |    1 +
 src/lib/datasrc/client_list.cc                     |  162 +++++++
 src/lib/datasrc/client_list.h                      |  289 ++++++++++++
 src/lib/datasrc/tests/Makefile.am                  |    4 +
 src/lib/datasrc/tests/client_list_unittest.cc      |  475 ++++++++++++++++++++
 src/lib/dhcp/tests/Makefile.am                     |    3 +
 src/lib/dns/python/tests/testutil.py               |   16 +-
 src/lib/dns/tests/Makefile.am                      |    3 +
 src/lib/exceptions/tests/Makefile.am               |    3 +
 src/lib/log/tests/Makefile.am                      |    3 +
 src/lib/nsas/tests/Makefile.am                     |    3 +
 src/lib/python/isc/bind10/special_component.py     |    4 +
 src/lib/python/isc/bind10/tests/component_test.py  |   13 +-
 .../python/isc/bind10/tests/sockcreator_test.py    |    9 +
 .../python/isc/config/tests/module_spec_test.py    |   16 +-
 src/lib/python/isc/ddns/session.py                 |    7 +-
 src/lib/python/isc/ddns/tests/session_tests.py     |   19 +-
 .../isc/util/cio/tests/socketsession_test.py       |   24 +-
 src/lib/resolve/tests/Makefile.am                  |    3 +
 src/lib/server_common/tests/Makefile.am            |    3 +
 src/lib/statistics/tests/Makefile.am               |    3 +
 src/lib/util/tests/Makefile.am                     |    3 +
 src/lib/util/tests/socketsession_unittest.cc       |   12 +-
 src/lib/xfr/tests/Makefile.am                      |    3 +
 src/valgrind-suppressions                          |   11 +
 src/valgrind-suppressions.revisit                  |   17 +
 tests/lettuce/configurations/ddns/ddns.config.orig |   78 ++++
 .../lettuce/configurations/ddns/noddns.config.orig |   43 ++
 .../example.org.sqlite3.orig}                      |  Bin 15360 -> 15360 bytes
 tests/lettuce/data/example.org.sqlite3             |  Bin 15360 -> 15360 bytes
 tests/lettuce/features/ddns_system.feature         |  144 ++++++
 tests/lettuce/features/example.feature             |    3 +
 tests/lettuce/features/terrain/bind10_control.py   |   31 +-
 tests/lettuce/features/terrain/nsupdate.py         |  168 +++++++
 tests/lettuce/features/terrain/querying.py         |   10 +-
 tests/lettuce/features/terrain/terrain.py          |   14 +-
 tests/tools/badpacket/tests/Makefile.am            |    3 +
 tests/tools/perfdhcp/tests/Makefile.am             |    3 +
 76 files changed, 2324 insertions(+), 163 deletions(-)
 create mode 100644 src/lib/datasrc/client_list.cc
 create mode 100644 src/lib/datasrc/client_list.h
 create mode 100644 src/lib/datasrc/tests/client_list_unittest.cc
 create mode 100644 src/valgrind-suppressions
 create mode 100644 src/valgrind-suppressions.revisit
 create mode 100644 tests/lettuce/configurations/ddns/ddns.config.orig
 create mode 100644 tests/lettuce/configurations/ddns/noddns.config.orig
 copy tests/lettuce/data/{example.org.sqlite3 => ddns/example.org.sqlite3.orig} (100%)
 create mode 100644 tests/lettuce/features/ddns_system.feature
 create mode 100644 tests/lettuce/features/terrain/nsupdate.py

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 9408e3d..9cec414 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+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'
diff --git a/Makefile.am b/Makefile.am
index ccc5524..7024294 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; \
diff --git a/configure.ac b/configure.ac
index f5a772a..70df25d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
@@ -1292,6 +1301,7 @@ Features:
 
 Developer:
   Google Tests:  $gtest_path
+  Valgrind: $found_valgrind
   C++ Code Coverage: $USE_LCOV
   Python Code Coverage: $USE_PYCOVERAGE
   Logger checks: $enable_logger_checks
diff --git a/doc/guide/Makefile.am b/doc/guide/Makefile.am
index ffe89c9..7d90d37 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.xml b/doc/guide/bind10-guide.xml
index e5adb0c..3a73695 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -87,9 +87,10 @@
     <section>
       <title>Supported Platforms</title>
       <para>
-  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.
+  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.
@@ -127,11 +128,13 @@
       </para>
 
       <para>
-        The <command>b10-xfrin</command>, <command>b10-xfrout</command>,
-        and <command>b10-zonemgr</command> components require the
-        libpython3 library and the Python _sqlite3.so module
-        (which is included with Python).
-        The Python module needs to be built for the corresponding Python 3.
+        The <command>b10-ddns</command>, <command>b10-xfrin</command>,
+        <command>b10-xfrout</command>, and <command>b10-zonemgr</command>
+        components require the libpython3 library and the Python
+        _sqlite3.so module (which is included with Python).
+        The <command>b10-stats-httpd</command> component uses the
+        Python pyexpat.so module.
+        The Python modules need to be built for the corresponding Python 3.
       </para>
 <!-- TODO: this will change ... -->
 
@@ -196,6 +199,16 @@
 
           <listitem>
             <simpara>
+              <command>b10-ddns</command> —
+              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.
+            </simpara>
+          </listitem>
+
+          <listitem>
+            <simpara>
               <command>b10-msgq</command> —
               Message bus daemon.
               This process coordinates communication between all of the other
@@ -1831,7 +1844,6 @@ what if a NOTIFY is sent?
 
   <chapter id="xfrout">
     <title>Outbound Zone Transfers</title>
-
     <para>
       The <command>b10-xfrout</command> process is started by
       <command>bind10</command>.
@@ -1907,6 +1919,325 @@ what is XfroutClient xfr_client??
 
   </chapter>
 
+  <chapter id="ddns">
+    <title>Dynamic DNS Update</title>
+
+    <para>
+      BIND 10 supports the server side of the Dynamic DNS Update
+      (DDNS) protocol as defined in RFC 2136.
+      This service is provided by the <command>b10-ddns</command>
+      component, which is started by the <command>bind10</command>
+      process if configured so.
+    </para>
+
+    <para>
+      When the <command>b10-auth</command> authoritative DNS server
+      receives an UPDATE request, it internally forwards the request
+      to <command>b10-ddns</command>, which handles the rest of
+      request processing.
+      When the processing is completed <command>b10-ddns</command>
+      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 <command>b10-xfrout</command> so that other secondary
+      servers will be notified via the DNS notify protocol.
+      In addition, if <command>b10-auth</command> serves the updated
+      zone from its in-memory cache (as described in
+      <xref linkend="in-memory-datasource-with-sqlite3-backend" />),
+      <command>b10-ddns</command> will also
+      notify <command>b10-auth</command> so that <command>b10-auth</command>
+      will re-cache the updated zone content.
+    </para>
+
+    <para>
+      The <command>b10-ddns</command> 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 <command>b10-ddns</command>.
+      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.)
+    </para>
+
+    <para>
+      As of this writing <command>b10-ddns</command> 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><simpara>
+	  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.
+	  <!-- See Trac #2063 -->
+      </simpara></note>
+    </para>
+
+    <section>
+      <title>Enabling Dynamic Update</title>
+      <para>
+        First off, it must be made sure that a few components on which
+        <command>b10-ddns</command> depends are configured to run,
+        which are <command>b10-auth</command>
+        and <command>b10-zonemgr</command>.
+        In addition, <command>b10-xfrout</command> should also be
+        configured to run; otherwise the notification after an update
+        (see above) will fail with a timeout, suspending the DDNS
+        service while <command>b10-ddns</command> waits for the
+        response (see the description of the <ulink
+        url="bind10-messages.html#DDNS_UPDATE_NOTIFY_FAIL">DDNS_UPDATE_NOTIFY_FAIL</ulink>
+        log message for further details).
+        If BIND 10 is already configured to provide authoritative DNS
+        service they should normally be configured to run already.
+      </para>
+
+      <para>
+        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 <command>b10-ddns</command> component
+        configures itself with the data source referring to the
+        <quote>database_file</quote> configuration parameter of
+        <command>b10-auth</command>.
+        So this information must be configured correctly before starting
+        <command>b10-ddns</command>.
+
+        <note><simpara>
+            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.
+        </simpara></note>
+      </para>
+
+      <para>
+	In general, if something goes wrong regarding the dependency
+	described above, <command>b10-ddns</command> 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.
+      </para>
+
+      <para>
+        Next, to enable the DDNS service, <command>b10-ddns</command>
+        needs to be explicitly configured to run.
+        It can be done by using the <command>bindctl</command>
+        utility.  For example:
+      <screen>
+> <userinput>config add Boss/components b10-ddns</userinput>
+> <userinput>config set Boss/components/b10-ddns/address DDNS</userinput>
+> <userinput>config set Boss/components/b10-ddns/kind dispensable</userinput>
+> <userinput>config commit</userinput>
+</screen>
+      <note><simpara>
+	  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
+	  <command>b10-ddns</command> would start and work without
+	  specifying it.  But for it to shutdown gracefully this
+	  parameter should also be specified.
+      </simpara></note>
+      </para>
+    </section>
+
+    <section>
+      <title>Access Control</title>
+      <para>
+        By default <command>b10-ddns</command> 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
+        <quote>zones</quote> configuration parameter of
+        <command>b10-ddns</command>.
+        This is a list of per-zone configuration regarding DDNS.
+        Each list element consists of the following parameters:
+        <variablelist>
+          <varlistentry>
+            <term>origin</term>
+            <listitem>
+              <simpara>The zone's origin name</simpara>
+            </listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>class</term>
+            <listitem>
+              <simpara>The RR class of the zone
+                (normally <quote>IN</quote>, and in that case
+		can be omitted in configuration)</simpara>
+            </listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>update_acl</term>
+            <listitem>
+              <simpara>List of access control rules (ACL) for the zone</simpara>
+            </listitem>
+          </varlistentry>
+        </variablelist>
+        The syntax of the ACL is the same as ACLs for other
+        components.
+        Specific examples are given below.
+      </para>
+
+      <para>
+        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 <quote>example.org</quote> of RR class <quote>IN</quote>
+        from clients that send requests signed with a TSIG whose
+        key name is "key.example.org" (and refuses all others):
+      <screen>
+> <userinput>config add DDNS/zones</userinput>
+> <userinput>config set DDNS/zones[0]/origin example.org</userinput>
+> <userinput>config set DDNS/zones[0]/class IN</userinput>
+(Note: "class" can be omitted)
+> <userinput>config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "key": "key.example.org"}</userinput>
+> <userinput>config commit</userinput>
+</screen>
+      The TSIG key must be configured system wide
+      (see <xref linkend="xfrout"/>.)
+      </para>
+
+      <para>
+        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.
+      <screen>
+> <userinput>config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "from": "::1", "key": "key.example"}</userinput>
+> <userinput>config show DDNS/zones[0]/update_acl</userinput>
+DDNS/zones[0]/update_acl[0]     {"action": "ACCEPT", "key": "key.example.org"} any (modified)
+DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.example"} any (modified)
+> <userinput>config commit</userinput>
+</screen>
+      (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).
+      </para>
+
+      <note><simpara>
+          The <command>b10-ddns</command> 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.
+      </simpara></note>
+
+      <para>
+	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
+	<command>b10-ddns</command>.
+      </para>
+
+      <para>
+	Other actions than "ACCEPT", namely "REJECT" and "DROP", can be
+	used, too.
+	See <xref linkend="resolverserver"/> about their effects.
+      </para>
+
+      <para>
+        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.
+	<!-- See Trac ticket #2065 -->
+      </para>
+
+      <note><simpara>
+        Contrary to what RFC 2136 (literally) specifies,
+        <command>b10-ddns</command> 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.
+      </simpara></note>
+    </section>
+
+    <section>
+      <title>Miscellaneous Operational Issues</title>
+      <para>
+        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.
+      </para>
+
+      <para>
+        Also unlike BIND 9, it's currently not possible
+        to <quote>freeze</quote> 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.
+      </para>
+
+      <para>
+        Dynamic updates are only applicable to primary zones.
+        In order to avoid updating secondary zones via DDNS requests,
+        <command>b10-ddns</command> refers to the
+        <quote>secondary_zones</quote> configuration of
+        <command>b10-zonemgr</command>.  Zones listed in
+        <quote>secondary_zones</quote> will never be updated via DDNS
+        regardless of the update ACL configuration;
+	<command>b10-ddns</command> 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
+        <quote>secondary_zones</quote>, 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.
+      </para>
+      <para>
+        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.
+      </para>
+    </section>
+  </chapter>
+
   <chapter id="resolverserver">
     <title>Recursive Name Server</title>
 
diff --git a/src/Makefile.am b/src/Makefile.am
index ca4a702..395553c 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/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 4b6621e..84fd5fa 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 =
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index 4e7b49b..c751583 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -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.
 
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 08e16c6..b9dbc36 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -169,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).
         
@@ -208,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
@@ -1156,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
@@ -1175,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
@@ -1205,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:
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 3b80fb5..6ed7411 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.
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 1ddb916..bcfb6c5 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/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index 5fdabb4..856adf1 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/ddns/b10-ddns.8 b/src/bin/ddns/b10-ddns.8
index 131b6cc..8b369e8 100644
--- a/src/bin/ddns/b10-ddns.8
+++ b/src/bin/ddns/b10-ddns.8
@@ -1,7 +1,7 @@
 '\" t
 .\"     Title: b10-ddns
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
-.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
 .\"      Date: February 28, 2012
 .\"    Manual: BIND10
 .\"    Source: BIND10
@@ -9,6 +9,15 @@
 .\"
 .TH "B10\-DDNS" "8" "February 28, 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 '
+.\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" -----------------------------------------------------------------
 .\" disable hyphenation
@@ -33,29 +42,29 @@ 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
+updates the zone in the BIND 10 zone data source\&.
 .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 request processing\&. When the process is completed
+\fBb10\-ddns\fR
+will send a response to the client with the processing result\&. 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 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 depends on some other BIND 10 components:
+\fBb10-auth\fR(8)
+and
+\fBb10-zonemgr\fR(8)\&.
 .PP
 
 \fBb10\-ddns\fR
@@ -75,7 +84,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;
+\fIupdate_acl\fR
+is a list of 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,6 +106,8 @@ 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"
diff --git a/src/bin/ddns/b10-ddns.xml b/src/bin/ddns/b10-ddns.xml
index 15fcb1a..08bd1fa 100644
--- a/src/bin/ddns/b10-ddns.xml
+++ b/src/bin/ddns/b10-ddns.xml
@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>February 28, 2012</date>
+    <date>June 18, 2012</date>
   </refentryinfo>
 
   <refmeta>
@@ -58,23 +58,33 @@
       Normally it is started by the
       <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       boss process.
-      When the <command>b10-auth</command> DNS server receives
-      a DDNS update, <command>b10-ddns</command> updates the zone
-      in the BIND 10 zone data store.
     </para>
 
-    <note><para>
-      Currently installed is a dummy component. It does not provide
-      any functionality. It is a skeleton implementation that
-      will be expanded later.
-<!-- TODO: #1458 -->
-    </para></note>
+    <para>
+      When the <command>b10-auth</command> authoritative DNS server
+      receives an UPDATE request, it internally forwards the request
+      to <command>b10-ddns</command>, which handles the rest of
+      request processing.
+      When the processing is completed <command>b10-ddns</command>
+      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 <command>b10-auth</command> and
+      <command>b10-xfrout</command> so the new version of the zone will
+      be served, and other secondary servers will be notified via the
+      DNS notify protocol.
+    </para>
 
     <para>
       This daemon communicates with BIND 10 over a
       <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       C-Channel connection.  If this connection is not established,
       <command>b10-ddns</command> will exit.
+      The <command>b10-ddns</command> daemon also depends on some other
+      BIND 10 components (either directly or indirectly):
+      <citerefentry><refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>b10-xfrout</refentrytitle><manvolnum>8</manvolnum></citerefentry>, and
+      <citerefentry><refentrytitle>b10-zonemgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
     </para>
 
     <para>
@@ -92,13 +102,24 @@
 
       <varlistentry>
         <term>
+          <option>-h</option>,
+          <option>--help</option>
+        </term>
+        <listitem>
+          <para>
+            Print the command line arguments and exit.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <option>-v</option>,
           <option>--verbose</option>
         </term>
         <listitem>
           <para>
             This value is ignored at this moment, but is provided for
-            compatibility with the bind10 Boss process
+            compatibility with the bind10 Boss process.
           </para>
         </listitem>
       </varlistentry>
@@ -112,10 +133,18 @@
     </para>
     <para>
       <varname>zones</varname>
-      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:
+      <varname>origin</varname> is a textual domain name of the zone;
+      <varname>class</varname> (text) is the RR class of the zone;
+      <varname>update_acl</varname> 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.
     </para>
 
     <para>
@@ -145,6 +174,12 @@
         <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
+        <refentrytitle>b10-xfrout</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-zonemgr</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citetitle>BIND 10 Guide</citetitle>.
@@ -156,6 +191,7 @@
     <para>
       The <command>b10-ddns</command> daemon was first implemented
       in December 2011 for the ISC BIND 10 project.
+      The first functional version was released in June 2012.
     </para>
   </refsect1>
 </refentry><!--
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index 76ffca0..cc5b2ff 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -378,10 +378,13 @@ class DDNSServer:
         Perform any cleanup that is necessary when shutting down the server.
         Do NOT call this to initialize shutdown, use trigger_shutdown().
 
-        Currently, it only causes the ModuleCCSession to send a message that
-        this module is stopping.
         '''
+        # tell the ModuleCCSession to send a message that this module is
+        # stopping.
         self._cc.send_stopping()
+        # make sure any open socket is explicitly closed, per Python
+        # convention.
+        self._listen_socket.close()
 
     def accept(self):
         """
@@ -612,7 +615,7 @@ class DDNSServer:
         Get and process all commands sent from cfgmgr or other modules.
         This loops waiting for events until self.shutdown() has been called.
         '''
-        logger.info(DDNS_RUNNING)
+        logger.info(DDNS_STARTED)
         cc_fileno = self._cc.get_socket().fileno()
         listen_fileno = self._listen_socket.fileno()
         while not self._shutdown:
diff --git a/src/bin/ddns/ddns_messages.mes b/src/bin/ddns/ddns_messages.mes
index 61311bc..92099fd 100644
--- a/src/bin/ddns/ddns_messages.mes
+++ b/src/bin/ddns/ddns_messages.mes
@@ -153,10 +153,6 @@ case, there may not be able to do anything to fix it at the server
 side, but the administrator may want to check the general reachability
 with the client address.
 
-% DDNS_RUNNING ddns server is running and listening for updates
-The ddns process has successfully started and is now ready to receive commands
-and updates.
-
 % DDNS_SECONDARY_ZONES_UPDATE updated secondary zone list (%1 zones are listed)
 b10-ddns has successfully updated the internal copy of secondary zones
 obtained from b10-zonemgr, based on a latest update to zonemgr's
@@ -192,6 +188,10 @@ 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.
@@ -214,16 +214,31 @@ notify messages to secondary servers.
 
 % DDNS_UPDATE_NOTIFY_FAIL failed to notify %1 of updates to %2: %3
 b10-ddns has made updates to a zone based on an update request and
-tried to notify an external module of the updates, but the
-notification fails.  Severity of this effect depends on the type of
-the module.  If it's b10-xfrout, this means DNS notify messages won't
-be sent to secondary servers of the zone.  It's suboptimal, but not
-necessarily critical as the secondary servers will try to check the
-zone's status periodically.  If it's b10-auth and the notification was
-needed to have it reload the corresponding zone, it's more serious
-because b10-auth won't be able to serve the new version of the zone
-unless some explicit recovery action is taken.  So the administrator
-needs to examine this message and takes an appropriate action.  In
-either case, this notification is generally expected to succeed; so
-the fact it fails itself means there's something wrong in the BIND 10
-system, and it would be advisable to check other log messages.
+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.
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index 89a18da..b0e2a66 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -342,6 +342,10 @@ class TestDDNSServer(unittest.TestCase):
         self.__select_answer = None
         self.__select_exception = None
         self.__hook_called = False
+        # Because we overwrite the _listen_socket, close any existing
+        # socket object.
+        if self.ddns_server._listen_socket is not None:
+            self.ddns_server._listen_socket.close()
         self.ddns_server._listen_socket = FakeSocket(2)
         ddns.select.select = self.__select
 
@@ -380,6 +384,9 @@ class TestDDNSServer(unittest.TestCase):
         # Now make sure the clear_socket really works
         ddns.clear_socket()
         self.assertFalse(os.path.exists(ddns.SOCKET_FILE))
+        # Let ddns object complete any necessary cleanup (not part of the test,
+        # but for suppressing any warnings from the Python interpreter)
+        ddnss.shutdown_cleanup()
 
     def test_initial_config(self):
         # right now, the only configuration is the zone configuration, whose
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index 07de393..997aea2 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -38,6 +38,9 @@ if USE_STATIC_LINK
 AM_LDFLAGS = -static
 endif
 
+TESTS_ENVIRONMENT = \
+        libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index c1a8f3c..ab247bc 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -34,6 +34,9 @@ if USE_STATIC_LINK
 AM_LDFLAGS = -static
 endif
 
+TESTS_ENVIRONMENT = \
+        libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 333ae89..58b1d87 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -177,6 +177,7 @@ class MsgQ:
             # (note this is a catch-all, but we reraise it)
             if os.path.exists(self.socket_file):
                 os.remove(self.socket_file)
+            self.listen_socket.close()
             raise e
 
         if self.poller:
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index fe4f7d4..6dc7d1c 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -156,6 +156,12 @@ class SendNonblock(unittest.TestCase):
         except socket.error:
             pass
 
+        # Explicitly close temporary socket pair as the Python
+        # interpreter expects it.  It may not be 100% exception safe,
+        # but since this is only for tests we prefer brevity.
+        read.close()
+        write.close()
+
     def test_infinite_sendmsg(self):
         """
         Tries sending messages (and not reading them) until it either times
@@ -218,6 +224,12 @@ class SendNonblock(unittest.TestCase):
                     os.kill(queue_pid, signal.SIGTERM)
         self.terminate_check(run)
 
+        # Explicitly close temporary socket pair as the Python
+        # interpreter expects it.  It may not be 100% exception safe,
+        # but since this is only for tests we prefer brevity.
+        queue.close()
+        out.close()
+
     def test_small_sends(self):
         """
         Tests sending small data many times.
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
index 4d407bb..af3fcd3 100644
--- a/src/bin/resolver/tests/Makefile.am
+++ b/src/bin/resolver/tests/Makefile.am
@@ -14,6 +14,9 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
+TESTS_ENVIRONMENT = \
+        libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
diff --git a/src/bin/sockcreator/tests/Makefile.am b/src/bin/sockcreator/tests/Makefile.am
index ef518b5..cc5959c 100644
--- a/src/bin/sockcreator/tests/Makefile.am
+++ b/src/bin/sockcreator/tests/Makefile.am
@@ -8,6 +8,9 @@ if USE_STATIC_LINK
 AM_LDFLAGS = -static
 endif
 
+TESTS_ENVIRONMENT = \
+        libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
diff --git a/src/bin/tests/process_rename_test.py.in b/src/bin/tests/process_rename_test.py.in
index f96c023..1156b29 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 a860022..a1f4d28 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)
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index b60535c..e4fc873 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 4dd12ce..d0c54f8 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):
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index a636f78..9f674a2 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -115,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
@@ -149,9 +153,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.
 
 % 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
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 885827f..42ed679 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -112,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):
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 7af17a9..8cb616d 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -427,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
 
diff --git a/src/lib/acl/tests/Makefile.am b/src/lib/acl/tests/Makefile.am
index 6369511..6df91c7 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 95094f0..a96a3e6 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/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index eaa2173..39b098d 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
diff --git a/src/lib/bench/tests/Makefile.am b/src/lib/bench/tests/Makefile.am
index 3f8a678..8069559 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/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
index b638f55..fe33d3c 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/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
index e49b599..b891628 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
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index 2f1fc6f..1d9bad8 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/cryptolink/tests/Makefile.am b/src/lib/cryptolink/tests/Makefile.am
index fbe1bad..be2f4d6 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/Makefile.am b/src/lib/datasrc/Makefile.am
index 37efef0..9a4d733 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -35,6 +35,7 @@ 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
 
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
new file mode 100644
index 0000000..549b216
--- /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 <memory>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace datasrc {
+
+void
+ConfigurableClientList::configure(const Element& config, bool) {
+    // TODO: Implement the cache
+    // TODO: Implement recycling from the old configuration.
+    size_t i(0); // Outside of the try to be able to access it in the catch
+    try {
+        vector<DataSourceInfo> new_data_sources;
+        for (; i < config.size(); ++i) {
+            // Extract the parameters
+            const ConstElementPtr dconf(config.get(i));
+            const ConstElementPtr typeElem(dconf->get("type"));
+            if (typeElem == ConstElementPtr()) {
+                isc_throw(ConfigurationError, "Missing the type option in "
+                          "data source no " << i);
+            }
+            const string type(typeElem->stringValue());
+            ConstElementPtr paramConf(dconf->get("params"));
+            if (paramConf == ConstElementPtr()) {
+                paramConf.reset(new NullElement());
+            }
+            // TODO: Special-case the master files type.
+            // Ask the factory to create the data source for us
+            const DataSourcePair ds(this->getDataSourceClient(type,
+                                                              paramConf));
+            // And put it into the vector
+            new_data_sources.push_back(DataSourceInfo(ds.first, ds.second));
+        }
+        // If everything is OK up until now, we have the new configuration
+        // ready. So just put it there and let the old one die when we exit
+        // the scope.
+        data_sources_.swap(new_data_sources);
+    } catch (const TypeError& te) {
+        isc_throw(ConfigurationError, "Malformed configuration at data source "
+                  "no. " << i << ": " << te.what());
+    }
+}
+
+ClientList::FindResult
+ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
+                            bool) const
+{
+    // Nothing found yet.
+    //
+    // We have this class as a temporary storage, as the FindResult can't be
+    // assigned.
+    struct MutableResult {
+        MutableResult() :
+            datasrc_client(NULL),
+            matched_labels(0),
+            matched(false)
+        {}
+        DataSourceClient* datasrc_client;
+        ZoneFinderPtr finder;
+        uint8_t matched_labels;
+        bool matched;
+        operator FindResult() const {
+            // Conversion to the right result. If we return this, there was
+            // a partial match at best.
+            return (FindResult(datasrc_client, finder, false));
+        }
+    } candidate;
+
+    BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
+        // TODO: Once we have support for the caches, consider them too here
+        // somehow. This would probably get replaced by a function, that
+        // checks if there's a cache available, if it is, checks the loaded
+        // zones and zones expected to be in the real data source. If it is
+        // the cached one, provide the cached one. If it is in the external
+        // data source, use the datasource and don't provide the finder yet.
+        const DataSourceClient::FindResult result(
+            info.data_src_client_->findZone(name));
+        switch (result.code) {
+            case result::SUCCESS:
+                // If we found an exact match, we have no hope to getting
+                // a better one. Stop right here.
+
+                // TODO: In case we have only the datasource and not the finder
+                // and the need_updater parameter is true, get the zone there.
+                return (FindResult(info.data_src_client_, result.zone_finder,
+                                   true));
+            case result::PARTIALMATCH:
+                if (!want_exact_match) {
+                    // In case we have a partial match, check if it is better
+                    // than what we have. If so, replace it.
+                    //
+                    // We don't need the labels at the first partial match,
+                    // we have nothing to compare with. So we don't get it
+                    // (as a performance) and hope we will not need it at all.
+                    const uint8_t labels(candidate.matched ?
+                        result.zone_finder->getOrigin().getLabelCount() : 0);
+                    if (candidate.matched && candidate.matched_labels == 0) {
+                        // But if the hope turns out to be false, we need to
+                        // compute it for the first match anyway.
+                        candidate.matched_labels = candidate.finder->
+                            getOrigin().getLabelCount();
+                    }
+                    if (labels > candidate.matched_labels ||
+                        !candidate.matched) {
+                        // This one is strictly better. Replace it.
+                        candidate.datasrc_client = info.data_src_client_;
+                        candidate.finder = result.zone_finder;
+                        candidate.matched_labels = labels;
+                        candidate.matched = true;
+                    }
+                }
+                break;
+            default:
+                // Nothing found, nothing to do.
+                break;
+        }
+    }
+
+    // TODO: In case we have only the datasource and not the finder
+    // and the need_updater parameter is true, get the zone there.
+
+    // Return the partial match we have. In case we didn't want a partial
+    // match, this surely contains the original empty result.
+    return (candidate);
+}
+
+// NOTE: This function is not tested, it would be complicated. However, the
+// purpose of the function is to provide a very thin wrapper to be able to
+// replace the call to DataSourceClientContainer constructor in tests.
+ConfigurableClientList::DataSourcePair
+ConfigurableClientList::getDataSourceClient(const string& type,
+                                            const ConstElementPtr&
+                                            configuration)
+{
+    DataSourceClientContainerPtr
+        container(new DataSourceClientContainer(type, configuration));
+    return (DataSourcePair(&container->getInstance(), container));
+}
+
+}
+}
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
new file mode 100644
index 0000000..599dca8
--- /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 <dns/name.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace datasrc {
+
+class ZoneFinder;
+typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
+class DataSourceClient;
+typedef boost::shared_ptr<DataSourceClient> DataSourceClientPtr;
+class DataSourceClientContainer;
+typedef boost::shared_ptr<DataSourceClientContainer>
+    DataSourceClientContainerPtr;
+
+/// \brief The list of data source clients.
+///
+/// The purpose of this class is to hold several data source clients and search
+/// through them to find one containing a zone best matching a request.
+///
+/// All the data source clients should be for the same class. If you need
+/// to handle multiple classes, you need to create multiple separate lists.
+///
+/// This is an abstract base class. It is not expected we would use multiple
+/// implementation inside the servers (but it is not forbidden either), we
+/// have it to allow easy testing. It is possible to create a mock-up class
+/// instead of creating a full-blown configuration. The real implementation
+/// is the ConfigurableClientList.
+class ClientList : public boost::noncopyable {
+protected:
+    /// \brief Constructor.
+    ///
+    /// It is protected to prevent accidental creation of the abstract base
+    /// class.
+    ClientList() {}
+public:
+    /// \brief Virtual destructor
+    virtual ~ClientList() {}
+    /// \brief Structure holding the (compound) result of find.
+    ///
+    /// As this is read-only structure, we don't bother to create accessors.
+    /// Instead, all the member variables are defined as const and can be
+    /// accessed directly.
+    struct FindResult {
+        /// \brief Constructor.
+        ///
+        /// It simply fills in the member variables according to the
+        /// parameters. See the member descriptions for their meaning.
+        FindResult(DataSourceClient* dsrc_client, const ZoneFinderPtr& finder,
+                   bool exact_match) :
+            dsrc_client_(dsrc_client),
+            finder_(finder),
+            exact_match_(exact_match)
+        {}
+
+        /// \brief Negative answer constructor.
+        ///
+        /// This conscructs a result for negative answer. Both pointers are
+        /// NULL, and exact_match_ is false.
+        FindResult() :
+            dsrc_client_(NULL),
+            exact_match_(false)
+        {}
+
+        /// \brief Comparison operator.
+        ///
+        /// It is needed for tests and it might be of some use elsewhere
+        /// too.
+        bool operator ==(const FindResult& other) const {
+        return (dsrc_client_ == other.dsrc_client_ &&
+                finder_ == other.finder_ &&
+                exact_match_ == other.exact_match_);
+        }
+
+        /// \brief The found data source client.
+        ///
+        /// The client of the data source containing the best matching zone.
+        /// If no such data source exists, this is NULL pointer.
+        ///
+        /// Note that the pointer is valid only as long the ClientList which
+        /// returned the pointer is alive and was not reconfigured. The
+        /// ownership is preserved within the ClientList.
+        DataSourceClient* const dsrc_client_;
+
+        /// \brief The finder for the requested zone.
+        ///
+        /// This is the finder corresponding to the best matching zone.
+        /// This may be NULL even in case the datasrc_ is something
+        /// else, depending on the find options.
+        ///
+        /// \see find
+        const ZoneFinderPtr finder_;
+
+        /// \brief If the result is an exact match.
+        const bool exact_match_;
+    };
+
+    /// \brief Search for a zone through the data sources.
+    ///
+    /// This searches the contained data source clients for a one that best
+    /// matches the zone name.
+    ///
+    /// There are two expected usage scenarios. One is answering queries. In
+    /// this case, the zone finder is needed and the best matching superzone
+    /// of the searched name is needed. Therefore, the call would look like:
+    ///
+    /// \code FindResult result(list->find(queried_name));
+    ///   FindResult result(list->find(queried_name));
+    ///   if (result.datasrc_) {
+    ///       createTheAnswer(result.finder_);
+    ///   } else {
+    ///       createNotAuthAnswer();
+    /// } \endcode
+    ///
+    /// The other scenario is manipulating zone data (XfrOut, XfrIn, DDNS,
+    /// ...). In this case, the finder itself is not so important. However,
+    /// we need an exact match (if we want to manipulate zone data, we must
+    /// know exactly, which zone we are about to manipulate). Then the call
+    ///
+    /// \code FindResult result(list->find(zone_name, true, false));
+    ///   FindResult result(list->find(zone_name, true, false));
+    ///   if (result.datasrc_) {
+    ///       ZoneUpdaterPtr updater(result.datasrc_->getUpdater(zone_name);
+    ///       ...
+    /// } \endcode
+    ///
+    /// \param zone The name of the zone to look for.
+    /// \param want_exact_match If it is true, it returns only exact matches.
+    ///     If the best possible match is partial, a negative result is
+    ///     returned instead. It is possible the caller could check it and
+    ///     act accordingly if the result would be partial match, but with this
+    ///     set to true, the find might be actually faster under some
+    ///     circumstances.
+    /// \param want_finder If this is false, the finder_ member of FindResult
+    ///     might be NULL even if the corresponding data source is found. This
+    ///     is because of performance, in some cases the finder is a side
+    ///     result of the searching algorithm (therefore asking for it again
+    ///     would be a waste), but under other circumstances it is not, so
+    ///     providing it when it is not needed would also be wasteful.
+    ///
+    ///     Other things are never the side effect of searching, therefore the
+    ///     caller can get them explicitly (the updater, journal reader and
+    ///     iterator).
+    /// \return A FindResult describing the data source and zone with the
+    ///     longest match against the zone parameter.
+    virtual FindResult find(const dns::Name& zone,
+                            bool want_exact_match = false,
+                            bool want_finder = true) const = 0;
+};
+
+/// \brief Shared pointer to the list.
+typedef boost::shared_ptr<ClientList> ClientListPtr;
+/// \brief Shared const pointer to the list.
+typedef boost::shared_ptr<const ClientList> ConstClientListPtr;
+
+/// \Concrete implementation of the ClientList, which is constructed based on
+///     configuration.
+///
+/// This is the implementation which is expected to be used in the servers.
+/// However, it is expected most of the code will use it as the ClientList,
+/// only the creation is expected to be direct.
+///
+/// While it is possible to inherit this class, it is not expected to be
+/// inherited except for tests.
+class ConfigurableClientList : public ClientList {
+public:
+    /// \brief Exception thrown when there's an error in configuration.
+    class ConfigurationError : public Exception {
+    public:
+        ConfigurationError(const char* file, size_t line, const char* what) :
+            Exception(file, line, what)
+        {}
+    };
+
+    /// \brief Sets the configuration.
+    ///
+    /// This fills the ClientList with data source clients corresponding to the
+    /// configuration. The data source clients are newly created or recycled
+    /// from previous configuration.
+    ///
+    /// If any error is detected, an exception is thrown and the current
+    /// configuration is preserved.
+    ///
+    /// \param configuration The JSON element describing the configuration to
+    ///     use.
+    /// \param allow_cache If it is true, the 'cache' option of the
+    ///     configuration is used and some zones are cached into an In-Memory
+    ///     data source according to it. If it is false, it is ignored and
+    ///     no In-Memory data sources are created.
+    /// \throw DataSourceError if there's a problem creating a data source
+    ///     client.
+    /// \throw ConfigurationError if the configuration is invalid in some
+    ///     sense.
+    void configure(const data::Element& configuration, bool allow_cache);
+
+    /// \brief Implementation of the ClientList::find.
+    virtual FindResult find(const dns::Name& zone,
+                            bool want_exact_match = false,
+                            bool want_finder = true) const;
+
+    /// \brief This holds one data source client and corresponding information.
+    ///
+    /// \todo The content yet to be defined.
+    struct DataSourceInfo {
+        /// \brief Default constructor.
+        ///
+        /// Don't use directly. It is here so the structure can live in
+        /// a vector.
+        DataSourceInfo() :
+            data_src_client_(NULL)
+        {}
+        DataSourceInfo(DataSourceClient* data_src_client,
+                       const DataSourceClientContainerPtr& container) :
+            data_src_client_(data_src_client),
+            container_(container)
+        {}
+        DataSourceClient* data_src_client_;
+        DataSourceClientContainerPtr container_;
+    };
+
+    /// \brief The collection of data sources.
+    typedef std::vector<DataSourceInfo> DataSources;
+protected:
+    /// \brief The data sources held here.
+    ///
+    /// All our data sources are stored here. It is protected to let the
+    /// tests in. You should consider it private if you ever want to
+    /// derive this class (which is not really recommended anyway).
+    DataSources data_sources_;
+
+    /// \brief Convenience type alias.
+    ///
+    /// \see getDataSource
+    typedef std::pair<DataSourceClient*, DataSourceClientContainerPtr>
+        DataSourcePair;
+
+    /// \brief Create a data source client of given type and configuration.
+    ///
+    /// This is a thin wrapper around the DataSourceClientContainer
+    /// constructor. The function is here to make it possible for tests
+    /// to replace the DataSourceClientContainer with something else.
+    /// Also, derived classes could want to create the data source clients
+    /// in a different way, though inheriting this class is not recommended.
+    ///
+    /// The parameters are the same as of the constructor.
+    /// \return Pair containing both the data source client and the container.
+    ///     The container might be NULL in the derived class, it is
+    ///     only stored so the data source client is properly destroyed when
+    ///     not needed. However, in such case, it is the caller's
+    ///     responsibility to ensure the data source client is deleted when
+    ///     needed.
+    virtual DataSourcePair getDataSourceClient(const std::string& type,
+                                               const data::ConstElementPtr&
+                                               configuration);
+public:
+    /// \brief Access to the data source clients.
+    ///
+    /// It can be used to examine the loaded list of data sources clients
+    /// directly. It is not known if it is of any use other than testing, but
+    /// it might be, so it is just made public (there's no real reason to
+    /// hide it).
+    const DataSources& getDataSources() const { return (data_sources_); }
+};
+
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_CONTAINER_H
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index adc8e0e..e72fd71 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
@@ -59,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)
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 0000000..4fed961
--- /dev/null
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -0,0 +1,475 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/client_list.h>
+#include <datasrc/client.h>
+#include <datasrc/data_source.h>
+
+#include <dns/rrclass.h>
+
+#include <gtest/gtest.h>
+
+#include <set>
+
+using namespace isc::datasrc;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace boost;
+using namespace std;
+
+namespace {
+
+// A test data source. It pretends it has some zones.
+class MockDataSourceClient : public DataSourceClient {
+public:
+    class Finder : public ZoneFinder {
+    public:
+        Finder(const Name& origin) :
+            origin_(origin)
+        {}
+        Name getOrigin() const { return (origin_); }
+        // The rest is not to be called, so just have them
+        RRClass getClass() const {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        shared_ptr<Context> find(const Name&, const RRType&,
+                                 const FindOptions)
+        {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        shared_ptr<Context> findAll(const Name&,
+                                    vector<ConstRRsetPtr>&,
+                                    const FindOptions)
+        {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        FindNSEC3Result findNSEC3(const Name&, bool) {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+        Name findPreviousName(const Name&) const {
+            isc_throw(isc::NotImplemented, "Not implemented");
+        }
+    private:
+        Name origin_;
+    };
+    // Constructor from a list of zones.
+    MockDataSourceClient(const char* zone_names[]) {
+        for (const char** zone(zone_names); *zone; ++zone) {
+            zones.insert(Name(*zone));
+        }
+    }
+    // Constructor from configuration. The list of zones will be empty, but
+    // it will keep the configuration inside for further inspection.
+    MockDataSourceClient(const string& type,
+                         const ConstElementPtr& configuration) :
+        type_(type),
+        configuration_(configuration)
+    {}
+    virtual FindResult findZone(const Name& name) const {
+        if (zones.empty()) {
+            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
+        set<Name>::const_iterator it(zones.upper_bound(name));
+        if (it == zones.begin()) {
+            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
+        --it;
+        NameComparisonResult compar(it->compare(name));
+        const ZoneFinderPtr finder(new Finder(*it));
+        switch (compar.getRelation()) {
+            case NameComparisonResult::EQUAL:
+                return (FindResult(result::SUCCESS, finder));
+            case NameComparisonResult::SUPERDOMAIN:
+                return (FindResult(result::PARTIALMATCH, finder));
+            default:
+                return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
+    }
+    // These methods are not used. They just need to be there to have
+    // complete vtable.
+    virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+        getJournalReader(const Name&, uint32_t, uint32_t) const
+    {
+        isc_throw(isc::NotImplemented, "Not implemented");
+    }
+    const string type_;
+    const ConstElementPtr configuration_;
+private:
+    set<Name> zones;
+};
+
+
+// The test version is the same as the normal version. We, however, add
+// some methods to dig directly in the internals, for the tests.
+class TestedList : public ConfigurableClientList {
+public:
+    DataSources& getDataSources() { return (data_sources_); }
+    // Overwrite the list's method to get a data source with given type
+    // and configuration. We mock the data source and don't create the
+    // container. This is just to avoid some complexity in the tests.
+    virtual DataSourcePair getDataSourceClient(const string& type,
+                                               const ConstElementPtr&
+                                               configuration)
+    {
+        if (type == "error") {
+            isc_throw(DataSourceError, "The error data source type");
+        }
+        shared_ptr<MockDataSourceClient>
+            ds(new MockDataSourceClient(type, configuration));
+        // Make sure it is deleted when the test list is deleted.
+        to_delete_.push_back(ds);
+        return (DataSourcePair(ds.get(), DataSourceClientContainerPtr()));
+    }
+private:
+    // Hold list of data sources created internally, so they are preserved
+    // until the end of the test and then deleted.
+    vector<shared_ptr<MockDataSourceClient> > to_delete_;
+};
+
+const char* ds_zones[][3] = {
+    {
+        "example.org.",
+        "example.com.",
+        NULL
+    },
+    {
+        "sub.example.org.",
+        NULL, NULL
+    },
+    {
+        NULL, NULL, NULL
+    },
+    {
+        "sub.example.org.",
+        NULL, NULL
+    }
+};
+
+const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones));
+
+class ListTest : public ::testing::Test {
+public:
+    ListTest() :
+        // The empty list corresponds to a list with no elements inside
+        list_(new TestedList()),
+        config_elem_(Element::fromJSON("["
+            "{"
+            "   \"type\": \"test_type\","
+            "   \"cache\": \"off\","
+            "   \"params\": {}"
+            "}]"))
+    {
+        for (size_t i(0); i < ds_count; ++ i) {
+            shared_ptr<MockDataSourceClient>
+                ds(new MockDataSourceClient(ds_zones[i]));
+            ds_.push_back(ds);
+            ds_info_.push_back(ConfigurableClientList::DataSourceInfo(ds.get(),
+                DataSourceClientContainerPtr()));
+        }
+    }
+    // Check the positive result is as we expect it.
+    void positiveResult(const ClientList::FindResult& result,
+                        const shared_ptr<MockDataSourceClient>& dsrc,
+                        const Name& name, bool exact,
+                        const char* test)
+    {
+        SCOPED_TRACE(test);
+        EXPECT_EQ(dsrc.get(), result.dsrc_client_);
+        ASSERT_NE(ZoneFinderPtr(), result.finder_);
+        EXPECT_EQ(name, result.finder_->getOrigin());
+        EXPECT_EQ(exact, result.exact_match_);
+    }
+    // Configure the list with multiple data sources, according to
+    // some configuration. It uses the index as parameter, to be able to
+    // loop through the configurations.
+    void multiConfiguration(size_t index) {
+        list_->getDataSources().clear();
+        switch (index) {
+            case 2:
+                list_->getDataSources().push_back(ds_info_[2]);
+                // The ds_[2] is empty. We just check that it doesn't confuse
+                // us. Fall through to the case 0.
+            case 0:
+                list_->getDataSources().push_back(ds_info_[0]);
+                list_->getDataSources().push_back(ds_info_[1]);
+                break;
+            case 1:
+                // The other order
+                list_->getDataSources().push_back(ds_info_[1]);
+                list_->getDataSources().push_back(ds_info_[0]);
+                break;
+            case 3:
+                list_->getDataSources().push_back(ds_info_[1]);
+                list_->getDataSources().push_back(ds_info_[0]);
+                // It is the same as ds_[1], but we take from the first one.
+                // The first one to match is the correct one.
+                list_->getDataSources().push_back(ds_info_[3]);
+                break;
+            default:
+                FAIL() << "Unknown configuration index " << index;
+        }
+    }
+    void checkDS(size_t index, const string& type, const string& params) const
+    {
+        ASSERT_GT(list_->getDataSources().size(), index);
+        MockDataSourceClient* ds(dynamic_cast<MockDataSourceClient*>(
+            list_->getDataSources()[index].data_src_client_));
+
+        // Comparing with NULL does not work
+        ASSERT_NE(ds, static_cast<const MockDataSourceClient*>(NULL));
+        EXPECT_EQ(type, ds->type_);
+        EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_));
+    }
+    shared_ptr<TestedList> list_;
+    const ClientList::FindResult negativeResult_;
+    vector<shared_ptr<MockDataSourceClient> > ds_;
+    vector<ConfigurableClientList::DataSourceInfo> ds_info_;
+    const ConstElementPtr config_elem_;
+};
+
+// Test the test itself
+TEST_F(ListTest, selfTest) {
+    EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
+    EXPECT_EQ(result::PARTIALMATCH,
+              ds_[0]->findZone(Name("sub.example.org")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("org")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
+    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
+}
+
+// Test the list we create with empty configuration is, in fact, empty
+TEST_F(ListTest, emptyList) {
+    EXPECT_TRUE(list_->getDataSources().empty());
+}
+
+// Check the values returned by a find on an empty list. It should be
+// a negative answer (nothing found) no matter if we want an exact or inexact
+// match.
+TEST_F(ListTest, emptySearch) {
+    // No matter what we try, we don't get an answer.
+
+    // Note: we don't have operator<< for the result class, so we cannot use
+    // EXPECT_EQ.  Same for other similar cases.
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), false,
+                                               false));
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), false,
+                                               true));
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), true,
+                                               false));
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("example.org"), true,
+                                               true));
+}
+
+// Put a single data source inside the list and check it can find an
+// exact match if there's one.
+TEST_F(ListTest, singleDSExactMatch) {
+    list_->getDataSources().push_back(ds_info_[0]);
+    // This zone is not there
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("org."), true));
+    // But this one is, so check it.
+    positiveResult(list_->find(Name("example.org"), true), ds_[0],
+                   Name("example.org"), true, "Exact match");
+    // When asking for a sub zone of a zone there, we get nothing
+    // (we want exact match, this would be partial one)
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("sub.example.org."),
+                                               true));
+}
+
+// When asking for a partial match, we get all that the exact one, but more.
+TEST_F(ListTest, singleDSBestMatch) {
+    list_->getDataSources().push_back(ds_info_[0]);
+    // This zone is not there
+    EXPECT_TRUE(negativeResult_ == list_->find(Name("org.")));
+    // But this one is, so check it.
+    positiveResult(list_->find(Name("example.org")), ds_[0],
+                   Name("example.org"), true, "Exact match");
+    // When asking for a sub zone of a zone there, we get the parent
+    // one.
+    positiveResult(list_->find(Name("sub.example.org.")), ds_[0],
+                   Name("example.org"), false, "Subdomain match");
+}
+
+const char* const test_names[] = {
+    "Sub second",
+    "Sub first",
+    "With empty",
+    "With a duplicity"
+};
+
+TEST_F(ListTest, multiExactMatch) {
+    // Run through all the multi-configurations
+    for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) {
+        SCOPED_TRACE(test_names[i]);
+        multiConfiguration(i);
+        // Something that is nowhere there
+        EXPECT_TRUE(negativeResult_ == list_->find(Name("org."), true));
+        // This one is there exactly.
+        positiveResult(list_->find(Name("example.org"), true), ds_[0],
+                       Name("example.org"), true, "Exact match");
+        // This one too, but in a different data source.
+        positiveResult(list_->find(Name("sub.example.org."), true), ds_[1],
+                       Name("sub.example.org"), true, "Subdomain match");
+        // But this one is in neither data source.
+        EXPECT_TRUE(negativeResult_ ==
+                    list_->find(Name("sub.example.com."), true));
+    }
+}
+
+TEST_F(ListTest, multiBestMatch) {
+    // Run through all the multi-configurations
+    for (size_t i(0); i < 4; ++ i) {
+        SCOPED_TRACE(test_names[i]);
+        multiConfiguration(i);
+        // Something that is nowhere there
+        EXPECT_TRUE(negativeResult_ == list_->find(Name("org.")));
+        // This one is there exactly.
+        positiveResult(list_->find(Name("example.org")), ds_[0],
+                       Name("example.org"), true, "Exact match");
+        // This one too, but in a different data source.
+        positiveResult(list_->find(Name("sub.example.org.")), ds_[1],
+                       Name("sub.example.org"), true, "Subdomain match");
+        // But this one is in neither data source. But it is a subdomain
+        // of one of the zones in the first data source.
+        positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
+                       Name("example.com."), false, "Subdomain in com");
+    }
+}
+
+// Check the configuration is empty when the list is empty
+TEST_F(ListTest, configureEmpty) {
+    ConstElementPtr elem(new ListElement);
+    list_->configure(*elem, true);
+    EXPECT_TRUE(list_->getDataSources().empty());
+}
+
+// Check we can get multiple data sources and they are in the right order.
+TEST_F(ListTest, configureMulti) {
+    ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"type1\","
+        "   \"cache\": \"off\","
+        "   \"params\": {}"
+        "},"
+        "{"
+        "   \"type\": \"type2\","
+        "   \"cache\": \"off\","
+        "   \"params\": {}"
+        "}]"
+    ));
+    list_->configure(*elem, true);
+    EXPECT_EQ(2, list_->getDataSources().size());
+    checkDS(0, "type1", "{}");
+    checkDS(1, "type2", "{}");
+}
+
+// Check we can pass whatever we want to the params
+TEST_F(ListTest, configureParams) {
+    const char* params[] = {
+        "true",
+        "false",
+        "null",
+        "\"hello\"",
+        "42",
+        "[]",
+        "{}",
+        NULL
+    };
+    for (const char** param(params); *param; ++param) {
+        SCOPED_TRACE(*param);
+        ConstElementPtr elem(Element::fromJSON(string("["
+            "{"
+            "   \"type\": \"t\","
+            "   \"cache\": \"off\","
+            "   \"params\": ") + *param +
+            "}]"));
+        list_->configure(*elem, true);
+        EXPECT_EQ(1, list_->getDataSources().size());
+        checkDS(0, "t", *param);
+    }
+}
+
+TEST_F(ListTest, wrongConfig) {
+    const char* configs[] = {
+        // A lot of stuff missing from there
+        "[{\"type\": \"test_type\", \"params\": 13}, {}]",
+        // Some bad types completely
+        "{}",
+        "true",
+        "42",
+        "null",
+        "[{\"type\": \"test_type\", \"params\": 13}, true]",
+        "[{\"type\": \"test_type\", \"params\": 13}, []]",
+        "[{\"type\": \"test_type\", \"params\": 13}, 42]",
+        // Bad type of type
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": 42}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": true}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]",
+        // TODO: Once cache is supported, add some invalid cache values
+        NULL
+    };
+    // Put something inside to see it survives the exception
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+    for (const char** config(configs); *config; ++config) {
+        SCOPED_TRACE(*config);
+        ConstElementPtr elem(Element::fromJSON(*config));
+        EXPECT_THROW(list_->configure(*elem, true),
+                     ConfigurableClientList::ConfigurationError);
+        // Still untouched
+        checkDS(0, "test_type", "{}");
+        EXPECT_EQ(1, list_->getDataSources().size());
+    }
+}
+
+// The param thing defaults to null. Cache is not used yet.
+TEST_F(ListTest, defaults) {
+    ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"type1\""
+        "}]"));
+    list_->configure(*elem, true);
+    EXPECT_EQ(1, list_->getDataSources().size());
+    checkDS(0, "type1", "null");
+}
+
+// Check we can call the configure multiple times, to change the configuration
+TEST_F(ListTest, reconfigure) {
+    ConstElementPtr empty(new ListElement);
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+    list_->configure(*empty, true);
+    EXPECT_TRUE(list_->getDataSources().empty());
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+}
+
+// Make sure the data source error exception from the factory is propagated
+TEST_F(ListTest, dataSrcError) {
+    ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"error\""
+        "}]"));
+    list_->configure(*config_elem_, true);
+    checkDS(0, "test_type", "{}");
+    EXPECT_THROW(list_->configure(*elem, true), DataSourceError);
+    checkDS(0, "test_type", "{}");
+}
+
+}
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 4c86ee7..13e8458 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -19,6 +19,9 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
+TESTS_ENVIRONMENT = \
+	libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += libdhcp++_unittests
diff --git a/src/lib/dns/python/tests/testutil.py b/src/lib/dns/python/tests/testutil.py
index 679f827..6a1397f 100644
--- a/src/lib/dns/python/tests/testutil.py
+++ b/src/lib/dns/python/tests/testutil.py
@@ -28,14 +28,14 @@ def read_wire_data(filename):
     data = bytes()
     for path in testdata_path.split(":"):
         try:
-            file = open(path + os.sep + filename, "r")
-            for line in file:
-                line = line.strip()
-                if line == "" or line.startswith("#"):
-                    pass
-                else:
-                    cur_data = bytes.fromhex(line)
-                    data += cur_data
+            with open(path + os.sep + filename, "r") as f:
+                for line in f:
+                    line = line.strip()
+                    if line == "" or line.startswith("#"):
+                        pass
+                    else:
+                        cur_data = bytes.fromhex(line)
+                        data += cur_data
 
             return data
         except IOError:
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 6057828..208c7ef 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -14,6 +14,9 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
+TESTS_ENVIRONMENT = \
+	libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
diff --git a/src/lib/exceptions/tests/Makefile.am b/src/lib/exceptions/tests/Makefile.am
index 2444b02..28af100 100644
--- a/src/lib/exceptions/tests/Makefile.am
+++ b/src/lib/exceptions/tests/Makefile.am
@@ -9,6 +9,9 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
+TESTS_ENVIRONMENT = \
+	libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 9742037..615f64b 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -47,6 +47,9 @@ logger_lock_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
 logger_lock_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 logger_lock_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
 
+TESTS_ENVIRONMENT = \
+	libtool --mode=execute $(VALGRIND_COMMAND)
+
 if HAVE_GTEST
 TESTS =
 
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index afd91f6..3557c82 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -25,6 +25,9 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
+TESTS_ENVIRONMENT = \
+	libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index ebdc07f..688ccf5 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -37,6 +37,7 @@ class SockCreator(BaseComponent):
         BaseComponent.__init__(self, boss, kind)
         self.__creator = None
         self.__uid = boss.uid
+        self.__gid = boss.gid
 
     def _start_internal(self):
         self._boss.curproc = 'b10-sockcreator'
@@ -45,6 +46,9 @@ class SockCreator(BaseComponent):
         self._boss.register_process(self.pid(), self)
         self._boss.set_creator(self.__creator)
         self._boss.log_started(self.pid())
+        if self.__gid is not None:
+            logger.info(BIND10_SETGID, self.__gid)
+            posix.setgid(self.__gid)
         if self.__uid is not None:
             logger.info(BIND10_SETUID, self.__uid)
             posix.setuid(self.__uid)
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index ec0e8af..af529f8 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -104,6 +104,8 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.__stop_process_params = None
         self.__start_simple_params = None
         # Pretending to be boss
+        self.gid = None
+        self.__gid_set = None
         self.uid = None
         self.__uid_set = None
 
@@ -609,6 +611,9 @@ class ComponentTests(BossUtils, unittest.TestCase):
         self.assertTrue(process.killed)
         self.assertFalse(process.terminated)
 
+    def setgid(self, gid):
+        self.__gid_set = gid
+
     def setuid(self, uid):
         self.__uid_set = uid
 
@@ -637,7 +642,9 @@ class ComponentTests(BossUtils, unittest.TestCase):
         """
         component = isc.bind10.special_component.SockCreator(None, self,
                                                              'needed', None)
+        orig_setgid = isc.bind10.special_component.posix.setgid
         orig_setuid = isc.bind10.special_component.posix.setuid
+        isc.bind10.special_component.posix.setgid = self.setgid
         isc.bind10.special_component.posix.setuid = self.setuid
         orig_creator = \
             isc.bind10.special_component.isc.bind10.sockcreator.Creator
@@ -645,18 +652,22 @@ class ComponentTests(BossUtils, unittest.TestCase):
         isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
             lambda path: self.FakeCreator()
         component.start()
-        # No uid set in boss, nothing called.
+        # No gid/uid set in boss, nothing called.
+        self.assertIsNone(self.__gid_set)
         self.assertIsNone(self.__uid_set)
         # Doesn't do anything, but doesn't crash
         component.stop()
         component.kill()
         component.kill(True)
+        self.gid = 4200
         self.uid = 42
         component = isc.bind10.special_component.SockCreator(None, self,
                                                              'needed', None)
         component.start()
         # This time, it get's called
+        self.assertEqual(4200, self.__gid_set)
         self.assertEqual(42, self.__uid_set)
+        isc.bind10.special_component.posix.setgid = orig_setgid
         isc.bind10.special_component.posix.setuid = orig_setuid
         isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
             orig_creator
diff --git a/src/lib/python/isc/bind10/tests/sockcreator_test.py b/src/lib/python/isc/bind10/tests/sockcreator_test.py
index d97d21b..f67781c 100644
--- a/src/lib/python/isc/bind10/tests/sockcreator_test.py
+++ b/src/lib/python/isc/bind10/tests/sockcreator_test.py
@@ -303,6 +303,7 @@ class WrapTests(unittest.TestCase):
 
         # Transfer the descriptor
         send_fd(t1.fileno(), p1.fileno())
+        p1.close()
         p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
 
         # Now, pass some data trough the socket
@@ -318,6 +319,14 @@ class WrapTests(unittest.TestCase):
         data = t1.recv(1)
         self.assertEqual(b'C', data)
 
+        # Explicitly close temporary socket pair as the Python
+        # interpreter expects it.  It may not be 100% exception safe,
+        # but since this is only for tests we prefer brevity.
+        p1.close()
+        p2.close()
+        t1.close()
+        t2.close()
+
 if __name__ == '__main__':
     isc.log.init("bind10") # FIXME Should this be needed?
     isc.log.resetUnitTestRootLogger()
diff --git a/src/lib/python/isc/config/tests/module_spec_test.py b/src/lib/python/isc/config/tests/module_spec_test.py
index fc53d23..bb2bcda 100644
--- a/src/lib/python/isc/config/tests/module_spec_test.py
+++ b/src/lib/python/isc/config/tests/module_spec_test.py
@@ -46,8 +46,8 @@ class TestModuleSpec(unittest.TestCase):
         self.spec1(dd)
 
     def test_open_file_obj(self):
-        file1 = open(self.spec_file("spec1.spec"))
-        dd = isc.config.module_spec_from_file(file1)
+        with open(self.spec_file("spec1.spec")) as file1:
+            dd = isc.config.module_spec_from_file(file1)
         self.spec1(dd)
 
     def test_open_bad_file_obj(self):
@@ -89,8 +89,8 @@ class TestModuleSpec(unittest.TestCase):
 
     def validate_data(self, specfile_name, datafile_name):
         dd = self.read_spec_file(specfile_name);
-        data_file = open(self.spec_file(datafile_name))
-        data_str = data_file.read()
+        with open(self.spec_file(datafile_name)) as data_file:
+            data_str = data_file.read()
         data = isc.cc.data.parse_value_str(data_str)
         return dd.validate_config(True, data)
         
@@ -109,8 +109,8 @@ class TestModuleSpec(unittest.TestCase):
 
     def validate_command_params(self, specfile_name, datafile_name, cmd_name):
         dd = self.read_spec_file(specfile_name);
-        data_file = open(self.spec_file(datafile_name))
-        data_str = data_file.read()
+        with open(self.spec_file(datafile_name)) as data_file:
+            data_str = data_file.read()
         params = isc.cc.data.parse_value_str(data_str)
         return dd.validate_command(cmd_name, params)
 
@@ -131,8 +131,8 @@ class TestModuleSpec(unittest.TestCase):
     def test_statistics_validation(self):
         def _validate_stat(specfile_name, datafile_name):
             dd = self.read_spec_file(specfile_name);
-            data_file = open(self.spec_file(datafile_name))
-            data_str = data_file.read()
+            with open(self.spec_file(datafile_name)) as data_file:
+                data_str = data_file.read()
             data = isc.cc.data.parse_value_str(data_str)
             return dd.validate_statistics(True, data, [])
         self.assertFalse(self.read_spec_file("spec1.spec").validate_statistics(True, None, None));
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
index 04030c3..366bc8b 100644
--- a/src/lib/python/isc/ddns/session.py
+++ b/src/lib/python/isc/ddns/session.py
@@ -242,12 +242,17 @@ class UpdateSession:
         '''
         try:
             self._get_update_zone()
+            # Contrary to what RFC2136 specifies, we do ACL checks before
+            # prerequisites. It's now generally considered to be a bad
+            # idea, and actually does harm such as information
+            # leak. It should make more sense to prevent any security issues
+            # by performing ACL check as early as possible.
+            self.__check_update_acl(self.__zname, self.__zclass)
             self._create_diff()
             prereq_result = self.__check_prerequisites()
             if prereq_result != Rcode.NOERROR():
                 self.__make_response(prereq_result)
                 return UPDATE_ERROR, self.__zname, self.__zclass
-            self.__check_update_acl(self.__zname, self.__zclass)
             update_result = self.__do_update()
             if update_result != Rcode.NOERROR():
                 self.__make_response(update_result)
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index 0239bb1..f7c2d3c 100644
--- a/src/lib/python/isc/ddns/tests/session_tests.py
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -657,12 +657,12 @@ class SessionTest(SessionTestBase):
         self.assertEqual(str(expected_soa),
                          str(session._UpdateSession__added_soa))
 
-    def check_full_handle_result(self, expected, updates):
+    def check_full_handle_result(self, expected, updates, prerequisites=[]):
         '''Helper method for checking the result of a full handle;
            creates an update session, and fills it with the list of rrsets
            from 'updates'. Then checks if __handle()
            results in a response with rcode 'expected'.'''
-        msg = create_update_msg([TEST_ZONE_RECORD], [], updates)
+        msg = create_update_msg([TEST_ZONE_RECORD], prerequisites, updates)
         zconfig = ZoneConfig(set(), TEST_RRCLASS, self._datasrc_client,
                              self._acl_map)
         session = UpdateSession(msg, TEST_CLIENT4, zconfig)
@@ -902,6 +902,21 @@ class SessionTest(SessionTestBase):
                                 [ b'\x00\x0a\x04mail\x07example\x03org\x00' ])
         self.rrset_update_del_rrset_mx = rrset_update_del_rrset_mx
 
+    def test_acl_before_prereq(self):
+        name_in_use_no = create_rrset("foo.example.org", RRClass.ANY(),
+                                      RRType.ANY(), 0)
+
+        # Test a prerequisite that would fail
+        self.check_full_handle_result(Rcode.NXDOMAIN(), [], [ name_in_use_no ])
+
+        # Change ACL so that it would be denied
+        self._acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
+                             REQUEST_LOADER.load([{"action": "REJECT"}])}
+
+        # The prerequisite should now not be reached; it should fail on the
+        # ACL
+        self.check_full_handle_result(Rcode.REFUSED(), [], [ name_in_use_no ])
+
     def test_prescan(self):
         '''Test whether the prescan succeeds on data that is ok, and whether
            if notices the SOA if present'''
diff --git a/src/lib/python/isc/util/cio/tests/socketsession_test.py b/src/lib/python/isc/util/cio/tests/socketsession_test.py
index 66b43d5..d492f6d 100644
--- a/src/lib/python/isc/util/cio/tests/socketsession_test.py
+++ b/src/lib/python/isc/util/cio/tests/socketsession_test.py
@@ -22,6 +22,8 @@ TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
 TEST_UNIX_FILE = TESTDATA_OBJDIR + '/ssessiontest.unix'
 TEST_DATA = b'BIND10 test'
 TEST_PORT = 53535
+TEST_PORT2 = 53536
+TEST_PORT3 = 53537
 
 class TestForwarder(unittest.TestCase):
     '''In general, this is a straightforward port of the C++ counterpart.
@@ -31,12 +33,15 @@ class TestForwarder(unittest.TestCase):
     '''
 
     def setUp(self):
+        self.listen_sock = None
         self.forwarder = SocketSessionForwarder(TEST_UNIX_FILE)
         if os.path.exists(TEST_UNIX_FILE):
             os.unlink(TEST_UNIX_FILE)
         self.large_text = b'a' * 65535
 
     def tearDown(self):
+        if self.listen_sock is not None:
+            self.listen_sock.close()
         if os.path.exists(TEST_UNIX_FILE):
             os.unlink(TEST_UNIX_FILE)
 
@@ -172,15 +177,22 @@ class TestForwarder(unittest.TestCase):
             sock.settimeout(10)
             self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0])
         else:
-            server_sock.close()
             self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA))
             client_sock.setblocking(True)
             client_sock.settimeout(10)
             self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA)))
+            server_sock.close()
+            client_sock.close()
+
+        passed_sock.close()
+        sock.close()
 
     def test_push_and_pop(self):
-        # This is a straightforward port of C++ pushAndPop test.
+        # This is a straightforward port of C++ pushAndPop test.  See the
+        # C++ version why we use multiple ports for "local".
         local6 = ('::1', TEST_PORT, 0, 0)
+        local6_alt = ('::1', TEST_PORT2, 0, 0)
+        local6_alt2 = ('::1', TEST_PORT3, 0, 0)
         remote6 = ('2001:db8::1', 5300, 0, 0)
         self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
                                 local6, remote6, TEST_DATA, True)
@@ -188,6 +200,7 @@ class TestForwarder(unittest.TestCase):
                                 local6, remote6, TEST_DATA, False)
 
         local4 = ('127.0.0.1', TEST_PORT)
+        local4_alt = ('127.0.0.1', TEST_PORT2)
         remote4 = ('192.0.2.2', 5300)
         self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
                                 local4, remote4, TEST_DATA, False)
@@ -195,11 +208,11 @@ class TestForwarder(unittest.TestCase):
                                 local4, remote4, TEST_DATA, False)
 
         self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
-                                local6, remote6, self.large_text, False)
+                                local6_alt, remote6, self.large_text, False)
         self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
                                 local6, remote6, self.large_text, False)
         self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
-                                local4, remote4, self.large_text, False)
+                                local4_alt, remote4, self.large_text, False)
         self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
                                 local4, remote4, self.large_text, False)
 
@@ -207,7 +220,7 @@ class TestForwarder(unittest.TestCase):
         # scope (zone) ID
         scope6 = ('fe80::1', TEST_PORT, 0, 1)
         self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
-                                local6, scope6, TEST_DATA, False)
+                                local6_alt2, scope6, TEST_DATA, False)
 
     def test_push_too_fast(self):
         # A straightforward port of C++ pushTooFast test.
@@ -235,6 +248,7 @@ class TestForwarder(unittest.TestCase):
         receiver = SocketSessionReceiver(accept_sock)
         s.close()
         self.assertRaises(SocketSessionError, receiver.pop)
+        accept_sock.close()
 
 class TestReceiver(unittest.TestCase):
     # We only check a couple of failure cases on construction.  Valid cases
diff --git a/src/lib/resolve/tests/Makefile.am b/src/lib/resolve/tests/Makefile.am
index e7c59f4..4ee3811 100644
--- a/src/lib/resolve/tests/Makefile.am
+++ b/src/lib/resolve/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/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index b059d47..366c68e 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/src/lib/server_common/tests/Makefile.am
@@ -22,6 +22,9 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
+TESTS_ENVIRONMENT = \
+        libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
diff --git a/src/lib/statistics/tests/Makefile.am b/src/lib/statistics/tests/Makefile.am
index d66acdf..c0f0295 100644
--- a/src/lib/statistics/tests/Makefile.am
+++ b/src/lib/statistics/tests/Makefile.am
@@ -15,6 +15,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
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index cf1e5a5..a7811d5 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -15,6 +15,9 @@ endif
 
 CLEANFILES = *.gcno *.gcda
 
+TESTS_ENVIRONMENT = \
+        libtool --mode=execute $(VALGRIND_COMMAND)
+
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
diff --git a/src/lib/util/tests/socketsession_unittest.cc b/src/lib/util/tests/socketsession_unittest.cc
index b9f2667..e83c140 100644
--- a/src/lib/util/tests/socketsession_unittest.cc
+++ b/src/lib/util/tests/socketsession_unittest.cc
@@ -53,6 +53,7 @@ namespace {
 
 const char* const TEST_UNIX_FILE = TEST_DATA_TOPBUILDDIR "/test.unix";
 const char* const TEST_PORT = "53535";
+const char* const TEST_PORT2 = "53536"; // use this in case we need 2 ports
 const char TEST_DATA[] = "BIND10 test";
 
 // A simple helper structure to automatically close test sockets on return
@@ -540,8 +541,12 @@ ForwardTest::checkPushAndPop(int family, int type, int protocol,
 }
 
 TEST_F(ForwardTest, pushAndPop) {
-    // Pass a UDP/IPv6 session.
+    // Pass a UDP/IPv6 session.  We use different ports for different UDP
+    // tests because Solaris 11 seems to prohibit reusing the same port for
+    // some short period once the socket FD is forwarded, even if the sockets
+    // are closed.  See Trac #2028.
     const SockAddrInfo sai_local6(getSockAddr("::1", TEST_PORT));
+    const SockAddrInfo sai_local6_alt(getSockAddr("::1", TEST_PORT2));
     const SockAddrInfo sai_remote6(getSockAddr("2001:db8::1", "5300"));
     {
         SCOPED_TRACE("Passing UDP/IPv6 session");
@@ -559,6 +564,7 @@ TEST_F(ForwardTest, pushAndPop) {
     // receiver, which should be usable for multiple attempts of passing,
     // regardless of family of the passed session
     const SockAddrInfo sai_local4(getSockAddr("127.0.0.1", TEST_PORT));
+    const SockAddrInfo sai_local4_alt(getSockAddr("127.0.0.1", TEST_PORT2));
     const SockAddrInfo sai_remote4(getSockAddr("192.0.2.2", "5300"));
     {
         SCOPED_TRACE("Passing UDP/IPv4 session");
@@ -575,7 +581,7 @@ TEST_F(ForwardTest, pushAndPop) {
     // Also try large data
     {
         SCOPED_TRACE("Passing UDP/IPv6 session with large data");
-        checkPushAndPop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, sai_local6,
+        checkPushAndPop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, sai_local6_alt,
                         sai_remote6, large_text_.c_str(), large_text_.length(),
                         false);
     }
@@ -587,7 +593,7 @@ TEST_F(ForwardTest, pushAndPop) {
     }
     {
         SCOPED_TRACE("Passing UDP/IPv4 session with large data");
-        checkPushAndPop(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local4,
+        checkPushAndPop(AF_INET, SOCK_DGRAM, IPPROTO_UDP, sai_local4_alt,
                         sai_remote4, large_text_.c_str(), large_text_.length(),
                         false);
     }
diff --git a/src/lib/xfr/tests/Makefile.am b/src/lib/xfr/tests/Makefile.am
index 4abb456..d8f3933 100644
--- a/src/lib/xfr/tests/Makefile.am
+++ b/src/lib/xfr/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/valgrind-suppressions b/src/valgrind-suppressions
new file mode 100644
index 0000000..06257e5
--- /dev/null
+++ b/src/valgrind-suppressions
@@ -0,0 +1,11 @@
+# Valgrind suppressions file. Place permanent suppressions that we never
+# want to reconsider again into this file. For temporary suppressions
+# that we want to revisit in the future, use
+# valgrind-suppressions.revisit.
+#
+# Don't add any "obj:" lines in suppressions as these are likely
+# site-specific. Use "..." instead to match these. Look at the other
+# suppressions as examples.
+#
+# In case you want to make sense of the following symbols, demangle them
+# with a command like: c++filt < valgrind-suppressions
diff --git a/src/valgrind-suppressions.revisit b/src/valgrind-suppressions.revisit
new file mode 100644
index 0000000..8b4a8c7
--- /dev/null
+++ b/src/valgrind-suppressions.revisit
@@ -0,0 +1,17 @@
+# Place temporary suppressions that we want to revisit in the future
+# into this file. For permanent suppressions that we don't want to look
+# at again, use valgrind-suppressions.
+#
+# Don't add any "obj:" lines in suppressions as these are likely
+# site-specific. Use "..." instead to match these. Look at the other
+# suppressions as examples.
+#
+# In case you want to make sense of the following symbols, demangle them
+# with a command like: c++filt < valgrind-suppressions.revisit
+
+############################################################################
+#### beginning of suppressions for existing issues that we want to fix. ####
+
+
+####### end of suppressions for existing issues that we want to fix. #######
+############################################################################
diff --git a/tests/lettuce/configurations/ddns/ddns.config.orig b/tests/lettuce/configurations/ddns/ddns.config.orig
new file mode 100644
index 0000000..80b92f7
--- /dev/null
+++ b/tests/lettuce/configurations/ddns/ddns.config.orig
@@ -0,0 +1,78 @@
+{
+    "version": 2,
+    "Logging": {
+        "loggers": [
+            {
+                "debuglevel": 99,
+                "severity": "DEBUG",
+                "name": "*"
+            }
+        ]
+    },
+    "Zonemgr": {
+        "secondary_zones": [
+            {
+                "class": "IN",
+                "name": "secondary.org"
+            }
+        ]
+    },
+    "Auth": {
+        "database_file": "data/ddns/example.org.sqlite3",
+        "listen_on": [
+            {
+                "port": 47806,
+                "address":
+                "127.0.0.1"
+            }
+        ]
+    },
+    "Boss": {
+        "components": {
+            "b10-xfrout": {
+                "kind": "dispensable",
+                "address": "Xfrout"
+            },
+            "b10-zonemgr": {
+                "kind": "dispensable",
+                "address": "ZoneMgr"
+            },
+            "b10-ddns": {
+                "kind": "dispensable",
+                "address": "DDNS"
+            },
+            "b10-auth": {
+                "kind": "needed",
+                "special": "auth"
+            },
+            "b10-cmdctl": {
+                "kind": "needed",
+                "special": "cmdctl"
+            }
+        }
+    },
+    "DDNS": {
+        "zones": [
+            {
+                "origin": "example.org.",
+                "update_acl": [
+                    {
+                        "action": "ACCEPT",
+                        "from": "127.0.0.1"
+                    }
+                ],
+                "class": "IN"
+            },
+            {
+                "origin": "secondary.org.",
+                "update_acl": [
+                    {
+                        "action": "ACCEPT",
+                        "from": "127.0.0.1"
+                    }
+                ],
+                "class": "IN"
+            }
+        ]
+    }
+}
diff --git a/tests/lettuce/configurations/ddns/noddns.config.orig b/tests/lettuce/configurations/ddns/noddns.config.orig
new file mode 100644
index 0000000..bf89537
--- /dev/null
+++ b/tests/lettuce/configurations/ddns/noddns.config.orig
@@ -0,0 +1,43 @@
+{
+    "version": 2,
+    "Logging": {
+        "loggers": [
+            {
+                "severity": "DEBUG",
+                "name": "*",
+                "debuglevel": 99
+            }
+        ]
+    },
+    "DDNS": {"zones": []},
+    "Auth": {
+        "database_file": "data/ddns/example.org.sqlite3",
+        "listen_on": [
+            {
+                "port": 47806,
+                "address": "127.0.0.1"
+            }
+        ],
+        "datasources": [
+            {
+                "type": "memory",
+                "class": "IN",
+                "zones": [
+                    {
+                        "origin": "example.org",
+                        "filetype": "sqlite3",
+                        "file": "data/ddns/example.org.sqlite3"
+                    }
+                ]
+            }
+        ]
+    },
+    "Boss": {
+        "components": {
+            "b10-xfrout": {"kind": "dispensable"},
+            "b10-auth": {"kind": "needed", "special": "auth"},
+            "b10-zonemgr": {"kind": "dispensable", "address": "ZoneMgr" },
+            "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}
+        }
+    }
+}
diff --git a/tests/lettuce/data/ddns/example.org.sqlite3.orig b/tests/lettuce/data/ddns/example.org.sqlite3.orig
new file mode 100644
index 0000000..427fa24
Binary files /dev/null and b/tests/lettuce/data/ddns/example.org.sqlite3.orig differ
diff --git a/tests/lettuce/data/example.org.sqlite3 b/tests/lettuce/data/example.org.sqlite3
index 427fa24..f79a4e2 100644
Binary files a/tests/lettuce/data/example.org.sqlite3 and b/tests/lettuce/data/example.org.sqlite3 differ
diff --git a/tests/lettuce/features/ddns_system.feature b/tests/lettuce/features/ddns_system.feature
new file mode 100644
index 0000000..327ef96
--- /dev/null
+++ b/tests/lettuce/features/ddns_system.feature
@@ -0,0 +1,144 @@
+Feature: DDNS System
+    A number of BIND10-specific DDNS tests, that do not fall under the
+    'compliance' category; specific ACL checks, module checks, etc.
+
+    Scenario: Module tests
+        # The given config has b10-ddns disabled
+        Given I have bind10 running with configuration ddns/noddns.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+        # Sanity check
+        bind10 module DDNS should not be running
+
+        # Test 1
+        When I use DDNS to set the SOA serial to 1235
+        # Note: test spec says refused here, system returns SERVFAIL
+        #The DDNS response should be REFUSED
+        The DDNS response should be SERVFAIL
+        And the SOA serial for example.org should be 1234
+
+        # Test 2
+        When I configure bind10 to run DDNS
+        And wait for new bind10 stderr message DDNS_STARTED
+        bind10 module DDNS should be running
+
+        # Test 3
+        When I use DDNS to set the SOA serial to 1236
+        The DDNS response should be REFUSED
+        And the SOA serial for example.org should be 1234
+
+        # Test 4
+        When I send bind10 the following commands
+        """
+        config add DDNS/zones
+        config set DDNS/zones[0]/origin example.org
+        config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "from": "127.0.0.1"}
+        config commit
+        """
+
+        # Test 5
+        When I use DDNS to set the SOA serial to 1237
+        # also check if Auth server reloaded
+        And wait for new bind10 stderr message AUTH_LOAD_ZONE
+        The DDNS response should be SUCCESS
+        And the SOA serial for example.org should be 1237
+
+        # Test 6
+        When I send bind10 the command DDNS shutdown
+        And wait for new bind10 stderr message DDNS_STOPPED
+
+        # Test 7
+        # BoB should restart it
+        And wait for new bind10 stderr message DDNS_STARTED
+
+        # Test 8
+        # Known issue: after shutdown, first new attempt results in SERVFAIL
+        When I use DDNS to set the SOA serial to 1238
+        The DDNS response should be SERVFAIL
+        And the SOA serial for example.org should be 1237
+
+        When I use DDNS to set the SOA serial to 1238
+        And wait for new bind10 stderr message AUTH_LOAD_ZONE
+        The DDNS response should be SUCCESS
+        And the SOA serial for example.org should be 1238
+
+        # Test 9
+        When I send bind10 the command Auth shutdown
+        And wait for new bind10 stderr message AUTH_SHUTDOWN
+        # BoB should restart it automatically
+        And wait for new bind10 stderr message AUTH_SERVER_STARTED
+
+        # Test 10
+        When I use DDNS to set the SOA serial to 1239
+        And wait for new bind10 stderr message AUTH_LOAD_ZONE
+        The DDNS response should be SUCCESS
+        And the SOA serial for example.org should be 1239
+
+        # Test 11
+        When I configure BIND10 to stop running DDNS
+        And wait for new bind10 stderr message DDNS_STOPPED
+
+        bind10 module DDNS should not be running
+
+        # Test 12
+        When I use DDNS to set the SOA serial to 1240
+        # should this be REFUSED again?
+        The DDNS response should be SERVFAIL
+        And the SOA serial for example.org should be 1239
+
+    Scenario: ACL
+        Given I have bind10 running with configuration ddns/ddns.config
+        And wait for bind10 stderr message BIND10_STARTED_CC
+        And wait for bind10 stderr message AUTH_SERVER_STARTED
+        And wait for bind10 stderr message DDNS_STARTED
+
+        # Sanity check
+        A query for new1.example.org should have rcode NXDOMAIN
+        A query for new2.example.org should have rcode NXDOMAIN
+        A query for new3.example.org should have rcode NXDOMAIN
+        The SOA serial for example.org should be 1234
+
+        # Test 1
+        When I use DDNS to add a record new1.example.org. 3600 IN A 192.0.2.1
+        The DDNS response should be SUCCESS
+        A query for new1.example.org should have rcode NOERROR
+        The SOA serial for example.org should be 1235
+
+        # Test 2
+        When I set DDNS ACL 0 for 127.0.0.1 to REJECT
+        Then use DDNS to add a record new2.example.org. 3600 IN A 192.0.2.2
+        The DDNS response should be REFUSED
+        A query for new2.example.org should have rcode NXDOMAIN
+        The SOA serial for example.org should be 1235
+
+        # Test 3
+        When I set DDNS ACL 0 for 127.0.0.1 to ACCEPT
+        Then use DDNS to add a record new3.example.org. 3600 IN A 192.0.2.3
+        The DDNS response should be SUCCESS
+        A query for new3.example.org should have rcode NOERROR
+        The SOA serial for example.org should be 1236
+
+    #Scenario: DDNS and Xfrout
+    ## Unfortunately, Xfrout can only notify to inzone slaves, and hence only
+    ## to port 53, which we do not want to use for Lettuce tests (for various
+    ## reasons). So for now this test is only an outline, the configs
+    ## themselves have not been set up yet
+    #    When I start bind10 with configuration ddns/primary.config as primary
+    #    And wait for primary stderr message AUTH_SERVER_STARTED
+    #    And wait for primary stderr message XFROUT_STARTED
+    #    And wait for primary stderr message DDNS_STARTED
+
+    #    And I start bind10 with configuration example2.org.config with cmdctl port 47804 as secondary
+    #    And wait for secondary stderr message AUTH_SERVER_STARTED
+    #    And wait for secondary stderr message XFRIN_STARTED
+
+    #    # Sanity check
+    #    The SOA serial for example.org should be 1234
+    #    The SOA serial for example.org at 127.0.0.1:47807 should be 1234
+
+    #    When I use DDNS to set the SOA serial to 1235
+    #    The DDNS response should be SUCCESS
+
+    #    The SOA serial for example.org should be 1235
+    #    The SOA serial for example.org at 127.0.0.1:47807 should be 1235
diff --git a/tests/lettuce/features/example.feature b/tests/lettuce/features/example.feature
index 9b7c2ca..685cf8b 100644
--- a/tests/lettuce/features/example.feature
+++ b/tests/lettuce/features/example.feature
@@ -182,6 +182,9 @@ Feature: Example feature
 
         A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
         A query for www.example.org to [::1]:47807 should have rcode NOERROR
+        The SOA serial for example.org should be 1234
+        The SOA serial for example.org at 127.0.0.1:47806 should be 1234
+        The SOA serial for example.org at ::1:47807 should be 1234
 
         Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
         And wait for bind10_one stderr message DATASRC_SQLITE_OPEN
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index c56afb7..a08a887 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -52,7 +52,7 @@ def start_bind10(step, config_file, cmdctl_port, msgq_sockfile, process_name):
     It will also fail if there is a running process with the given process_name
     already.
     """
-    args = [ 'bind10', '-v' ]
+    args = [ 'bind10', '-n', '-v' ]
     if config_file is not None:
         args.append('-p')
         args.append("configurations/")
@@ -334,3 +334,32 @@ def module_is_running(step, name, not_str):
         not_str = ""
     step.given('send bind10 the command help')
     step.given('last bindctl output should' + not_str + ' contain ' + name + ' exactly')
+
+ at step('Configure BIND10 to run DDNS')
+def configure_ddns_on(step):
+    """
+    Convenience compound step to enable the b10-ddns module.
+    """
+    step.behave_as("""
+    When I send bind10 the following commands
+        \"\"\"
+        config add Boss/components b10-ddns
+        config set Boss/components/b10-ddns/kind dispensable
+        config set Boss/components/b10-ddns/address DDNS
+        config commit
+        \"\"\"
+    """)
+
+ at step('Configure BIND10 to stop running DDNS')
+def configure_ddns_off(step):
+    """
+    Convenience compound step to disable the b10-ddns module.
+    """
+    step.behave_as("""
+    When I send bind10 the following commands
+        \"\"\"
+        config remove Boss/components b10-ddns
+        config commit
+        \"\"\"
+    """)
+
diff --git a/tests/lettuce/features/terrain/nsupdate.py b/tests/lettuce/features/terrain/nsupdate.py
new file mode 100644
index 0000000..946439d
--- /dev/null
+++ b/tests/lettuce/features/terrain/nsupdate.py
@@ -0,0 +1,168 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from lettuce import *
+import subprocess
+import re
+
+def run_nsupdate(commands):
+    """Run nsupdate.
+       Parameters:
+       commands: a sequence of strings which will be sent.
+       update_address: adress to send the update to
+       update_port: port to send the update to
+       zone: zone to update
+
+       Appends 'send' and 'quit' as final commands.
+
+       nsupdate's stdout and stderr streams are stored (as one multiline string
+       in world.last_nsupdate_stdout/stderr.
+
+       The return code is stored in world.last_nsupdate_returncode
+       (it is not checked here, since a number of tests intentionally
+       result in a non-zero return code).
+    """
+    commands.append('send')
+    commands.append('quit')
+    args = ['nsupdate' ]
+    nsupdate = subprocess.Popen(args, 1, None, subprocess.PIPE,
+                                subprocess.PIPE, subprocess.PIPE)
+    for line in commands:
+        nsupdate.stdin.write(line + "\n")
+    (stdout, stderr) = nsupdate.communicate()
+    world.last_nsupdate_returncode = nsupdate.returncode
+    world.last_nsupdate_stdout = stdout
+    world.last_nsupdate_stderr = stderr
+
+ at step('send a DDNS update for (\S+) with the following commands:')
+def send_multiple_commands(step, zone):
+    """
+    Run nsupdate, and send it the given multiline set of commands.
+    A send and quit command is always appended.
+
+    This is the most 'raw' wrapper around the nsupdate call; every
+    command except the final send needs to be specified. Intended
+    for those tests that have unique properties.
+    """
+    commands = step.multiline.split("\n")
+    run_nsupdate(commands, zone)
+
+ at step('DDNS response should be ([A-Z]+)')
+def check_ddns_response(step, response):
+    """
+    Checks the result of the last call to nsupdate.
+
+    If the given response argument is SUCCESS, it simply checks whether
+    the return code from nsupdate is 0 (there is no output in that case).
+    If not, it checks whether it is not 0, and if the given response string
+    matches a line 'update failed: <response>' in the stderr output of
+    nsupdate.
+
+    Prints exit code, stdout and stderr streams of nsupdate if it fails.
+    """
+    # For success, nsupdate is silent, only check result code 0
+    if response == "SUCCESS":
+        assert 0 == world.last_nsupdate_returncode,\
+               "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
+               "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
+               "stderr:\n" + str(world.last_nsupdate_stderr)
+    else:
+        found = False
+        for line in world.last_nsupdate_stderr.split('\n'):
+            if line == "update failed: " + response:
+                found = True
+        assert found and (0 != world.last_nsupdate_returncode),\
+               "Response " + response + " not found in nsupdate output\n" +\
+               "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
+               "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
+               "stderr:\n" + str(world.last_nsupdate_stderr)
+
+
+# Individual steps to create a DDNS update packet through nsupdate
+ at step('Prepare a DDNS update(?: for (\S+))?(?: to (\S+)(?: port ([0-9]+)))?')
+def prepare_update(step, zone, server, port):
+    """
+    Prepares an nsupdate command that sets up an update to a server
+    for a zone. The update is not sent yet, but the commands
+    are stored in world.nsupdate_commands.
+    """
+    commands = []
+    if server is not None:
+        commands.append("server " + server)
+    else:
+        commands.append("server 127.0.0.1")
+    if port is not None:
+        commands[0] = commands[0] + " " + port
+    else:
+        commands[0] = commands[0] + " 47806"
+    if zone is not None:
+        commands.append("zone " + zone)
+    world.nsupdate_commands = commands
+
+ at step('Add to the DDNS update: (.*)')
+def add_line_to_ddns_update(step, line):
+    """
+    Adds a single line to the prepared nsupdate. It is not sent yet.
+    The line must conform to nsupdate syntax.
+    """
+    world.nsupdate_commands.append(line)
+
+ at step('Add the following lines to the DDNS update:')
+def add_lines_to_ddns_update(step, line):
+    """
+    Adds multiple lines to the prepared nsupdate. It is not sent yet.
+    The lines must conform to nsupdate syntax.
+    """
+    world.nsupdate_commands.extend(step.multiline.split('\n'))
+
+ at step('Send the DDNS update')
+def run_ddns_update(step):
+    """
+    Runs the prepared nsupdate, see run_nsupdate() for more information.
+    """
+    run_nsupdate(world.nsupdate_commands)
+
+ at step('use DDNS to set the SOA SERIAL to ([0-9]+)')
+def set_serial_to(step, new_serial):
+    """
+    Convenience compound step; prepare an update for example.org,
+    which sets the SERIAL to the given value, and send it.
+    It makes no other changes, and has hardcoded values for the other
+    SOA rdata fields.
+    """
+    step.given('Prepare a DDNS update')
+    step.given('add to the DDNS update: update add example.org 3600 IN SOA ns1.example.org. admin.example.org. ' + new_serial + ' 3600 1800 2419200 7200')
+    step.given('Send the DDNS update')
+
+ at step('use DDNS to add a record (.*)')
+def add_record(step, new_record):
+    """
+    Convenience compound step; prepare an update for example.org,
+    which adds one record, then send it.
+    Apart from the update addition, the update will not contain anything else.
+    """
+    step.given('Prepare a DDNS update')
+    step.given('add to the DDNS update: update add ' + new_record)
+    step.given('Send the DDNS update')
+
+ at step('set DDNS ACL ([0-9]+) for ([0-9.]+) to ([A-Z]+)')
+def set_ddns_acl_to(step, nr, address, action):
+    """
+    Convenience step to update a single ACL for DDNS.
+    Replaces the ACL at the given index for the given
+    address, to the given action
+    """
+    step.given('set bind10 configuration DDNS/zones[' + nr + ']/update_acl to [{"action": "' + action + '", "from": "' + address + '"}]')
+    step.given('last bindctl output should not contain Error')
diff --git a/tests/lettuce/features/terrain/querying.py b/tests/lettuce/features/terrain/querying.py
index a547014..abd7c18 100644
--- a/tests/lettuce/features/terrain/querying.py
+++ b/tests/lettuce/features/terrain/querying.py
@@ -240,8 +240,8 @@ def query(step, dnssec, query_name, qtype, qclass, addr, port, rcode):
         "Expected: " + rcode + ", got " + query_result.rcode
     world.last_query_result = query_result
 
- at step('The SOA serial for ([\w.]+) should be ([0-9]+)')
-def query_soa(step, query_name, serial):
+ at step('The SOA serial for ([\S.]+) (?:at (\S+)(?::([0-9]+)) )?should be ([0-9]+)')
+def query_soa(step, query_name, address, port, serial=None):
     """
     Convenience function to check the SOA SERIAL value of the given zone at
     the nameserver at the default address (127.0.0.1:47806).
@@ -251,7 +251,11 @@ def query_soa(step, query_name, serial):
     If the rcode is not NOERROR, or the answer section does not contain the
     SOA record, this step fails.
     """
-    query_result = QueryResult(query_name, "SOA", "IN", "127.0.0.1", "47806")
+    if address is None:
+        address = "127.0.0.1"
+    if port is None:
+        port = "47806"
+    query_result = QueryResult(query_name, "SOA", "IN", address, port)
     assert "NOERROR" == query_result.rcode,\
         "Got " + query_result.rcode + ", expected NOERROR"
     assert len(query_result.answer_section) == 1,\
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index caa17ce..a35d0de 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -53,8 +53,14 @@ copylist = [
      "configurations/resolver/resolver_basic.config"],
     ["configurations/multi_instance/multi_auth.config.orig",
      "configurations/multi_instance/multi_auth.config"],
+    ["configurations/ddns/ddns.config.orig",
+     "configurations/ddns/ddns.config"],
+    ["configurations/ddns/noddns.config.orig",
+     "configurations/ddns/noddns.config"],
     ["data/inmem-xfrin.sqlite3.orig",
-     "data/inmem-xfrin.sqlite3"]
+     "data/inmem-xfrin.sqlite3"],
+    ["data/ddns/example.org.sqlite3.orig",
+     "data/ddns/example.org.sqlite3"]
 ]
 
 # This is a list of files that, if present, will be removed before a scenario
@@ -258,7 +264,7 @@ class RunningProcesses:
         Initialize with no running processes.
         """
         self.processes = {}
-    
+
     def add_process(self, step, process_name, args):
         """
         Start a process with the given arguments, and store it under the given
@@ -297,14 +303,14 @@ class RunningProcesses:
             "Process " + name + " unknown"
         self.processes[process_name].stop_process()
         del self.processes[process_name]
-        
+
     def stop_all_processes(self):
         """
         Stop all running processes.
         """
         for process in self.processes.values():
             process.stop_process()
-    
+
     def keep_files(self):
         """
         Keep the redirection files for stdout/stderr output of all processes
diff --git a/tests/tools/badpacket/tests/Makefile.am b/tests/tools/badpacket/tests/Makefile.am
index 2daa664..bf70669 100644
--- a/tests/tools/badpacket/tests/Makefile.am
+++ b/tests/tools/badpacket/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/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
index d4034b3..21e5b46 100644
--- a/tests/tools/perfdhcp/tests/Makefile.am
+++ b/tests/tools/perfdhcp/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



More information about the bind10-changes mailing list