BIND 10 trac2436, updated. 8dc51055585e92db23cfb97fd280a10eecf3ff7a [2436] Remove the validation from the b10-loadzone
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jan 8 14:38:57 UTC 2013
The branch, trac2436 has been updated
discards 24d4a811795ef1e7f72af8ca9534ff12e4a59b06 (commit)
discards 6b199fc7d5e9880822aaa1f2e285760f59200751 (commit)
discards 0c25dda981710f0a8d46e79cfa7b1a5af75c7b7f (commit)
discards ccde39b3aa9da7d46b112865780c4af4c686f4b3 (commit)
via 8dc51055585e92db23cfb97fd280a10eecf3ff7a (commit)
via 18adb5bce11c191f14aa26e58fca838aeb9d80d0 (commit)
via f4e6ab2a98e80e8832b201a6ed2db716bd1d4ee1 (commit)
via b25df37f16da91f53b2e068fdd5aeb78e349311b (commit)
via dac912f20c6f315f951979906e79c9a47ea172fe (commit)
via 1dc55b22817baca2b9750082dbf408209affbf02 (commit)
via 856d60a96bbed5735541659d7c12c51fc055a6cb (commit)
via 792c129a0785c73dd28fd96a8f1439fe6534a3f1 (commit)
via b85908b8e8321ae1b1ab139c7182a5026bc7f73d (commit)
via 3e191cf2889f9ef77561ebac18e1cc019d1bffe4 (commit)
via 8399e2b6d96d18197dc27b9f4bee1f5051f6c7af (commit)
via 3c162ca506c8fc08ab93374521807b2132e81c24 (commit)
via 5eb2ff86d745543185551758bf31132596ad20d9 (commit)
via 0de32e7fe21e18069d26e3d282ac8078c852f004 (commit)
via 70cb4d4886e27554e9a9e8751089ba8bd4678701 (commit)
via 83e71312ca3631e5a8f2364bb8434d69ae000ee1 (commit)
via b55b8b6686cc80eed41793c53d1779f4de3e9e3c (commit)
via 7234eee9cf529a98841ca502dde655bd15f4299f (commit)
via 806865cd2e830785f0d377d759e7475b9e1f640b (commit)
via e7f9ab53656fdfb8a1b27c7056f8dc2c86d8f966 (commit)
via 35d02013f29d19fabf678089ba97db2e67ab3f25 (commit)
via 004823862f69853d3c4acd6f8e0bc60925c25ed1 (commit)
via 1fa69473d881a5c31cd802563f2c398e1f6d1ecb (commit)
via 3baea8cf898f2e9dcd35c88e48999b8112732f9d (commit)
via 08c2f82000bacb76d0602cc9c428637aed0baf21 (commit)
via be570bcaf88318286027da7da40a600cbe4223e9 (commit)
via e5a354694f4d0192132d00e25223d16eb24f25d7 (commit)
via a92495cd29fbe405bef744b98d01ff200caa05c6 (commit)
via f516fc484544b7e08475947d6945bc87636d4115 (commit)
via 96f973347163030737690174068f51c193a2f4e6 (commit)
via 18831888667fe9363c22d3d19db9b0f98e56ef70 (commit)
via 15a69fd77a14825ca0deb1cba52690bf0ea33195 (commit)
via 50e76dc960ad76ec97004832d718cb337390fce8 (commit)
via 3e9a69424117d8f9841d76cf71dab353b6aa5435 (commit)
via 890e0b0b7df76e61c1638744647a793143537722 (commit)
via a6ce7e4332223277ca50cd68a65e7e404621717c (commit)
via d83fc2a12f36818bdd60c87e61d01be7a5dfa4e5 (commit)
via 9e85dc2c98c9fde0f1d73c1d6c0d0d6188b41439 (commit)
via 36622df85e6b1b7ce4fc621fbfaa21d2b35003d8 (commit)
via 059fcd024b1611a7c5ca52a6f7055f7c9d14f106 (commit)
via 9fbb0ae95365974d80a87704f64d6e4907dcb52e (commit)
via 4cb0974a3a642d06513ecc43743109bd92e45a08 (commit)
via 573d2b1fbc3145d18ec71621f56e5c74a328ed3c (commit)
via b8e5414ee0445b3b0141203470ac07f42d7bbc32 (commit)
via aa32f38e127d1b3b3d4e1349fbfdcac155dc5c5e (commit)
via 40502d5e2c6cbe71143e842ac7908e8a55b62324 (commit)
via 60b979b7088f9e282b173c8d71792e2c4b20b57c (commit)
via 0974318566abe08d0702ddd185156842c6642424 (commit)
via 31147f0f82fd1e1b8ead164153599269822517ea (commit)
via 8679b4053024172db8e3d55b7ed50a855efc77c0 (commit)
via da5c7b1faa2363bbca6db163364e6e0073bcc3ab (commit)
via 0232cbe00f15efbe750b21c80973feb220bdc6c2 (commit)
via 67eb8ca010320d7c942ba154d70456d8e9981fa7 (commit)
via 0b722ec0d7813e933db3919df492dccf11a3b4eb (commit)
via b0732ce0447db537b3d643735f16aad09ddd4c7c (commit)
via 1c2b85d7d1c1448d78b8f2a9927b7952ef42e39d (commit)
via 62794b031c00451e7bec0a0b94b4a2dadb42ae0e (commit)
via 82041829bd8cc7400d0fee9d08e4cf1091c25a70 (commit)
via baa1ddd9057d0f10f85faa67f92e94268189dcd1 (commit)
via 5379cabb61c3daff05c541ab6c9675a8a3ebfed7 (commit)
via 30cbe355d7fcccf3e06472e5fd04c9de870adbe6 (commit)
via 2629140f12accdfa6b4de4f59e5a76b0cc10ecb6 (commit)
via ea8082a263cb6d2b69764da2322b2c483913e5da (commit)
via 36fea252b03a38a0a86b830bc6a3373a88966413 (commit)
via d4bd5936f52d20ba21865858861dc358f531da04 (commit)
via 19f2ceb60860badfc183f9d4e7d54ac9105ee296 (commit)
via ce6444b69feb909e5c8212e7335eeca14a2b11ea (commit)
via 33ba1827b513aa735a483f6927667a9e872a2207 (commit)
via 23035066d0c07c7b83120d3cba9efc1febe18ff5 (commit)
via e17ddc8b0f6726a185e982c22dbf787351c22cdd (commit)
via 8960a571bc2db7129d4ce8861f83808fb71dc81c (commit)
via 3ff7531f6be864ca6965a60ef1e2744e945a3569 (commit)
via fe40dade09da3ca910f034e650db2a3d5a93a49b (commit)
via fb8433b523f854b3978ef1c868d573e172a5a76c (commit)
via 5d2f35c760be8fe9aa54f566461ca8a9c5d0db78 (commit)
via 5d8d1e296a4eccc34ab0c19acfa9a4f39a63745c (commit)
via 08dec0f8e0129ed43373dca3a335d752a3fb5e2c (commit)
via 6ed60d209f8c6d7d7d4978b19a2feb6170e2c67a (commit)
via 448686bfad7694acde8b08355dd5921efd4bb2ca (commit)
via 5deb6bbc2df15dc20f36727f0abeaa4d4ad0c17e (commit)
via 17cd3618c21b12d7867d9e5e4dba81c1cee46cb6 (commit)
via 376b3b0760c5198dbf5ccf740c666ed9c9acaf45 (commit)
via 28a43fa59ffd4f772104a76e3636b8c54b329aa3 (commit)
via 9065335de2f05d3bc1d874b933ba80b51feb68f6 (commit)
via caf95c197aa4abb916d8e27d3a5004ebed2e6108 (commit)
via a32568b7b3006315e79ee411b50db8cc4dc435a7 (commit)
via 3c702b8966081eb2d73914754abd247350957bdd (commit)
via 8b1a0145b79147510ccde00b28e5124b8951e4d1 (commit)
via 19bd1a5a0b222ac2473793d87539f76a34cb1bec (commit)
via 27e7210b344cea5c7098bf233481d5f2bf7cac97 (commit)
via 230078e5f45f29fec72e35a77e2fd897fb6d3fb7 (commit)
via 77b42eabd7630b5ba7f5f9fa383de73e0e7fe49d (commit)
via 31a4bc5aacc85f79416c6c4da2e33e642077e9f9 (commit)
via 89f331dff577268f90d885dc871cd2a152940ef8 (commit)
via c6c569cae91e19aa6267081ef9926f52b87d903d (commit)
via 3ac15510bb80c1f7fe76779dfa9ff254eb5cc796 (commit)
via e1c2233fab06d393eed6e16fc09f9bf672963112 (commit)
via 5f8c941e31630163287b5197b6185dae9b03de13 (commit)
via 61c303c8f15dce6a985e5d0164a50844b6c64558 (commit)
via 6eb4014782afae7660dbdc291c315a211e5ceed5 (commit)
via bdb56ee0da86148611e50095c9d70dfe4e4f4b7f (commit)
via 17641de21f0430981b554af5595711a3bcf2d699 (commit)
via c864447b8ec187264fac032be1561c77bb3103b6 (commit)
via 772ce4c2888583e2fe1e9959c705608f6d1b7e98 (commit)
via 72beaf8964363734d4f5e7b4b11ae7b8b4b5557c (commit)
via b07567633570ebffebb13e92ae71d429967a7cb8 (commit)
via 46cf8771c26b220b60ca8f30332442b5a7dbcbef (commit)
via 593eacf16fe69fade5eeb71704ac25c6e046421b (commit)
via 34cb2bf92262addf0fb401365c23773dee3f3e89 (commit)
via c55cdfd6318072c9ae7005298fca07615065c97a (commit)
via be90b92cbe913d2eda9b95f18c099669d0a72a57 (commit)
via 6ce0831748423ab48611785f29ee999beeaf8322 (commit)
via 32b6b02bc06bbf0303639964f3544e9cba4b138d (commit)
via e632c67d9f527acf7cea58af306057e5ba5601a4 (commit)
via 8ea9bddd35ab3a5716a51310ccc4cc6a3d96b572 (commit)
via 318530edbf905ff288f527ef73bb88da4c57b548 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (24d4a811795ef1e7f72af8ca9534ff12e4a59b06)
\
N -- N -- N (8dc51055585e92db23cfb97fd280a10eecf3ff7a)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
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 8dc51055585e92db23cfb97fd280a10eecf3ff7a
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jan 4 18:30:30 2013 +0100
[2436] Remove the validation from the b10-loadzone
It is now done in the ZoneLoader in the C++ backend, so don't duplicate
the code.
commit 18adb5bce11c191f14aa26e58fca838aeb9d80d0
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Jan 8 15:23:25 2013 +0100
[2436] Update the mock finder, so it works with zone validation
commit f4e6ab2a98e80e8832b201a6ed2db716bd1d4ee1
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jan 4 18:28:05 2013 +0100
[2436] Perform the validation in ZoneLoader
TODO: Modify this commit and use the datasource RRsetCollection. This
way it doesn't work.
commit b25df37f16da91f53b2e068fdd5aeb78e349311b
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jan 4 16:22:11 2013 +0100
[2436] Test there's validation in ZoneLoader
commit dac912f20c6f315f951979906e79c9a47ea172fe
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Fri Jan 4 15:41:47 2013 +0100
[2436] Document validation in ZoneLoader
It should throw an exception if the validation fails.
commit 1dc55b22817baca2b9750082dbf408209affbf02
Merge: 856d60a 70cb4d4
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Tue Jan 8 14:14:13 2013 +0100
Merge remote-tracking branch 'origin/trac2435' into master
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 3 +-
ChangeLog | 25 +
configure.ac | 3 +
src/bin/auth/tests/.gitignore | 2 +
src/bin/auth/tests/Makefile.am | 38 +-
src/bin/auth/tests/gen-query-testdata.py | 98 ++
src/bin/auth/tests/query_inmemory_unittest.cc | 123 ---
src/bin/auth/tests/query_unittest.cc | 1108 ++++++++++++--------
src/bin/auth/tests/testdata/.gitignore | 10 +
src/bin/auth/tests/testdata/Makefile.am | 7 +-
src/bin/auth/tests/testdata/example-base-inc.zone | 236 +++++
src/bin/auth/tests/testdata/example-base.zone.in | 7 +
.../testdata/example-common-inc-template.zone | 5 +
src/bin/auth/tests/testdata/example-nsec3-inc.zone | 16 +
src/bin/auth/tests/testdata/example-nsec3.zone.in | 8 +
src/bin/auth/tests/testdata/example.zone | 121 ---
src/bin/auth/tests/testdata/example.zone.in | 6 +
src/bin/bind10/bind10_messages.mes | 7 -
src/bin/bind10/bind10_src.py.in | 79 +-
src/bin/bind10/tests/bind10_test.py.in | 97 +-
src/bin/dhcp4/config_parser.cc | 579 +++++++---
src/bin/dhcp4/config_parser.h | 116 +-
src/bin/dhcp4/dhcp4.spec | 10 +
src/bin/dhcp4/tests/config_parser_unittest.cc | 113 +-
src/bin/dhcp6/config_parser.cc | 748 ++++++++-----
src/bin/dhcp6/config_parser.h | 124 +--
src/bin/dhcp6/dhcp6.dox | 32 +-
src/bin/dhcp6/dhcp6.spec | 10 +
src/bin/dhcp6/dhcp6_messages.mes | 43 +
src/bin/dhcp6/dhcp6_srv.cc | 181 +++-
src/bin/dhcp6/dhcp6_srv.h | 39 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 52 +-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 380 +++++--
src/bin/loadzone/.gitignore | 1 +
src/bin/loadzone/run_loadzone.sh.in | 2 +-
src/bin/loadzone/tests/correct/known.test.out | 4 +-
src/bin/resolver/resolver.h | 6 +-
src/lib/cc/data.h | 3 +-
src/lib/config/config_data.h | 4 +-
src/lib/datasrc/Makefile.am | 1 +
src/lib/datasrc/{client.cc => rrset_collection.cc} | 47 +-
src/lib/datasrc/rrset_collection.h | 89 ++
src/lib/datasrc/tests/database_unittest.cc | 40 +
.../datasrc/tests/zone_finder_context_unittest.cc | 32 +-
src/lib/datasrc/tests/zone_loader_unittest.cc | 82 +-
src/lib/datasrc/zone_loader.cc | 8 +-
src/lib/dhcp/duid.cc | 10 +
src/lib/dhcp/duid.h | 3 +
src/lib/dhcp/option_definition.cc | 8 +-
src/lib/dhcp/option_definition.h | 2 +-
src/lib/dhcp/tests/duid_unittest.cc | 12 +-
src/lib/dhcpsrv/Makefile.am | 25 +-
src/lib/dhcpsrv/cfgmgr.cc | 26 +-
.../dhcpsrv/dhcp_config_parser.h} | 111 +-
.../dhcp6_log.cc => lib/dhcpsrv/dhcpsrv_log.cc} | 6 +-
src/lib/dhcpsrv/dhcpsrv_log.h | 66 ++
src/lib/dhcpsrv/dhcpsrv_messages.mes | 231 ++++
.../hwaddr.cc} | 32 +-
.../hwaddr.h} | 36 +-
src/lib/dhcpsrv/lease_mgr.cc | 14 +-
src/lib/dhcpsrv/lease_mgr.h | 20 +-
src/lib/dhcpsrv/lease_mgr_factory.cc | 53 +-
src/lib/dhcpsrv/lease_mgr_factory.h | 35 +-
src/lib/dhcpsrv/memfile_lease_mgr.cc | 71 +-
src/lib/dhcpsrv/memfile_lease_mgr.h | 1 +
src/lib/dhcpsrv/mysql_lease_mgr.cc | 49 +-
src/lib/dhcpsrv/mysql_lease_mgr.h | 1 +
src/lib/dhcpsrv/subnet.cc | 8 +-
src/lib/dhcpsrv/tests/Makefile.am | 1 +
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 26 +-
.../tests/hwaddr_unittest.cc} | 39 +-
.../dhcpsrv/tests/lease_mgr_factory_unittest.cc | 135 ++-
src/lib/dns/master_loader.cc | 2 +-
src/lib/dns/master_loader_callbacks.h | 6 +-
src/lib/dns/rdata/generic/detail/char_string.cc | 27 +-
src/lib/dns/rdata/generic/detail/char_string.h | 19 +-
src/lib/dns/rdata/generic/detail/txt_like.h | 10 +-
src/lib/dns/rrcollator.cc | 2 +-
src/lib/dns/rrset_collection.cc | 2 +-
src/lib/dns/rrset_collection.h | 14 +-
src/lib/dns/rrset_collection_base.h | 4 +-
src/lib/dns/tests/rdata_char_string_unittest.cc | 59 +-
src/lib/dns/tests/rdata_txt_like_unittest.cc | 18 +
src/lib/dns/tests/rrset_collection_unittest.cc | 17 +-
src/lib/dns/tests/zone_checker_unittest.cc | 2 +
src/lib/python/isc/bind10/component.py | 10 +-
src/lib/python/isc/bind10/special_component.py | 15 +-
src/lib/python/isc/bind10/tests/component_test.py | 32 +-
src/lib/python/isc/datasrc/tests/.gitignore | 1 +
src/lib/testutils/dnsmessage_test.h | 3 +-
tests/tools/perfdhcp/tests/.gitignore | 5 +
91 files changed, 4166 insertions(+), 1848 deletions(-)
create mode 100755 src/bin/auth/tests/gen-query-testdata.py
delete mode 100644 src/bin/auth/tests/query_inmemory_unittest.cc
create mode 100644 src/bin/auth/tests/testdata/example-base-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-base.zone.in
create mode 100644 src/bin/auth/tests/testdata/example-common-inc-template.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3.zone.in
delete mode 100644 src/bin/auth/tests/testdata/example.zone
create mode 100644 src/bin/auth/tests/testdata/example.zone.in
copy src/lib/datasrc/{client.cc => rrset_collection.cc} (50%)
create mode 100644 src/lib/datasrc/rrset_collection.h
copy src/{bin/dhcp6/config_parser.h => lib/dhcpsrv/dhcp_config_parser.h} (50%)
copy src/{bin/dhcp6/dhcp6_log.cc => lib/dhcpsrv/dhcpsrv_log.cc} (86%)
create mode 100644 src/lib/dhcpsrv/dhcpsrv_log.h
create mode 100644 src/lib/dhcpsrv/dhcpsrv_messages.mes
copy src/lib/{dns/master_loader_callbacks.cc => dhcpsrv/hwaddr.cc} (62%)
copy src/lib/{dns/master_loader_callbacks.cc => dhcpsrv/hwaddr.h} (56%)
copy src/lib/{xfr/tests/client_test.cc => dhcpsrv/tests/hwaddr_unittest.cc} (50%)
-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index dd6903c..7bc41b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,5 +34,6 @@ TAGS
/all.info
/coverage-cpp-html
/dns++.pc
-/report.info
+/local.zone.sqlite3
/logger_lockfile
+/report.info
diff --git a/ChangeLog b/ChangeLog
index 0be10ce..825a48f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+540. [func] marcin
+ DHCP Option values can be now specified using a string of
+ tokens separated with comma sign. Subsequent tokens are used
+ to set values for corresponding data fields in a particular
+ DHCP option. The format of the token matches the data type
+ of the corresponding option field: e.g. "192.168.2.1" for IPv4
+ address, "5" for integer value etc.
+ (Trac #2545, git 792c129a0785c73dd28fd96a8f1439fe6534a3f1)
+
+539. [func] stephen
+ Add logging to the DHCP server library.
+ (Trac #2524, git b55b8b6686cc80eed41793c53d1779f4de3e9e3c)
+
+538. [bug] muks
+ Added escaping of special characters (double-quotes, semicolon,
+ backslash, etc.) in text-like RRType's toText() implementation.
+ Without this change, some TXT and SPF RDATA were incorrectly
+ stored in SQLite3 datasource as they were not escaped.
+ (Trac #2535, git f516fc484544b7e08475947d6945bc87636d4115)
+
+537. [func] tomek
+ b10-dhcp6: Support for RELEASE message has been added. Clients
+ are now able to release their non-temporary IPv6 addresses.
+ (Trac #2326, git 0974318566abe08d0702ddd185156842c6642424)
+
536. [build] jinmei
Detect a build issue on FreeBSD with g++ 4.2 and Boost installed via
FreeBSD ports at ./configure time. This seems to be a bug of
diff --git a/configure.ac b/configure.ac
index 37c2dd3..892bc6c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1320,6 +1320,9 @@ AC_OUTPUT([doc/version.ent
src/bin/msgq/run_msgq.sh
src/bin/auth/auth.spec.pre
src/bin/auth/spec_config.h.pre
+ src/bin/auth/tests/testdata/example.zone
+ src/bin/auth/tests/testdata/example-base.zone
+ src/bin/auth/tests/testdata/example-nsec3.zone
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/tests/process_rename_test.py
diff --git a/src/bin/auth/tests/.gitignore b/src/bin/auth/tests/.gitignore
index d6d1ec8..a45eff7 100644
--- a/src/bin/auth/tests/.gitignore
+++ b/src/bin/auth/tests/.gitignore
@@ -1 +1,3 @@
/run_unittests
+/example_base_inc.cc
+/example_nsec3_inc.cc
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 42f202b..36a3c68 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -7,7 +7,8 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
-AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_top_srcdir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DDSRC_DIR=\"$(abs_top_builddir)/src/lib/datasrc\"
AM_CPPFLAGS += -DPLUGIN_DATA_PATH=\"$(abs_top_builddir)/src/bin/cfgmgr/plugins\"
@@ -50,7 +51,6 @@ run_unittests_SOURCES += config_syntax_unittest.cc
run_unittests_SOURCES += command_unittest.cc
run_unittests_SOURCES += common_unittest.cc
run_unittests_SOURCES += query_unittest.cc
-run_unittests_SOURCES += query_inmemory_unittest.cc
run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc
run_unittests_SOURCES += datasrc_clients_builder_unittest.cc
@@ -81,6 +81,40 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
run_unittests_LDADD += $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
+# The following are definitions for auto-generating test data for query
+# tests.
+BUILT_SOURCES = example_base_inc.cc example_nsec3_inc.cc
+BUILT_SOURCES += testdata/example-base.sqlite3
+BUILT_SOURCES += testdata/example-nsec3.sqlite3
+
+EXTRA_DIST = gen-query-testdata.py
+
+CLEANFILES += example_base_inc.cc example_nsec3_inc.cc
+
+example_base_inc.cc: $(srcdir)/testdata/example-base-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-base-inc.zone example_base_inc.cc
+
+example_nsec3_inc.cc: $(srcdir)/testdata/example-nsec3-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-nsec3-inc.zone example_nsec3_inc.cc
+
+testdata/example-base.sqlite3: testdata/example-base.zone
+ $(top_srcdir)/install-sh -c \
+ $(srcdir)/testdata/example-common-inc-template.zone \
+ testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-base.sqlite3\"}" \
+ example.com testdata/example-base.zone
+
+testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone
+ $(top_srcdir)/install-sh -c \
+ $(srcdir)/testdata/example-common-inc-template.zone \
+ testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
+ example.com testdata/example-nsec3.zone
+
check-local:
B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
diff --git a/src/bin/auth/tests/gen-query-testdata.py b/src/bin/auth/tests/gen-query-testdata.py
new file mode 100755
index 0000000..a71deb0
--- /dev/null
+++ b/src/bin/auth/tests/gen-query-testdata.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# 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.
+
+"""\
+This is a supplemental script to auto generate test data in the form of
+C++ source code from a DNS zone file.
+
+Usage: python gen-query-testdata.py source_file output-cc-file
+
+The usage doesn't matter much, though, because it's expected to be invoked
+from Makefile, and that would be only use case of this script.
+"""
+
+import sys
+import re
+
+# Markup for variable definition
+re_start_rr = re.compile('^;var=(.*)')
+
+# Skip lines starting with ';' (comments) or empty lines. re_start_rr
+# will also match this expression, so it should be checked first.
+re_skip = re.compile('(^;)|(^\s*$)')
+
+def parse_input(input_file):
+ '''Build an internal list of RR data from the input source file.
+
+ It generates a list of (variable_name, list of RR) tuples, where
+ variable_name is the expected C++ variable name for the subsequent RRs
+ if they are expected to be named. It can be an empty string if the RRs
+ are only expected to appear in the zone file.
+ The second element of the tuple is a list of strings, each of which
+ represents a single RR, e.g., "example.com 3600 IN A 192.0.2.1".
+
+ '''
+ result = []
+ rrs = None
+ with open(input_file) as f:
+ for line in f:
+ m = re_start_rr.match(line)
+ if m:
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+ rrs = []
+ rr_varname = m.group(1)
+ elif re_skip.match(line):
+ continue
+ else:
+ rrs.append(line.rstrip('\n'))
+
+ # if needed, store the last RRs (they are not followed by 'var=' mark)
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+
+ return result
+
+def generate_variables(out_file, rrsets_data):
+ '''Generate a C++ source file containing a C-string variables for RRs.
+
+ This produces a definition of C-string for each RRset that is expected
+ to be named as follows:
+ const char* const var_name =
+ "example.com. 3600 IN A 192.0.2.1\n"
+ "example.com. 3600 IN A 192.0.2.2\n";
+
+ Escape character '\' in the string will be further escaped so it will
+ compile.
+
+ '''
+ with open(out_file, 'w') as out:
+ for (var_name, rrs) in rrsets_data:
+ if len(var_name) > 0:
+ out.write('const char* const ' + var_name + ' =\n')
+ # Combine all RRs, escaping '\' as a C-string
+ out.write('\n'.join([' \"%s\\n\"' %
+ (rr.replace('\\', '\\\\'))
+ for rr in rrs]))
+ out.write(';\n')
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ sys.stderr.write('gen-query-testdata.py require 2 args\n')
+ sys.exit(1)
+ rrsets_data = parse_input(sys.argv[1])
+ generate_variables(sys.argv[2], rrsets_data)
+
diff --git a/src/bin/auth/tests/query_inmemory_unittest.cc b/src/bin/auth/tests/query_inmemory_unittest.cc
deleted file mode 100644
index f587ba2..0000000
--- a/src/bin/auth/tests/query_inmemory_unittest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// 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 <dns/name.h>
-#include <dns/message.h>
-#include <dns/rcode.h>
-#include <dns/opcode.h>
-
-#include <cc/data.h>
-
-#include <datasrc/client_list.h>
-
-#include <auth/query.h>
-
-#include <testutils/dnsmessage_test.h>
-
-#include <gtest/gtest.h>
-
-#include <string>
-
-using namespace isc::dns;
-using namespace isc::auth;
-using namespace isc::testutils;
-using isc::datasrc::ConfigurableClientList;
-using std::string;
-
-namespace {
-
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// This is not inside the zone, this is created at runtime
-const char* const synthetized_cname_txt =
- "www.dname.example.com. 3600 IN CNAME "
- "www.somethinglong.dnametarget.example.com.\n";
-
-// This is a subset of QueryTest using (subset of) the same test data, but
-// with the production in-memory data source. Both tests should be eventually
-// unified to avoid duplicates.
-class InMemoryQueryTest : public ::testing::Test {
-protected:
- InMemoryQueryTest() : list(RRClass::IN()), response(Message::RENDER) {
- response.setRcode(Rcode::NOERROR());
- response.setOpcode(Opcode::QUERY());
- list.configure(isc::data::Element::fromJSON(
- "[{\"type\": \"MasterFiles\","
- " \"cache-enable\": true, "
- " \"params\": {\"example.com\": \"" +
- string(TEST_OWN_DATA_DIR "/example.zone") +
- "\"}}]"), true);
- }
-
- ConfigurableClientList list;
- Message response;
- Query query;
-};
-
-// A wrapper to check resulting response message commonly used in
-// tests below.
-// check_origin needs to be specified only when the authority section has
-// an SOA RR. The interface is not generic enough but should be okay
-// for our test cases in practice.
-void
-responseCheck(Message& response, const isc::dns::Rcode& rcode,
- unsigned int flags, const unsigned int ancount,
- const unsigned int nscount, const unsigned int arcount,
- const char* const expected_answer,
- const char* const expected_authority,
- const char* const expected_additional,
- const Name& check_origin = Name::ROOT_NAME())
-{
- // In our test cases QID, Opcode, and QDCOUNT should be constant, so
- // we don't bother the test cases specifying these values.
- headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
- flags, 0, ancount, nscount, arcount);
- if (expected_answer != NULL) {
- rrsetsCheck(expected_answer,
- response.beginSection(Message::SECTION_ANSWER),
- response.endSection(Message::SECTION_ANSWER),
- check_origin);
- }
- if (expected_authority != NULL) {
- rrsetsCheck(expected_authority,
- response.beginSection(Message::SECTION_AUTHORITY),
- response.endSection(Message::SECTION_AUTHORITY),
- check_origin);
- }
- if (expected_additional != NULL) {
- rrsetsCheck(expected_additional,
- response.beginSection(Message::SECTION_ADDITIONAL),
- response.endSection(Message::SECTION_ADDITIONAL));
- }
-}
-
-/*
- * Test a query under a domain with DNAME. We should get a synthetized CNAME
- * as well as the DNAME.
- *
- * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
- * as well. This includes tests pointing inside the zone, outside the zone,
- * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
- */
-TEST_F(InMemoryQueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
- response);
-
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
- (string(dname_txt) + synthetized_cname_txt).c_str(),
- NULL, NULL);
-}
-}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 84b7f8a..a22d2d7 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -12,10 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <map>
-#include <sstream>
-#include <vector>
-
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/static_assert.hpp>
@@ -24,9 +20,12 @@
#include <dns/masterload.h>
#include <dns/message.h>
+#include <dns/master_loader.h>
#include <dns/name.h>
+#include <dns/nsec3hash.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
+#include <dns/rrcollator.h>
#include <dns/rrttl.h>
#include <dns/rrtype.h>
#include <dns/rdataclass.h>
@@ -40,6 +39,12 @@
#include <gtest/gtest.h>
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <vector>
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -49,7 +54,7 @@ using namespace isc::testutils;
namespace {
-// Simple wrapper for a sincle data source client.
+// Simple wrapper for a single data source client.
// The list simply delegates all the answers to the single
// client.
class SingletonList : public ClientList {
@@ -79,150 +84,21 @@ private:
DataSourceClient& client_;
};
+// These are commonly used data (auto-generated). There are some exceptional
+// data that are only used in a limited scenario, which are defined separately
+// below.
+#include <auth/tests/example_base_inc.cc>
+#include <auth/tests/example_nsec3_inc.cc>
-// This is the content of the mock zone (see below).
-// It's a sequence of textual RRs that is supposed to be parsed by
-// dns::masterLoad(). Some of the RRs are also used as the expected
-// data in specific tests, in which case they are referenced via specific
-// local variables (such as soa_txt).
-//
-// For readability consistency, all strings are placed in a separate line,
-// even if they are very short and can reasonably fit in a single line with
-// the corresponding variable. For example, we write
-// const char* const foo_txt =
-// "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-// instead of
-// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-const char* const soa_txt =
- "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
-const char* const zone_ns_txt =
- "example.com. 3600 IN NS glue.delegation.example.com.\n"
- "example.com. 3600 IN NS noglue.example.com.\n"
- "example.com. 3600 IN NS example.net.\n";
+// This is used only in one pathological test case.
const char* const zone_ds_txt =
"example.com. 3600 IN DS 57855 5 1 "
"B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const ns_addrs_txt =
- "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
- "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
- "noglue.example.com. 3600 IN A 192.0.2.53\n";
-const char* const delegation_txt =
- "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
- "delegation.example.com. 3600 IN NS noglue.example.com.\n"
- "delegation.example.com. 3600 IN NS cname.example.com.\n"
- "delegation.example.com. 3600 IN NS example.org.\n";
-// Borrowed from the RFC4035
-const char* const delegation_ds_txt =
- "delegation.example.com. 3600 IN DS 57855 5 1 "
- "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const mx_txt =
- "mx.example.com. 3600 IN MX 10 www.example.com.\n"
- "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
- "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
-const char* const www_a_txt =
- "www.example.com. 3600 IN A 192.0.2.80\n";
-const char* const cname_txt =
- "cname.example.com. 3600 IN CNAME www.example.com.\n";
-const char* const cname_nxdom_txt =
- "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
-// CNAME Leading out of zone
-const char* const cname_out_txt =
- "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// Some data at the dname node (allowed by RFC 2672)
-const char* const dname_a_txt =
- "dname.example.com. 3600 IN A 192.0.2.5\n";
+
// This is not inside the zone, this is created at runtime
const char* const synthetized_cname_txt =
"www.dname.example.com. 3600 IN CNAME "
"www.somethinglong.dnametarget.example.com.\n";
-// The rest of data won't be referenced from the test cases.
-const char* const other_zone_rrs =
- "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
- "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
- "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
-// Wildcards
-const char* const wild_txt =
- "*.wild.example.com. 3600 IN A 192.0.2.7\n";
-const char* const nsec_wild_txt =
- "*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
-const char* const cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
-const char* const nsec_cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN NSEC "
- "delegation.example.com. CNAME NSEC RRSIG\n";
-// Wildcard_nxrrset
-const char* const wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN A 192.0.2.9\n";
-const char* const nsec_wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG\n";
-const char* const wild_txt_next =
- "www.uwild.example.com. 3600 IN A 192.0.2.11\n";
-const char* const nsec_wild_txt_next =
- "www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
-// Wildcard empty
-const char* const empty_txt =
- "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
-const char* const nsec_empty_txt =
- "b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
-const char* const empty_prev_txt =
- "t.example.com. 3600 IN A 192.0.2.15\n";
-const char* const nsec_empty_prev_txt =
- "t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
-// Used in NXDOMAIN proof test. We are going to test some unusual case where
-// the best possible wildcard is below the "next domain" of the NSEC RR that
-// proves the NXDOMAIN, i.e.,
-// mx.example.com. (exist)
-// (.no.example.com. (qname, NXDOMAIN)
-// ).no.example.com. (exist)
-// *.no.example.com. (best possible wildcard, not exist)
-const char* const no_txt =
- ").no.example.com. 3600 IN AAAA 2001:db8::53\n";
-// NSEC records.
-const char* const nsec_apex_txt =
- "example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG\n";
-const char* const nsec_mx_txt =
- "mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG\n";
-const char* const nsec_no_txt =
- ").no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG\n";
-// We'll also test the case where a single NSEC proves both NXDOMAIN and the
-// non existence of wildcard. The following records will be used for that
-// test.
-// ).no.example.com. (exist, whose NSEC proves everything)
-// *.no.example.com. (best possible wildcard, not exist)
-// nx.no.example.com. (NXDOMAIN)
-// nz.no.example.com. (exist)
-const char* const nz_txt =
- "nz.no.example.com. 3600 IN AAAA 2001:db8::5300\n";
-const char* const nsec_nz_txt =
- "nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG\n";
-const char* const nsec_nxdomain_txt =
- "noglue.example.com. 3600 IN NSEC nonsec.example.com. A\n";
-
-// NSEC for the normal NXRRSET case
-const char* const nsec_www_txt =
- "www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";
-
-// Authoritative data without NSEC
-const char* const nonsec_a_txt =
- "nonsec.example.com. 3600 IN A 192.0.2.0\n";
-
-// NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-const string nsec3_apex_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG\n";
-const string nsec3_apex_rrsig_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
-const string nsec3_www_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-const string nsec3_www_rrsig_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
// NSEC3 for wild.example.com (used in wildcard tests, will be added on
// demand not to confuse other tests)
@@ -246,42 +122,13 @@ const char* const nsec3_uwild_txt =
"t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-// (Secure) delegation data; Delegation with DS record
-const char* const signed_delegation_txt =
- "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const signed_delegation_ds_txt =
- "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
- "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";
-
// (Secure) delegation data; Delegation without DS record (and both NSEC
// and NSEC3 denying its existence)
-const char* const unsigned_delegation_txt =
- "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_nsec_txt =
- "unsigned-delegation.example.com. 3600 IN NSEC "
- "unsigned-delegation-optout.example.com. NS RRSIG NSEC\n";
// This one will be added on demand
const char* const unsigned_delegation_nsec3_txt =
"q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
-// Delegation without DS record, and no direct matching NSEC3 record
-const char* const unsigned_delegation_optout_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_optout_nsec_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NSEC "
- "*.uwild.example.com. NS RRSIG NSEC\n";
-
-// (Secure) delegation data; Delegation where the DS lookup will raise an
-// exception.
-const char* const bad_delegation_txt =
- "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";
-
-// Delegation from an unsigned parent. There's no DS, and there's no NSEC
-// or NSEC3 that proves it.
-const char* const nosec_delegation_txt =
- "nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.\n";
-
// A helper function that generates a textual representation of RRSIG RDATA
// for the given covered type. The resulting RRSIG may not necessarily make
// sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -314,58 +161,14 @@ textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
return (rrset);
}
-// This is a mock Zone Finder class for testing.
-// It is a derived class of ZoneFinder for the convenient of tests.
-// Its find() method emulates the common behavior of protocol compliant
-// ZoneFinder classes, but simplifies some minor cases and also supports broken
-// behavior.
-// For simplicity, most names are assumed to be "in zone"; delegations
-// to child zones are identified by the existence of non origin NS records.
-// Another special name is "dname.example.com". Query names under this name
-// will result in DNAME.
-// This mock zone doesn't handle empty non terminal nodes (if we need to test
-// such cases find() should have specialized code for it).
-class MockZoneFinder : public ZoneFinder {
+// Setup for faked NSEC3 hash used throughout this test.
+class TestNSEC3Hash : public NSEC3Hash {
+private:
+ typedef map<Name, string> NSEC3HashMap;
+ typedef NSEC3HashMap::value_type NSEC3HashPair;
+ NSEC3HashMap hash_map_;
public:
- MockZoneFinder() :
- origin_(Name("example.com")),
- bad_signed_delegation_name_("bad-delegation.example.com"),
- dname_name_("dname.example.com"),
- has_SOA_(true),
- has_apex_NS_(true),
- rrclass_(RRClass::IN()),
- include_rrsig_anyway_(false),
- use_nsec3_(false),
- nsec_name_(origin_),
- nsec3_fake_(NULL),
- nsec3_name_(NULL)
- {
- stringstream zone_stream;
- zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
- delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
- cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
- dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
- nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
- nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
- wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
- wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
- nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
- empty_prev_txt << nsec_empty_prev_txt <<
- nsec3_apex_txt << nsec3_www_txt <<
- signed_delegation_txt << signed_delegation_ds_txt <<
- unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
- unsigned_delegation_optout_txt <<
- unsigned_delegation_optout_nsec_txt <<
- bad_delegation_txt << nosec_delegation_txt;
-
- masterLoad(zone_stream, origin_, rrclass_,
- boost::bind(&MockZoneFinder::loadRRset, this, _1));
-
- empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
- RRClass::IN(),
- RRType::NSEC(),
- RRTTL(3600)));
-
+ TestNSEC3Hash() {
// (Faked) NSEC3 hash map. For convenience we use hardcoded built-in
// map instead of calculating and using actual hash.
// The used hash values are borrowed from RFC5155 examples (they are
@@ -411,6 +214,79 @@ public:
hash_map_[Name("www1.uwild.example.com")] =
"q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
}
+ virtual string calculate(const Name& name) const {
+ const NSEC3HashMap::const_iterator found = hash_map_.find(name);
+ if (found != hash_map_.end()) {
+ return (found->second);
+ }
+ isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+ << name);
+ }
+ virtual bool match(const rdata::generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const rdata::generic::NSEC3&) const {
+ return (true);
+ }
+};
+
+class TestNSEC3HashCreator : public isc::dns::NSEC3HashCreator {
+public:
+ TestNSEC3HashCreator() {}
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3PARAM&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(uint8_t, uint16_t, const uint8_t*, size_t) const {
+ return (new TestNSEC3Hash);
+ }
+};
+
+// This is a mock Zone Finder class for testing.
+// It is a derived class of ZoneFinder for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// ZoneFinder classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; delegations
+// to child zones are identified by the existence of non origin NS records.
+// Another special name is "dname.example.com". Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
+class MockZoneFinder : public ZoneFinder {
+public:
+ MockZoneFinder() :
+ origin_(Name("example.com")),
+ bad_signed_delegation_name_("bad-delegation.example.com"),
+ dname_name_("dname.example.com"),
+ has_SOA_(true),
+ has_apex_NS_(true),
+ rrclass_(RRClass::IN()),
+ include_rrsig_anyway_(false),
+ use_nsec3_(false),
+ nsec_name_(origin_),
+ nsec3_fake_(NULL),
+ nsec3_name_(NULL)
+ {
+ RRCollator collator(boost::bind(&MockZoneFinder::loadRRset, this, _1));
+ MasterLoader loader(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone",
+ origin_, rrclass_,
+ MasterLoaderCallbacks::getNullCallbacks(),
+ collator.getCallback());
+ loader.load();
+
+ empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
+ RRClass::IN(),
+ RRType::NSEC(),
+ RRTTL(3600)));
+ }
virtual isc::dns::Name getOrigin() const { return (origin_); }
virtual isc::dns::RRClass getClass() const { return (rrclass_); }
virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
@@ -513,6 +389,19 @@ private:
};
void loadRRset(RRsetPtr rrset) {
+ // For simplicity we dynamically generate RRSIGs and add them below.
+ // The RRSIG RDATA should be consistent with that defined in the
+ // zone file.
+ if (rrset->getType() == RRType::RRSIG()) {
+ return;
+ }
+
+ // NSEC3PARAM is not used in the mock data source (and it would confuse
+ // non-NSEC3 test cases).
+ if (rrset->getType() == RRType::NSEC3PARAM()) {
+ return;
+ }
+
if (rrset->getType() == RRType::NSEC3()) {
// NSEC3 should go to the dedicated table
nsec3_domains_[rrset->getName()][rrset->getType()] = rrset;
@@ -565,9 +454,7 @@ private:
// Enabled when not NULL
const FindNSEC3Result* nsec3_fake_;
const Name* nsec3_name_;
-public:
- // Public, to allow tests looking up the right names for something
- map<Name, string> hash_map_;
+ TestNSEC3Hash nsec3_hash_;
};
// A helper function that generates a new RRset based on "wild_rrset",
@@ -627,11 +514,7 @@ MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
// For brevity, we assume several things below: maps should have an
// expected entry when operator[] is used; maps are not empty.
for (int i = 0; i < labels; ++i) {
- const string hlabel = hash_map_[name.split(i, labels - i)];
- if (hlabel.empty()) {
- isc_throw(isc::Unexpected, "findNSEC3() hash failure for " <<
- name.split(i, labels - i));
- }
+ const string hlabel = nsec3_hash_.calculate(name.split(i, labels - i));
const Name hname = Name(hlabel + ".example.com");
// We don't use const_iterator so that we can use operator[] below
Domains::iterator found_domain = nsec3_domains_.lower_bound(hname);
@@ -893,10 +776,55 @@ MockZoneFinder::find(const Name& name, const RRType& type,
return (createContext(options,NXDOMAIN, RRsetPtr()));
}
-class QueryTest : public ::testing::Test {
+enum DataSrcType {
+ MOCK,
+ INMEMORY,
+ SQLITE3
+};
+
+boost::shared_ptr<ClientList>
+createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
+ boost::shared_ptr<ConfigurableClientList> list;
+ switch (type) {
+ case MOCK:
+ return (boost::shared_ptr<ClientList>(new SingletonList(client)));
+ case INMEMORY:
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+ "\"}}]"), true);
+ return (list);
+ case SQLITE3:
+ // The copy should succeed; if it failed we should notice it in
+ // test cases. However, we check the return value to avoid problems
+ // in some glibcs where "system()" is annotated with the "warn unused
+ // result" attribute.
+ EXPECT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied"));
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied") +
+ "\"}}]"), true);
+ return (list);
+ default:
+ isc_throw(isc::Unexpected,
+ "Unexpected data source type, should be a bug in test code");
+ }
+}
+
+class QueryTest : public ::testing::TestWithParam<DataSrcType> {
protected:
QueryTest() :
- list(memory_client),
qname(Name("www.example.com")), qclass(RRClass::IN()),
qtype(RRType::A()), response(Message::RENDER),
qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
@@ -906,22 +834,166 @@ protected:
"glue.delegation.example.com. 3600 IN RRSIG " +
getCommonRRSIGText("AAAA") + "\n" +
"noglue.example.com. 3600 IN RRSIG " +
- getCommonRRSIGText("A"))
+ getCommonRRSIGText("A")),
+ base_zone_file(TEST_OWN_DATA_BUILDDIR "/example-base.zone"),
+ nsec3_zone_file(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone"),
+ common_zone_file(TEST_OWN_DATA_BUILDDIR "/example-common-inc.zone"),
+ rrsets_added_(false)
{
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3hash_creator_);
+
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
// create and add a matching zone.
mock_finder = new MockZoneFinder();
memory_client.addZone(ZoneFinderPtr(mock_finder));
}
+
+ virtual void SetUp() {
+ // clear the commonly included zone file.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+
+ // We create data source clients here, not in the constructor, so this
+ // doesn't happen for derived test class. This also ensures the
+ // data source clients are configured after setting NSEC3 hash in case
+ // there's dependency.
+ list_ = createDataSrcClientList(GetParam(), memory_client);
+ }
+
+ virtual void TearDown() {
+ // make sure to clear the commonly included zone file to prevent
+ // any remaining contents from affecting the next test.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+ }
+
+ virtual ~QueryTest() {
+ // Make sure we reset the hash creator to the default
+ setNSEC3HashCreator(NULL);
+ }
+
+ void enableNSEC3(const vector<string>& rrsets_to_add) {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ switch (GetParam()) {
+ case MOCK:
+ mock_finder->setNSEC3Flag(true);
+ addRRsets(rrsets_to_add, *list_, "");
+ break;
+ case INMEMORY:
+ addRRsets(rrsets_to_add, *list_, nsec3_zone_file);
+ break;
+ case SQLITE3:
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied"));
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied") +
+ "\"}}]"), true);
+ addRRsets(rrsets_to_add, *new_list, "");
+ list_ = new_list;
+ break;
+ }
+ }
+
+ // A helper to add some RRsets to the test zone in the middle of a test
+ // case. The detailed behavior is quite different depending on the
+ // data source type, and not all parameters are used in all cases.
+ //
+ // Note: due to limitation of its implementation, this method doesn't
+ // work correctly for in-memory if called more than once. This condition
+ // is explicitly checked so any accidental violation would be noted as a
+ // test failure.
+ void addRRsets(const vector<string>& rrsets_to_add, ClientList& list,
+ const string& zone_file)
+ {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ ofstream ofs;
+
+ switch (GetParam()) {
+ case MOCK:
+ // directly add them to the mock data source; ignore the passed
+ // list.
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ mock_finder->addRecord(*it);
+ }
+ break;
+ case INMEMORY:
+ ASSERT_FALSE(rrsets_added_);
+ rrsets_added_ = true;
+
+ // dump the RRsets to be added to the placeholder of commonly
+ // included zone file (removing any existing contents) and do
+ // full reconfiguration.
+ ofs.open(common_zone_file.c_str(), ios_base::trunc);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ofs << *it << "\n";
+ ofs << createRRSIG(textToRRset(*it))->toText() << "\n";
+ }
+ ofs.close();
+
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ zone_file + "\"}}]"), true);
+ list_ = new_list;
+ break;
+ case SQLITE3:
+ const Name origin("example.com");
+ ZoneUpdaterPtr updater =
+ list.find(origin, true, false).dsrc_client_->
+ getUpdater(origin, false);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ConstRRsetPtr rrset = textToRRset(*it);
+ updater->addRRset(*rrset);
+ updater->addRRset(*createRRSIG(rrset));
+ }
+ updater->commit();
+ break;
+ }
+ }
+
+private:
+ // A helper for enableNSEC3, creating an RRSIG RRset for the corresponding
+ // non-sig RRset, using the commonly used parameters.
+ static ConstRRsetPtr createRRSIG(ConstRRsetPtr rrset) {
+ RRsetPtr sig_rrset(new RRset(rrset->getName(), rrset->getClass(),
+ RRType::RRSIG(), rrset->getTTL()));
+ sig_rrset->addRdata(generic::RRSIG(
+ getCommonRRSIGText(rrset->getType().
+ toText())));
+ return (sig_rrset);
+ }
+
+protected:
MockZoneFinder* mock_finder;
// We use InMemoryClient here. We could have some kind of mock client
// here, but historically, the Query supported only InMemoryClient
// (originally named MemoryDataSrc) and was tested with it, so we keep
// it like this for now.
InMemoryClient memory_client;
- // A wrapper client list to wrap the single data source.
- SingletonList list;
+
+ boost::shared_ptr<ClientList> list_;
const Name qname;
const RRClass qclass;
const RRType qtype;
@@ -930,6 +1002,37 @@ protected:
const uint16_t query_code;
const string ns_addrs_and_sig_txt; // convenient shortcut
Query query;
+ TestNSEC3Hash nsec3_hash_;
+ vector<string> rrsets_to_add_;
+ const string base_zone_file;
+private:
+ const string nsec3_zone_file;
+ const string common_zone_file;
+ const TestNSEC3HashCreator nsec3hash_creator_;
+ bool rrsets_added_;
+};
+
+// We test the in-memory and SQLite3 data source implementations. SQLite3
+// will require a loadable module, which doesn't work with static link for
+// all platforms.
+INSTANTIATE_TEST_CASE_P(, QueryTest,
+ ::testing::Values(MOCK, INMEMORY
+#ifndef USE_STATIC_LINK
+ , SQLITE3
+#endif
+ ));
+
+// This inherit the QueryTest cases except for the parameterized setup;
+// it's intended to be used selected test cases that only work for mock
+// data sources either because of some limitation or because of the type of
+// tests (relying on a "broken" data source behavior that can't happen with
+// production data source implementations).
+class QueryTestForMockOnly : public QueryTest {
+protected:
+ // Override SetUp() to avoid parameterized setup
+ virtual void SetUp() {
+ list_ = createDataSrcClientList(MOCK, memory_client);
+ }
};
// A wrapper to check resulting response message commonly used in
@@ -969,7 +1072,7 @@ responseCheck(Message& response, const isc::dns::Rcode& rcode,
}
}
-TEST_F(QueryTest, noZone) {
+TEST_P(QueryTest, noZone) {
// There's no zone in the memory datasource. So the response should have
// REFUSED.
InMemoryClient empty_memory_client;
@@ -979,15 +1082,15 @@ TEST_F(QueryTest, noZone) {
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
-TEST_F(QueryTest, exactMatch) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatch) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchMultipleQueries) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatchMultipleQueries) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -996,27 +1099,26 @@ TEST_F(QueryTest, exactMatchMultipleQueries) {
response.clear(isc::dns::Message::RENDER);
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
SCOPED_TRACE("Second query");
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchIgnoreSIG) {
+TEST_P(QueryTest, exactMatchIgnoreSIG) {
// Check that we do not include the RRSIG when not requested even when
// we receive it from the data source.
mock_finder->setIncludeRRSIGAnyway(true);
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, dnssecPositive) {
+TEST_P(QueryTest, dnssecPositive) {
// Just like exactMatch, but the signatures should be included as well
- EXPECT_NO_THROW(query.process(list, qname, qtype, response,
- true));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response, true));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
(www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
@@ -1031,10 +1133,10 @@ TEST_F(QueryTest, dnssecPositive) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, exactAddrMatch) {
+TEST_P(QueryTest, exactAddrMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("noglue.example.com"),
qtype, response));
@@ -1044,10 +1146,10 @@ TEST_F(QueryTest, exactAddrMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexNSMatch) {
+TEST_P(QueryTest, apexNSMatch) {
// find match rrset, omit authority data which has already been provided
// in the answer section from the authority section.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::NS(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
@@ -1055,10 +1157,16 @@ TEST_F(QueryTest, apexNSMatch) {
}
// test type any query logic
-TEST_F(QueryTest, exactAnyMatch) {
+TEST_P(QueryTest, exactAnyMatch) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("noglue.example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("noglue.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
@@ -1069,10 +1177,10 @@ TEST_F(QueryTest, exactAnyMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexAnyMatch) {
+TEST_P(QueryTest, apexAnyMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
(string(soa_txt) + string(zone_ns_txt) +
@@ -1080,23 +1188,23 @@ TEST_F(QueryTest, apexAnyMatch) {
NULL, ns_addrs_txt, mock_finder->getOrigin());
}
-TEST_F(QueryTest, mxANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("mx.example.com"),
+TEST_P(QueryTest, mxANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("mx.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
(string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
(string(ns_addrs_txt) + string(www_a_txt)).c_str());
}
-TEST_F(QueryTest, glueANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("delegation.example.com"),
+TEST_P(QueryTest, glueANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("delegation.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, nodomainANY) {
- EXPECT_NO_THROW(query.process(list, Name("nxdomain.example.com"),
+TEST_P(QueryTest, nodomainANY) {
+ EXPECT_NO_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -1105,17 +1213,20 @@ TEST_F(QueryTest, nodomainANY) {
// This tests that when we need to look up Zone's apex NS records for
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
-TEST_F(QueryTest, noApexNS) {
+//
+// This only works with mock data source (for production datasrc the
+// post-load would reject such a zone)
+TEST_F(QueryTestForMockOnly, noApexNS) {
// Disable apex NS record
mock_finder->setApexNSFlag(false);
- EXPECT_THROW(query.process(list, Name("noglue.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("noglue.example.com"), qtype,
response), Query::NoApexNS);
// We don't look into the response, as it threw
}
-TEST_F(QueryTest, delegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, delegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
qtype, response));
@@ -1123,19 +1234,19 @@ TEST_F(QueryTest, delegation) {
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, delegationWithDNSSEC) {
+TEST_P(QueryTest, delegationWithDNSSEC) {
// Similar to the previous one, but with requesting DNSSEC.
// In this case the parent zone would behave as unsigned, so the result
// should be just like non DNSSEC delegation.
- query.process(list, Name("www.nosec-delegation.example.com"),
+ query.process(*list_, Name("www.nosec-delegation.example.com"),
qtype, response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 1, 0,
NULL, nosec_delegation_txt, NULL);
}
-TEST_F(QueryTest, secureDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.signed-delegation.example.com"),
qtype, response, true));
@@ -1149,8 +1260,8 @@ TEST_F(QueryTest, secureDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureUnsignedDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true));
@@ -1164,33 +1275,33 @@ TEST_F(QueryTest, secureUnsignedDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3) {
// Similar to the previous case, but the zone is signed with NSEC3,
// and this delegation is NOT an optout.
- const Name insecurechild_name("unsigned-delegation.example.com");
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
// The response should contain the NS and matching NSEC3 with its RRSIG
+ const Name insecurechild_name("unsigned-delegation.example.com");
responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
NULL,
(string(unsigned_delegation_txt) +
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[insecurechild_name] +
+ nsec3_hash_.calculate(insecurechild_name) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
// Similar to the previous case, but the delegation is an optout.
- mock_finder->setNSEC3Flag(true);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
@@ -1202,44 +1313,46 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
NULL,
(string(unsigned_delegation_txt) +
string(nsec3_apex_txt) +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com")] +
+ nsec3_hash_.calculate(Name("www.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, badSecureDelegation) {
+TEST_F(QueryTestForMockOnly, badSecureDelegation) {
+ // This is a broken data source scenario; works only with mock.
+
// Test whether exception is raised if DS query at delegation results in
// something different than SUCCESS or NXRRSET
- EXPECT_THROW(query.process(list,
+ EXPECT_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response, true), Query::BadDS);
// But only if DNSSEC is requested (it shouldn't even try to look for
// the DS otherwise)
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response));
}
-TEST_F(QueryTest, nxdomain) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, nxdomain) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC) {
+TEST_P(QueryTest, nxdomainWithNSEC) {
// NXDOMAIN with DNSSEC proof. We should have SOA, NSEC that proves
// NXDOMAIN and NSEC that proves nonexistence of matching wildcard,
// as well as their RRSIGs.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response, true));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
@@ -1255,12 +1368,18 @@ TEST_F(QueryTest, nxdomainWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC2) {
+TEST_P(QueryTest, nxdomainWithNSEC2) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about no_txt. In this case the best possible wildcard
// is derived from the next domain of the NSEC that proves NXDOMAIN, and
// the NSEC to provide the non existence of wildcard is different from
// the first NSEC.
- query.process(list, Name("(.no.example.com"), qtype, response,
+ query.process(*list_, Name("(.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1275,10 +1394,17 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
+TEST_P(QueryTest, nxdomainWithNSECDuplicate) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work. This is probably the same type of bug
+ // as nxdomainWithNSEC2 (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about nz_txt. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence of wildcard.
- query.process(list, Name("nx.no.example.com"), qtype, response,
+ query.process(*list_, Name("nx.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
NULL, (string(soa_txt) +
@@ -1290,52 +1416,62 @@ TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC1) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC1) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with non NSEC RR.
mock_finder->setNSECResult(Name("badnsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("badnsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("badnsec.example.com"),
qtype, response, true),
std::bad_cast);
}
-TEST_F(QueryTest, nxdomainBadNSEC2) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC2) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with an empty NSEC RR.
mock_finder->setNSECResult(Name("emptynsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("emptynsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("emptynsec.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC3) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns SUCCESS. it should be NXDOMAIN.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC4) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC4) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC5) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC5) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns non NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
// This is a bit odd, but we'll simply include the returned RRset.
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1350,28 +1486,30 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC6) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC6) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxrrset) {
- EXPECT_NO_THROW(query.process(list, Name("www.example.com"),
+TEST_P(QueryTest, nxrrset) {
+ EXPECT_NO_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC) {
+TEST_P(QueryTest, nxrrsetWithNSEC) {
// NXRRSET with DNSSEC proof. We should have SOA, NSEC that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1383,7 +1521,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, emptyNameWithNSEC) {
+TEST_P(QueryTest, emptyNameWithNSEC) {
// Empty non terminal with DNSSEC proof. This is one of the cases of
// Section 3.1.3.2 of RFC4035.
// mx.example.com. NSEC ).no.example.com. proves no.example.com. is a
@@ -1392,7 +1530,7 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
// exact match), so we only need one NSEC.
// From the point of the Query::process(), this is actually no different
// from the other NXRRSET case, but we check that explicitly just in case.
- query.process(list, Name("no.example.com"), RRType::A(),
+ query.process(*list_, Name("no.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1404,11 +1542,11 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithoutNSEC) {
+TEST_P(QueryTest, nxrrsetWithoutNSEC) {
// NXRRSET with DNSSEC proof requested, but there's no NSEC at that node.
// This is an unexpected event (if the zone is supposed to be properly
// signed with NSECs), but we accept and ignore the oddity.
- query.process(list, Name("nonsec.example.com"), RRType::TXT(),
+ query.process(*list_, Name("nonsec.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -1417,10 +1555,10 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC) {
+TEST_P(QueryTest, wildcardNSEC) {
// The qname matches *.wild.example.com. The response should contain
// an NSEC that proves the non existence of a closer name.
- query.process(list, Name("www.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "www") +
@@ -1437,10 +1575,10 @@ TEST_F(QueryTest, wildcardNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC) {
+TEST_P(QueryTest, CNAMEwildNSEC) {
// Similar to the previous case, but the matching wildcard record is
// CNAME.
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
@@ -1453,17 +1591,17 @@ TEST_F(QueryTest, CNAMEwildNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC3) {
+TEST_P(QueryTest, wildcardNSEC3) {
// Similar to wildcardNSEC, but the zone is signed with NSEC3.
// The next closer is y.wild.example.com, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the apex.
- mock_finder->setNSEC3Flag(true);
-
- // This is NSEC3 for wild.example.com, which will be used in the middle
+ //
+ // Adding NSEC3 for wild.example.com, which will be used in the middle
// of identifying the next closer name.
- mock_finder->addRecord(nsec3_atwild_txt);
+ rrsets_to_add_.push_back(nsec3_atwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("x.y.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("x.y.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "x.y") +
@@ -1474,35 +1612,37 @@ TEST_F(QueryTest, wildcardNSEC3) {
getCommonRRSIGText("NS") + "\n" +
// NSEC3 for the wildcard proof and its RRSIG
string(nsec3_apex_txt) +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC3) {
+TEST_P(QueryTest, CNAMEwildNSEC3) {
// Similar to CNAMEwildNSEC, but with NSEC3.
// The next closer is qname itself, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the www.example.com.
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(nsec3_atcnamewild_txt);
+ rrsets_to_add_.push_back(nsec3_atcnamewild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
string("www.cnamewild.example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("CNAME") + "\n").c_str(),
(string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, badWildcardNSEC3) {
+TEST_F(QueryTestForMockOnly, badWildcardNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNSEC3, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1511,46 +1651,58 @@ TEST_F(QueryTest, badWildcardNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, badWildcardProof1) {
+TEST_F(QueryTestForMockOnly, badWildcardProof1) {
+ // This is a broken data source scenario; works only with mock.
+
// Unexpected case in wildcard proof: ZoneFinder::find() returns SUCCESS
// when NXDOMAIN is expected.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof2) {
+TEST_F(QueryTestForMockOnly, badWildcardProof2) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof3) {
+TEST_F(QueryTestForMockOnly, badWildcardProof3) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// NXRRSET on WILDCARD with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence RRSETs of wildcard.
- query.process(list, Name("www.wild.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1562,12 +1714,18 @@ TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// WILDCARD + NXRRSET with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence RRSETs of
// wildcard.
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1582,15 +1740,15 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC3) {
// Similar to the previous case, but providing NSEC3 proofs according to
// RFC5155 Section 7.2.5.
- mock_finder->addRecord(nsec3_wild_txt);
- mock_finder->addRecord(nsec3_uwild_txt);
- mock_finder->setNSEC3Flag(true);
+ rrsets_to_add_.push_back(nsec3_wild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
@@ -1599,23 +1757,25 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_uwild_txt) +
- mock_finder->hash_map_[Name("uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(nsec3_wild_txt) +
- mock_finder->hash_map_[Name("*.uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("*.uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Collision) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1624,15 +1784,17 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Broken) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNxrrsetWithNSEC3, but no matching NSEC3 for the
// wildcard name will be returned. This shouldn't happen in a reasonably
- // NSEC-signed zone, and should result in an exception.
+ // NSEC3-signed zone, and should result in an exception.
mock_finder->setNSEC3Flag(true);
const Name wname("*.uwild.example.com.");
ZoneFinder::FindNSEC3Result nsec3(false, 0, textToRRset(nsec3_apex_txt),
@@ -1641,16 +1803,16 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
mock_finder->addRecord(nsec3_wild_txt);
mock_finder->addRecord(nsec3_uwild_txt);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardEmptyWithNSEC) {
+TEST_P(QueryTest, wildcardEmptyWithNSEC) {
// Empty WILDCARD with DNSSEC proof. We should have SOA, NSEC that proves
// the NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence wildcard.
- query.process(list, Name("a.t.example.com"), RRType::A(),
+ query.process(*list_, Name("a.t.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1669,24 +1831,26 @@ TEST_F(QueryTest, wildcardEmptyWithNSEC) {
* This tests that when there's no SOA and we need a negative answer. It should
* throw in that case.
*/
-TEST_F(QueryTest, noSOA) {
+TEST_F(QueryTestForMockOnly, noSOA) {
+ // This is a broken data source scenario; works only with mock.
+
// disable zone's SOA RR.
mock_finder->setSOAFlag(false);
// The NX Domain
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response), Query::NoSOA);
// Of course, we don't look into the response, as it throwed
// NXRRSET
- EXPECT_THROW(query.process(list, Name("nxrrset.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxrrset.example.com"),
qtype, response), Query::NoSOA);
}
-TEST_F(QueryTest, noMatchZone) {
+TEST_P(QueryTest, noMatchZone) {
// there's a zone in the memory datasource but it doesn't match the qname.
// should result in REFUSED.
- query.process(list, Name("example.org"), qtype, response);
+ query.process(*list_, Name("example.org"), qtype, response);
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
@@ -1696,8 +1860,8 @@ TEST_F(QueryTest, noMatchZone) {
* The MX RRset has two RRs, one pointing to a known domain with
* A record, other to unknown out of zone one.
*/
-TEST_F(QueryTest, MX) {
- query.process(list, Name("mx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MX) {
+ query.process(*list_, Name("mx.example.com"), RRType::MX(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
@@ -1710,8 +1874,8 @@ TEST_F(QueryTest, MX) {
*
* This should not trigger the additional processing for the exchange.
*/
-TEST_F(QueryTest, MXAlias) {
- query.process(list, Name("cnamemx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MXAlias) {
+ query.process(*list_, Name("cnamemx.example.com"), RRType::MX(),
response);
// there shouldn't be no additional RRs for the exchanges (we have 3
@@ -1730,69 +1894,69 @@ TEST_F(QueryTest, MXAlias) {
* TODO: We currently don't do chaining, so only the CNAME itself should be
* returned.
*/
-TEST_F(QueryTest, CNAME) {
- query.process(list, Name("cname.example.com"), RRType::A(),
+TEST_P(QueryTest, CNAME) {
+ query.process(*list_, Name("cname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME) {
+TEST_P(QueryTest, explicitCNAME) {
// same owner name as the CNAME test but explicitly query for CNAME RR.
// expect the same response as we don't provide a full chain yet.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_RRSET) {
+TEST_P(QueryTest, CNAME_NX_RRSET) {
// Leads to www.example.com, it doesn't have TXT
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
- query.process(list, Name("cname.example.com"), RRType::TXT(),
+ query.process(*list_, Name("cname.example.com"), RRType::TXT(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+TEST_P(QueryTest, explicitCNAME_NX_RRSET) {
// same owner name as the NXRRSET test but explicitly query for CNAME RR.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+TEST_P(QueryTest, CNAME_NX_DOMAIN) {
// Leads to nxdomain.example.com
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional,
// RCODE being NXDOMAIN.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
// RCODE being NOERROR.
- query.process(list, Name("cnamenxdom.example.com"), RRType::A(),
+ query.process(*list_, Name("cnamenxdom.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_nxdom_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+TEST_P(QueryTest, explicitCNAME_NX_DOMAIN) {
// same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
- query.process(list, Name("cnamenxdom.example.com"),
+ query.process(*list_, Name("cnamenxdom.example.com"),
RRType::CNAME(), response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_OUT) {
+TEST_P(QueryTest, CNAME_OUT) {
/*
* This leads out of zone. This should have only the CNAME even
* when we do chaining.
@@ -1801,16 +1965,16 @@ TEST_F(QueryTest, CNAME_OUT) {
* Then the same test should be done with .org included there and
* see what it does (depends on what we want to do)
*/
- query.process(list, Name("cnameout.example.com"), RRType::A(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_out_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_OUT) {
+TEST_P(QueryTest, explicitCNAME_OUT) {
// same owner name as the OUT test but explicitly query for CNAME RR.
- query.process(list, Name("cnameout.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1825,8 +1989,8 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
* as well. This includes tests pointing inside the zone, outside the zone,
* pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
*/
-TEST_F(QueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1841,8 +2005,8 @@ TEST_F(QueryTest, DNAME) {
* ANY is handled specially sometimes. We check it is not the case with
* DNAME.
*/
-TEST_F(QueryTest, DNAME_ANY) {
- query.process(list, Name("www.dname.example.com"), RRType::ANY(),
+TEST_P(QueryTest, DNAME_ANY) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::ANY(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1850,8 +2014,8 @@ TEST_F(QueryTest, DNAME_ANY) {
}
// Test when we ask for DNAME explicitly, it does no synthetizing.
-TEST_F(QueryTest, explicitDNAME) {
- query.process(list, Name("dname.example.com"), RRType::DNAME(),
+TEST_P(QueryTest, explicitDNAME) {
+ query.process(*list_, Name("dname.example.com"), RRType::DNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1862,8 +2026,8 @@ TEST_F(QueryTest, explicitDNAME) {
* Request a RRset at the domain with DNAME. It should not synthetize
* the CNAME, it should return the RRset.
*/
-TEST_F(QueryTest, DNAME_A) {
- query.process(list, Name("dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME_A) {
+ query.process(*list_, Name("dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1874,8 +2038,8 @@ TEST_F(QueryTest, DNAME_A) {
* Request a RRset at the domain with DNAME that is not there (NXRRSET).
* It should not synthetize the CNAME.
*/
-TEST_F(QueryTest, DNAME_NX_RRSET) {
- EXPECT_NO_THROW(query.process(list, Name("dname.example.com"),
+TEST_P(QueryTest, DNAME_NX_RRSET) {
+ EXPECT_NO_THROW(query.process(*list_, Name("dname.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
@@ -1887,7 +2051,7 @@ TEST_F(QueryTest, DNAME_NX_RRSET) {
* however, should not throw (and crash the server), but respond with
* YXDOMAIN.
*/
-TEST_F(QueryTest, LongDNAME) {
+TEST_P(QueryTest, LongDNAME) {
// A name that is as long as it can be
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
@@ -1895,7 +2059,7 @@ TEST_F(QueryTest, LongDNAME) {
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
@@ -1907,14 +2071,14 @@ TEST_F(QueryTest, LongDNAME) {
* This tests that we don't reject valid one by some kind of off by
* one mistake.
*/
-TEST_F(QueryTest, MaxLenDNAME) {
+TEST_P(QueryTest, MaxLenDNAME) {
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
// Check the answer is OK
@@ -1963,7 +2127,10 @@ nsec3Check(bool expected_matched, uint8_t expected_labels,
actual_rrsets.end());
}
-TEST_F(QueryTest, findNSEC3) {
+TEST_F(QueryTestForMockOnly, findNSEC3) {
+ // This test is intended to test the mock data source behavior; no need
+ // to do it for others.
+
// In all test cases in the recursive mode, the closest encloser is the
// apex, and result's closest_labels should be the number of apex labels.
// (In non recursive mode closest_labels should be the # labels of the
@@ -1975,7 +2142,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, non recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), false));
}
@@ -1983,7 +2150,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), true));
}
@@ -1992,7 +2159,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, non recursive");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"),
false));
}
@@ -2002,7 +2169,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"), true));
}
@@ -2012,7 +2179,8 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, next closer != qname");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" +
+ nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nx.domain.example.com"),
true));
@@ -2022,14 +2190,14 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("largest");
nsec3Check(false, 4,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain2.example.com"),
false));
}
{
SCOPED_TRACE("smallest");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain3.example.com"),
false));
}
@@ -2100,14 +2268,17 @@ private:
const bool have_ds_;
};
-TEST_F(QueryTest, dsAboveDelegation) {
+TEST_F(QueryTestForMockOnly, dsAboveDelegation) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
Name("delegation.example.com"))));
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
RRType::DS(), response, true));
@@ -2121,7 +2292,7 @@ TEST_F(QueryTest, dsAboveDelegation) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, dsAboveDelegationNoData) {
+TEST_P(QueryTest, dsAboveDelegationNoData) {
// Similar to the previous case, but the query is for an unsigned zone
// (which doesn't have a DS at the parent). The response should be a
// "no data" error. The query should still be handled at the parent.
@@ -2131,7 +2302,7 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("unsigned-delegation.example.com"),
RRType::DS(), response, true));
@@ -2148,8 +2319,8 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// This one checks that type-DS query results in a "no data" response
// when it happens to be sent to the child zone, as described in RFC 4035,
// section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
-TEST_F(QueryTest, dsBelowDelegation) {
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegation) {
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -2164,9 +2335,10 @@ TEST_F(QueryTest, dsBelowDelegation) {
// Similar to the previous case, but even more pathological: the DS somehow
// exists in the child zone. The Query module should still return SOA.
// In our implementation NSEC/NSEC3 isn't attached in this case.
-TEST_F(QueryTest, dsBelowDelegationWithDS) {
- mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegationWithDS) {
+ rrsets_to_add_.push_back(zone_ds_txt);
+ addRRsets(rrsets_to_add_, *list_, base_zone_file);
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -2178,16 +2350,16 @@ TEST_F(QueryTest, dsBelowDelegationWithDS) {
// DS query received at a completely irrelevant (neither parent nor child)
// server. It should just like the "noZone" test case, but DS query involves
// special processing, so we test it explicitly.
-TEST_F(QueryTest, dsNoZone) {
- query.process(list, Name("example"), RRType::DS(), response,
+TEST_P(QueryTest, dsNoZone) {
+ query.process(*list_, Name("example"), RRType::DS(), response,
true);
responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
}
// DS query for a "grandchild" zone. This should result in normal
// delegation (unless this server also has authority of the grandchild zone).
-TEST_F(QueryTest, dsAtGrandParent) {
- query.process(list, Name("grand.delegation.example.com"),
+TEST_P(QueryTest, dsAtGrandParent) {
+ query.process(*list_, Name("grand.delegation.example.com"),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
(string(delegation_txt) + string(delegation_ds_txt) +
@@ -2201,12 +2373,15 @@ TEST_F(QueryTest, dsAtGrandParent) {
// side and should result in no data with SOA. Note that the server doesn't
// have authority for the "parent". Unlike the dsAboveDelegation test case
// the query should be handled in the child zone, not in the grandparent.
-TEST_F(QueryTest, dsAtGrandParentAndChild) {
+TEST_F(QueryTestForMockOnly, dsAtGrandParentAndChild) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
const Name childname("grand.delegation.example.com");
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(childname)));
- query.process(list, childname, RRType::DS(), response, true);
+ query.process(*list_, childname, RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
childname.toText() + " 3600 IN RRSIG " +
@@ -2220,11 +2395,14 @@ TEST_F(QueryTest, dsAtGrandParentAndChild) {
// DS query for the root name (quite pathological). Since there's no "parent",
// the query will be handled in the root zone anyway, and should (normally)
// result in no data.
-TEST_F(QueryTest, dsAtRoot) {
+TEST_F(QueryTestForMockOnly, dsAtRoot) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
// Pretend to be a root server.
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME())));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(". 3600 IN SOA . . 0 0 0 0 0\n") +
@@ -2237,11 +2415,14 @@ TEST_F(QueryTest, dsAtRoot) {
// Even more pathological case: A faked root zone actually has its own DS
// query. How we respond wouldn't matter much in practice, but check if
// it behaves as it's intended. This implementation should return the DS.
-TEST_F(QueryTest, dsAtRootWithDS) {
+TEST_F(QueryTestForMockOnly, dsAtRootWithDS) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME(),
true)));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
@@ -2253,19 +2434,19 @@ TEST_F(QueryTest, dsAtRootWithDS) {
}
// Check the signature is present when an NXRRSET is returned
-TEST_F(QueryTest, nxrrsetWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
// NXRRSET with DNSSEC proof. We should have SOA, NSEC3 that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_www_txt) + "\n" +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
@@ -2273,7 +2454,9 @@ TEST_F(QueryTest, nxrrsetWithNSEC3) {
// Check the exception is correctly raised when the NSEC3 thing isn't in the
// zone
-TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+TEST_F(QueryTestForMockOnly, nxrrsetMissingNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
mock_finder->setNSEC3Flag(true);
// We just need it to return false for "matched". This indicates
// there's no exact match for NSEC3 on www.example.com.
@@ -2281,67 +2464,67 @@ TEST_F(QueryTest, nxrrsetMissingNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, but does have a matching NSEC3 record
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation.example.com."),
+ query.process(*list_, Name("unsigned-delegation.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, and no directly matching NSEC3 record
// So the response should contain closest encloser proof (and the
// 'next closer' should have opt-out set, though that is not
// actually checked)
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation-optout.example.com."),
+ query.process(*list_, Name("unsigned-delegation-optout.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
+TEST_P(QueryTest, nxdomainWithNSEC3Proof) {
// Name Error (NXDOMAIN) case with NSEC3 proof per RFC5155 Section 7.2.2.
- // Enable NSEC3
- mock_finder->setNSEC3Flag(true);
// This will be the covering NSEC3 for the next closer
- mock_finder->addRecord(nsec3_uwild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
// This will be the covering NSEC3 for the possible wildcard
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ // Enable NSEC3
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
// SOA + its RRSIG
@@ -2350,24 +2533,26 @@ TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_uwild_txt) + "\n" +
- mock_finder->hash_map_[Name("uwild.example.com")] +
+ nsec3_hash_.calculate(Name("uwild.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[
- Name("unsigned-delegation.example.com")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadNextNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -2376,12 +2561,14 @@ TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadWildcardNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to nxdomainWithNSEC3Proof, but let findNSEC3() return a matching
// NSEC3 for the possible wildcard name, emulating run-time collision.
// This should result in BadNSEC3 exception.
@@ -2395,7 +2582,7 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3, &wname);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true),
Query::BadNSEC3);
}
@@ -2403,10 +2590,13 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
// The following are tentative tests until we really add tests for the
// query logic for these cases. At that point it's probably better to
// clean them up.
-TEST_F(QueryTest, emptyNameWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
- ZoneFinderContextPtr result = mock_finder->find(
- Name("no.example.com"), RRType::A(), ZoneFinder::FIND_DNSSEC);
+TEST_P(QueryTest, emptyNameWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
+ const Name qname("no.example.com");
+ ASSERT_TRUE(list_->find(qname).finder_);
+ ZoneFinderContextPtr result =
+ list_->find(qname).finder_->find(qname, RRType::A(),
+ ZoneFinder::FIND_DNSSEC);
EXPECT_EQ(ZoneFinder::NXRRSET, result->code);
EXPECT_FALSE(result->rrset);
EXPECT_TRUE(result->isNSEC3Signed());
@@ -2447,7 +2637,9 @@ loadRRsetVector() {
loadRRsetVectorCallback);
}
-TEST_F(QueryTest, DuplicateNameRemoval) {
+// Note: this is an independent test; don't have to be in the QueryTest
+// fixture.
+TEST(QueryTestSingle, DuplicateNameRemoval) {
// Load some RRsets into the master vector.
loadRRsetVector();
diff --git a/src/bin/auth/tests/testdata/.gitignore b/src/bin/auth/tests/testdata/.gitignore
index b2e0e50..37acc8a 100644
--- a/src/bin/auth/tests/testdata/.gitignore
+++ b/src/bin/auth/tests/testdata/.gitignore
@@ -6,3 +6,13 @@
/shortanswer_fromWire.wire
/simplequery_fromWire.wire
/simpleresponse_fromWire.wire
+/example-base.sqlite3
+/example-base.sqlite3.copied
+/example-base.zone
+/example-base.zone
+/example-common-inc.zone
+/example-nsec3-inc.zone
+/example-nsec3.sqlite3
+/example-nsec3.sqlite3.copied
+/example-nsec3.zone
+/example.zone
diff --git a/src/bin/auth/tests/testdata/Makefile.am b/src/bin/auth/tests/testdata/Makefile.am
index a4ea1a5..fed498a 100644
--- a/src/bin/auth/tests/testdata/Makefile.am
+++ b/src/bin/auth/tests/testdata/Makefile.am
@@ -1,4 +1,6 @@
-CLEANFILES = *.wire
+CLEANFILES = *.wire *.copied
+CLEANFILES += example-base.sqlite3 example-nsec3.sqlite3
+CLEANFILES += example-common-inc.zone
BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
@@ -24,5 +26,8 @@ EXTRA_DIST += example.com
EXTRA_DIST += example.zone
EXTRA_DIST += example.sqlite3
+EXTRA_DIST += example-base-inc.zone example-nsec3-inc.zone
+EXTRA_DIST += example-common-inc-template.zone
+
.spec.wire:
$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/bin/auth/tests/testdata/example-base-inc.zone b/src/bin/auth/tests/testdata/example-base-inc.zone
new file mode 100644
index 0000000..bbcbef1
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base-inc.zone
@@ -0,0 +1,236 @@
+;; This file defines a set of RRs commonly used in query tests in the
+;; form of standard master zone file.
+;;
+;; It's a sequence of the following pattern:
+;; ;var=<var_name>
+;; RR_1
+;; RR_2
+;; ..
+;; RR_n
+;;
+;; where var_name is a string that can be used as a variable name in a
+;; C/C++ source file or an empty string. RR_x is a single-line
+;; textual representation of an arbitrary DNS RR.
+;;
+;; If var_name is non empty, the generator script will define a C
+;; variable of C-string type for that set of RRs so that it can be referred
+;; to in the test source file.
+;;
+;; Note that lines beginning ';var=' is no different from other
+;; comment lines as a zone file. It has special meaning only for the
+;; generator script. Obviously, real comment lines cannot begin with
+;; ';var=' (which should be less likely to happen in practice though).
+;;
+;; These RRs will be loaded into in-memory data source in that order.
+;; Note that it may impose stricter restriction on the order of RRs.
+;; In general, each RRset of the same name and type and its RRSIG (if
+;; any) is expected to be grouped.
+
+;var=soa_txt
+example.com. 3600 IN SOA . . 1 0 0 0 0
+;var=zone_ns_txt
+example.com. 3600 IN NS glue.delegation.example.com.
+example.com. 3600 IN NS noglue.example.com.
+example.com. 3600 IN NS example.net.
+
+;var=
+example.com. 3600 IN RRSIG SOA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+example.com. 3600 IN RRSIG NS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Note: the position of the next RR is tricky. It's placed here to
+;; be grouped with the subsequent A RR of the name. But we also want
+;; to group the A RR with other RRs of a different owner name, so the RRSIG
+;; cannot be placed after the A RR. The empty 'var=' specification is
+;; not necessary here, but in case we want to reorganize the ordering
+;; (in which case it's more likely to be needed), we keep it here.
+;var=
+noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=ns_addrs_txt
+noglue.example.com. 3600 IN A 192.0.2.53
+glue.delegation.example.com. 3600 IN A 192.0.2.153
+glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
+
+;var=
+glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=delegation_txt
+delegation.example.com. 3600 IN NS glue.delegation.example.com.
+delegation.example.com. 3600 IN NS noglue.example.com.
+delegation.example.com. 3600 IN NS cname.example.com.
+delegation.example.com. 3600 IN NS example.org.
+
+;var=
+delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Borrowed from the RFC4035
+;var=delegation_ds_txt
+delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
+;var=mx_txt
+mx.example.com. 3600 IN MX 10 www.example.com.
+mx.example.com. 3600 IN MX 20 mailer.example.org.
+mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
+;var=www_a_txt
+www.example.com. 3600 IN A 192.0.2.80
+
+;var=
+www.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cname_txt
+cname.example.com. 3600 IN CNAME www.example.com.
+;var=cname_nxdom_txt
+cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
+;; CNAME Leading out of zone
+;var=cname_out_txt
+cnameout.example.com. 3600 IN CNAME www.example.org.
+;; The DNAME to do tests against
+;var=dname_txt
+dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
+;; Some data at the dname node (allowed by RFC 2672)
+;var=dname_a_txt
+dname.example.com. 3600 IN A 192.0.2.5
+;; This is not inside the zone, this is created at runtime
+;; www.dname.example.com. 3600 IN CNAME www.somethinglong.dnametarget.example.com.
+;; The rest of data won't be referenced from the test cases.
+;var=other_zone_rrs
+cnamemailer.example.com. 3600 IN CNAME www.example.com.
+cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
+mx.delegation.example.com. 3600 IN A 192.0.2.100
+;; Wildcards
+;var=wild_txt
+*.wild.example.com. 3600 IN A 192.0.2.7
+;var=nsec_wild_txt
+*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
+
+;var=
+*.wild.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.wild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cnamewild_txt
+*.cnamewild.example.com. 3600 IN CNAME www.example.org.
+;var=nsec_cnamewild_txt
+*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
+
+;var=
+*.cnamewild.example.com. 3600 IN RRSIG CNAME 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.cnamewild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Wildcard_nxrrset
+;var=wild_txt_nxrrset
+*.uwild.example.com. 3600 IN A 192.0.2.9
+;var=nsec_wild_txt_nxrrset
+*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
+;var=
+*.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=wild_txt_next
+www.uwild.example.com. 3600 IN A 192.0.2.11
+;var=
+www.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_wild_txt_next
+www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
+;; Wildcard empty
+;var=empty_txt
+b.*.t.example.com. 3600 IN A 192.0.2.13
+;var=nsec_empty_txt
+b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
+
+;var=
+b.*.t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=empty_prev_txt
+t.example.com. 3600 IN A 192.0.2.15
+;var=nsec_empty_prev_txt
+t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
+
+;var=
+t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Used in NXDOMAIN proof test. We are going to test some unusual case where
+;; the best possible wildcard is below the "next domain" of the NSEC RR that
+;; proves the NXDOMAIN, i.e.,
+;; mx.example.com. (exist)
+;; (.no.example.com. (qname, NXDOMAIN)
+;; ).no.example.com. (exist)
+;; *.no.example.com. (best possible wildcard, not exist)
+;var=no_txt
+\).no.example.com. 3600 IN AAAA 2001:db8::53
+;; NSEC records.
+;var=nsec_apex_txt
+example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
+;var=
+example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_mx_txt
+mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
+
+;var=
+mx.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=nsec_no_txt
+\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
+
+;var=
+\).no.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
+;; non existence of wildcard. The following records will be used for that
+;; test.
+;; ).no.example.com. (exist, whose NSEC proves everything)
+;; *.no.example.com. (best possible wildcard, not exist)
+;; nx.no.example.com. (NXDOMAIN)
+;; nz.no.example.com. (exist)
+;var=nz_txt
+nz.no.example.com. 3600 IN AAAA 2001:db8::5300
+;var=nsec_nz_txt
+nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
+;var=nsec_nxdomain_txt
+noglue.example.com. 3600 IN NSEC nonsec.example.com. A
+
+;var=
+noglue.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; NSEC for the normal NXRRSET case
+;var=nsec_www_txt
+www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
+
+;var=
+www.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Authoritative data without NSEC
+;var=nonsec_a_txt
+nonsec.example.com. 3600 IN A 192.0.2.0
+
+;; (Secure) delegation data; Delegation with DS record
+;var=signed_delegation_txt
+signed-delegation.example.com. 3600 IN NS ns.example.net.
+;var=signed_delegation_ds_txt
+signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
+
+;var=
+signed-delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; (Secure) delegation data; Delegation without DS record (and both NSEC
+;; and NSEC3 denying its existence)
+;var=unsigned_delegation_txt
+unsigned-delegation.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_nsec_txt
+unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
+
+;var=
+unsigned-delegation.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Delegation without DS record, and no direct matching NSEC3 record
+;var=unsigned_delegation_optout_txt
+unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_optout_nsec_txt
+unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
+
+;; (Secure) delegation data; Delegation where the DS lookup will raise an
+;; exception.
+;var=bad_delegation_txt
+bad-delegation.example.com. 3600 IN NS ns.example.net.
+
+;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
+;; or NSEC3 that proves it.
+;var=nosec_delegation_txt
+nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example-base.zone.in b/src/bin/auth/tests/testdata/example-base.zone.in
new file mode 100644
index 0000000..63d2af0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base.zone.in
@@ -0,0 +1,7 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests.
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example-common-inc-template.zone b/src/bin/auth/tests/testdata/example-common-inc-template.zone
new file mode 100644
index 0000000..d7259bf
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-common-inc-template.zone
@@ -0,0 +1,5 @@
+;;
+;; This is an initial template of part of test zone file used in query test
+;; and expected to be included from other zone files. This is
+;; intentionally kept empty.
+;;
diff --git a/src/bin/auth/tests/testdata/example-nsec3-inc.zone b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
new file mode 100644
index 0000000..7742df0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
@@ -0,0 +1,16 @@
+;; See query_testzone_data.txt for general notes.
+
+;; NSEC3PARAM. This is needed for database-based data source to
+;; signal the zone is NSEC3-signed
+;var=
+example.com. 3600 IN NSEC3PARAM 1 1 12 aabbccdd
+
+;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
+;var=nsec3_apex_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
+;var=nsec3_apex_rrsig_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec3_www_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+;var=nsec3_www_rrsig_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
diff --git a/src/bin/auth/tests/testdata/example-nsec3.zone.in b/src/bin/auth/tests/testdata/example-nsec3.zone.in
new file mode 100644
index 0000000..aaf3d6a
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3.zone.in
@@ -0,0 +1,8 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests including NSEC3 records, making the zone is "NSEC3 signed".
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_srcdir@/example-nsec3-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example.zone b/src/bin/auth/tests/testdata/example.zone
deleted file mode 100644
index af0b618..0000000
--- a/src/bin/auth/tests/testdata/example.zone
+++ /dev/null
@@ -1,121 +0,0 @@
-;;
-;; This is a complete (but crafted and somewhat broken) zone file used
-;; in query tests.
-;;
-
-example.com. 3600 IN SOA . . 0 0 0 0 0
-example.com. 3600 IN NS glue.delegation.example.com.
-example.com. 3600 IN NS noglue.example.com.
-example.com. 3600 IN NS example.net.
-example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-glue.delegation.example.com. 3600 IN A 192.0.2.153
-glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
-noglue.example.com. 3600 IN A 192.0.2.53
-delegation.example.com. 3600 IN NS glue.delegation.example.com.
-delegation.example.com. 3600 IN NS noglue.example.com.
-delegation.example.com. 3600 IN NS cname.example.com.
-delegation.example.com. 3600 IN NS example.org.
-;; Borrowed from the RFC4035
-delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-mx.example.com. 3600 IN MX 10 www.example.com.
-mx.example.com. 3600 IN MX 20 mailer.example.org.
-mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
-www.example.com. 3600 IN A 192.0.2.80
-cname.example.com. 3600 IN CNAME www.example.com.
-cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
-;; CNAME Leading out of zone
-cnameout.example.com. 3600 IN CNAME www.example.org.
-;; The DNAME to do tests against
-dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
-;; Some data at the dname node (allowed by RFC 2672)
-dname.example.com. 3600 IN A 192.0.2.5
-;; The rest of data won't be referenced from the test cases.
-cnamemailer.example.com. 3600 IN CNAME www.example.com.
-cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
-mx.delegation.example.com. 3600 IN A 192.0.2.100
-;; Wildcards
-*.wild.example.com. 3600 IN A 192.0.2.7
-*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
-*.cnamewild.example.com. 3600 IN CNAME www.example.org.
-*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
-;; Wildcard_nxrrset
-*.uwild.example.com. 3600 IN A 192.0.2.9
-*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
-www.uwild.example.com. 3600 IN A 192.0.2.11
-www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
-;; Wildcard empty
-b.*.t.example.com. 3600 IN A 192.0.2.13
-b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
-t.example.com. 3600 IN A 192.0.2.15
-t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
-;; Used in NXDOMAIN proof test. We are going to test some unusual case where
-;; the best possible wildcard is below the "next domain" of the NSEC RR that
-;; proves the NXDOMAIN, i.e.,
-;; mx.example.com. (exist)
-;; (.no.example.com. (qname, NXDOMAIN)
-;; ).no.example.com. (exist)
-;; *.no.example.com. (best possible wildcard, not exist)
-\).no.example.com. 3600 IN AAAA 2001:db8::53
-;; NSEC records.
-example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
-mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
-\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
-;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
-;; non existence of wildcard. The following records will be used for that
-;; test.
-;; ).no.example.com. (exist, whose NSEC proves everything)
-;; *.no.example.com. (best possible wildcard, not exist)
-;; nx.no.example.com. (NXDOMAIN)
-;; nz.no.example.com. (exist)
-nz.no.example.com. 3600 IN AAAA 2001:db8::5300
-nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
-noglue.example.com. 3600 IN NSEC nonsec.example.com. A
-
-;; NSEC for the normal NXRRSET case
-www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
-
-;; Authoritative data without NSEC
-nonsec.example.com. 3600 IN A 192.0.2.0
-
-;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-
-;; NSEC3 for wild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for *.uwild.example.com (will be added on demand not to confuse
-;; other tests)
-b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-;; NSEC3 for uwild.example.com. (will be added on demand)
-t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-
-;; (Secure) delegation data; Delegation with DS record
-signed-delegation.example.com. 3600 IN NS ns.example.net.
-signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
-
-;; (Secure) delegation data; Delegation without DS record (and both NSEC
-;; and NSEC3 denying its existence)
-unsigned-delegation.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
-;; This one will be added on demand
-q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG
-
-;; Delegation without DS record, and no direct matching NSEC3 record
-unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
-
-;; (Secure) delegation data; Delegation where the DS lookup will raise an
-;; exception.
-bad-delegation.example.com. 3600 IN NS ns.example.net.
-
-;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
-;; or NSEC3 that proves it.
-nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example.zone.in b/src/bin/auth/tests/testdata/example.zone.in
new file mode 100644
index 0000000..608c09f
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example.zone.in
@@ -0,0 +1,6 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests, excluding NSEC3 records.
+;;
+
+$INCLUDE @abs_builddir@/example-base.zone
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index 36a4422..9414ed6 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -308,13 +308,6 @@ During the startup process, a number of messages are exchanged between the
Boss process and the processes it starts. This error is output when a
message received by the Boss process is not recognised.
-% BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
-The resolver is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
-
% BIND10_STOP_PROCESS asking %1 to shut down
The boss module is sending a shutdown command to the given module over
the message channel.
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 882653b..6fe5485 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -103,8 +103,31 @@ VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
# This is for boot_time of Boss
_BASETIME = time.gmtime()
+# Detailed error message commonly used on startup failure, possibly due to
+# permission issue regarding log lock file. We dump verbose message because
+# it may not be clear exactly what to do if it simply says
+# "failed to open <filename>: permission denied"
+NOTE_ON_LOCK_FILE = """\
+TIP: if this is about permission error for a lock file, check if the directory
+of the file is writable for the user of the bind10 process; often you need
+to start bind10 as a super user. Also, if you specify the -u option to
+change the user and group, the directory must be writable for the group,
+and the created lock file must be writable for that user. Finally, make sure
+the lock file is not left in the directly before restarting.
+"""
+
class ProcessInfoError(Exception): pass
+class ChangeUserError(Exception):
+ '''Exception raised when setuid/setgid fails.
+
+ When raised, it's expected to be propagated via underlying component
+ management modules to the top level so that it will help provide useful
+ fatal error message.
+
+ '''
+ pass
+
class ProcessInfo:
"""Information about a process"""
@@ -206,8 +229,8 @@ class BoB:
# restart. Components manage their own restart schedule now
self.components_to_restart = []
self.runnable = False
- self.uid = setuid
- self.gid = setgid
+ self.__uid = setuid
+ self.__gid = setgid
self.username = username
self.verbose = verbose
self.nokill = nokill
@@ -269,6 +292,31 @@ class BoB:
# Update the configuration
self._component_configurator.reconfigure(comps)
+ def change_user(self):
+ '''Change the user and group to those specified on construction.
+
+ This method is expected to be called by a component on initial
+ startup when the system is ready to switch the user and group
+ (i.e., once all components that need the privilege of the original
+ user have started).
+ '''
+ try:
+ if self.__gid is not None:
+ logger.info(BIND10_SETGID, self.__gid)
+ posix.setgid(self.__gid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change group: ' + str(ex))
+
+ try:
+ if self.__uid is not None:
+ posix.setuid(self.__uid)
+ # We use one-shot logger after setuid here. This will
+ # detect any permission issue regarding logging due to the
+ # result of setuid at the earliest opportunity.
+ isc.log.Logger("boss").info(BIND10_SETUID, self.__uid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change user: ' + str(ex))
+
def config_handler(self, new_config):
# If this is initial update, don't do anything now, leave it to startup
if not self.runnable:
@@ -578,8 +626,6 @@ class BoB:
are pure speculation. As with the auth daemon, they should be
read from the configuration database.
"""
- if self.uid is not None and self.__started:
- logger.warn(BIND10_START_AS_NON_ROOT_RESOLVER)
self.curproc = "b10-resolver"
# XXX: this must be read from the configuration manager in the future
resargs = ['b10-resolver']
@@ -646,6 +692,9 @@ class BoB:
try:
self.c_channel_env = c_channel_env
self.start_all_components()
+ except ChangeUserError as e:
+ self.kill_started_components()
+ return str(e) + '; ' + NOTE_ON_LOCK_FILE.replace('\n', ' ')
except Exception as e:
self.kill_started_components()
return "Unable to start " + self.curproc + ": " + str(e)
@@ -1155,7 +1204,19 @@ def remove_lock_files():
for f in lockfiles:
fname = lpath + '/' + f
if os.path.isfile(fname):
- os.unlink(fname)
+ try:
+ os.unlink(fname)
+ except OSError as e:
+ # We catch and ignore permission related error on unlink.
+ # This can happen if bind10 started with -u, created a lock
+ # file as a privileged user, but the directory is not writable
+ # for the changed user. This setup will cause immediate
+ # start failure, and we leave verbose error message including
+ # the leftover lock file, so it should be acceptable to ignore
+ # it (note that it doesn't make sense to log this event at
+ # this poitn)
+ if e.errno != errno.EPERM and e.errno != errno.EACCES:
+ raise
return
@@ -1173,13 +1234,7 @@ def main():
except RuntimeError as e:
sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
str(e))
- sys.stderr.write("""\
-TIP: if this is about permission error for a lock file, check if the directory
-of the file is writable for the user of the bind10 process; often you need
-to start bind10 as a super user. Also, if you specify the -u option to
-change the user and group, the directory must be writable for the group,
-and the created lock file must be writable for that user.
-""")
+ sys.stderr.write(NOTE_ON_LOCK_FILE)
sys.exit(1)
# Check user ID.
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 409790c..ccfa831 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -341,6 +341,18 @@ class TestCacheCommands(unittest.TestCase):
class TestBoB(unittest.TestCase):
+ def setUp(self):
+ # Save original values that may be tweaked in some tests
+ self.__orig_setgid = bind10_src.posix.setgid
+ self.__orig_setuid = bind10_src.posix.setuid
+ self.__orig_logger_class = isc.log.Logger
+
+ def tearDown(self):
+ # Restore original values saved in setUp()
+ bind10_src.posix.setgid = self.__orig_setgid
+ bind10_src.posix.setuid = self.__orig_setuid
+ isc.log.Logger = self.__orig_logger_class
+
def test_init(self):
bob = BoB()
self.assertEqual(bob.verbose, False)
@@ -349,10 +361,56 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
self.assertIsNone(bob._socket_cache)
+ def __setgid(self, gid):
+ self.__gid_set = gid
+
+ def __setuid(self, uid):
+ self.__uid_set = uid
+
+ def test_change_user(self):
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+
+ self.__gid_set = None
+ self.__uid_set = None
+ bob = BoB()
+ bob.change_user()
+ # No gid/uid set in boss, nothing called.
+ self.assertIsNone(self.__gid_set)
+ self.assertIsNone(self.__uid_set)
+
+ BoB(setuid=42, setgid=4200).change_user()
+ # This time, it get's called
+ self.assertEqual(4200, self.__gid_set)
+ self.assertEqual(42, self.__uid_set)
+
+ def raising_set_xid(gid_or_uid):
+ ex = OSError()
+ ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+ raise ex
+
+ # Let setgid raise an exception
+ bind10_src.posix.setgid = raising_set_xid
+ bind10_src.posix.setuid = self.__setuid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let initial log output after setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+ isc.log.Logger = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
def test_set_creator(self):
"""
Test the call to set_creator. First time, the cache is created
@@ -423,7 +481,6 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
def test_command_handler(self):
@@ -2021,8 +2078,10 @@ class TestBossComponents(unittest.TestCase):
def start_all_components(self):
self.started = True
- if self.throw:
+ if self.throw is True:
raise Exception('Assume starting components has failed.')
+ elif self.throw:
+ raise self.throw
def kill_started_components(self):
self.killed = True
@@ -2067,6 +2126,12 @@ class TestBossComponents(unittest.TestCase):
r = bob.startup()
self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
+ # Check failure of changing user results in a different message
+ bob = MockBobStartup(bind10_src.ChangeUserError('failed to chusr'))
+ r = bob.startup()
+ self.assertIn('failed to chusr', r)
+ self.assertTrue(bob.killed)
+
# Check the case when socket file already exists
isc.cc.Session = DummySessionSocketExists
bob = MockBobStartup(False)
@@ -2277,11 +2342,15 @@ class TestFunctions(unittest.TestCase):
self.assertFalse(os.path.exists(self.lockfile_testpath))
os.mkdir(self.lockfile_testpath)
self.assertTrue(os.path.isdir(self.lockfile_testpath))
+ self.__isfile_orig = bind10_src.os.path.isfile
+ self.__unlink_orig = bind10_src.os.unlink
def tearDown(self):
os.rmdir(self.lockfile_testpath)
self.assertFalse(os.path.isdir(self.lockfile_testpath))
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+ bind10_src.os.path.isfile = self.__isfile_orig
+ bind10_src.os.unlink = self.__unlink_orig
def test_remove_lock_files(self):
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
@@ -2305,6 +2374,28 @@ class TestFunctions(unittest.TestCase):
# second call should not assert anyway
bind10_src.remove_lock_files()
+ def test_remove_lock_files_fail(self):
+ # Permission error on unlink is ignored; other exceptions are really
+ # unexpected and propagated.
+ def __raising_unlink(unused, ex):
+ raise ex
+
+ bind10_src.os.path.isfile = lambda _: True
+ os_error = OSError()
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, os_error)
+
+ os_error.errno = errno.EPERM
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.EACCES
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.ENOENT
+ self.assertRaises(OSError, bind10_src.remove_lock_files)
+
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
+ self.assertRaises(Exception, bind10_src.remove_lock_files)
+
def test_get_signame(self):
# just test with some samples
signame = bind10_src.get_signame(signal.SIGTERM)
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 76181c2..193b1de 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -18,7 +18,9 @@
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
@@ -28,21 +30,31 @@
#include <map>
using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::asiolink;
-namespace isc {
-namespace dhcp {
+namespace {
/// @brief auxiliary type used for storing element name and its parser
typedef pair<string, ConstElementPtr> ConfigPair;
/// @brief a factory method that will create a parser for a given element name
-typedef Dhcp4ConfigParser* ParserFactory(const std::string& config_id);
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
/// @brief a collection of factories that creates parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<std::string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<std::string, std::string> StringStorage;
+
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
/// @brief a collection of pools
///
/// That type is used as intermediate storage, when pools are parsed, but there is
@@ -69,12 +81,12 @@ OptionStorage option_defaults;
/// will accept any configuration and will just print it out
/// on commit. Useful for debugging existing configurations and
/// adding new ones.
-class DebugParser : public Dhcp4ConfigParser {
+class DebugParser : public DhcpConfigParser {
public:
/// @brief Constructor
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@@ -83,7 +95,7 @@ public:
/// @brief builds parameter value
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@@ -97,7 +109,7 @@ public:
/// This is a method required by base class. It pretends to apply the
/// configuration, but in fact it only prints the parameter out.
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@@ -109,7 +121,7 @@ public:
/// @brief factory that constructs DebugParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* Factory(const std::string& param_name) {
return (new DebugParser(param_name));
}
@@ -121,6 +133,85 @@ private:
ConstElementPtr value_;
};
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::Dhcp4ConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::Dhcp4ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@@ -128,27 +219,36 @@ private:
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref Dhcp4ConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class Uint32Parser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class Uint32Parser : public DhcpConfigParser {
public:
/// @brief constructor for Uint32Parser
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
- :storage_(&uint32_defaults), param_name_(param_name) {
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(Dhcp4ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
- /// @brief builds parameter value
- ///
- /// Parses configuration entry and stores it in a storage. See
- /// \ref setStorage() for details.
+ /// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
/// @throw BadValue if supplied value could not be base to uint32_t
+ /// or the parameter name is empty.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(Dhcp4ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
int64_t check;
string x = value->str();
try {
@@ -168,31 +268,27 @@ public:
// value is small enough to fit
value_ = static_cast<uint32_t>(check);
-
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by \ref Subnet4ConfigParser when constructing
- /// the subnet.
+ /// @brief Stores the parsed uint32_t value in a storage.
virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
}
/// @brief factory that constructs Uint32Parser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Uint32Parser(param_name));
}
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv4ConfigInherit for details.
+ /// See @ref dhcpv4ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(Uint32Storage* storage) {
@@ -217,47 +313,48 @@ private:
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref Dhcp4ConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class StringParser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class StringParser : public DhcpConfigParser {
public:
/// @brief constructor for StringParser
/// @param param_name name of the configuration parameter being parsed
StringParser(const std::string& param_name)
:storage_(&string_defaults), param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(Dhcp4ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
/// @brief parses parameter value
///
/// Parses configuration entry and stores it in storage. See
- /// \ref setStorage() for details.
+ /// @ref setStorage() for details.
///
/// @param value pointer to the content of parsed values
virtual void build(ConstElementPtr value) {
value_ = value->str();
boost::erase_all(value_, "\"");
-
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parser. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by its parent parser when constructing
- /// an object, e.g. the subnet.
+ /// @brief Stores the parsed value in a storage.
virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
}
/// @brief factory that constructs StringParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new StringParser(param_name));
}
@@ -290,7 +387,7 @@ private:
/// designates all interfaces.
///
/// It is useful for parsing Dhcp4/interface parameter.
-class InterfaceListConfigParser : public Dhcp4ConfigParser {
+class InterfaceListConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -328,7 +425,7 @@ public:
/// @brief factory that constructs InterfaceListConfigParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new InterfaceListConfigParser(param_name));
}
@@ -347,7 +444,7 @@ private:
/// before build(). Otherwise exception will be thrown.
///
/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
-class PoolParser : public Dhcp4ConfigParser {
+class PoolParser : public DhcpConfigParser {
public:
/// @brief constructor.
@@ -408,7 +505,7 @@ public:
}
Pool4Ptr pool(new Pool4(addr, len));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -421,7 +518,7 @@ public:
Pool4Ptr pool(new Pool4(min, max));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -440,17 +537,22 @@ public:
pools_ = storage;
}
- /// @brief does nothing.
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers (for subnet) are expected
- /// to use values stored in the storage.
- virtual void commit() {}
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
/// @brief factory that constructs PoolParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new PoolParser(param_name));
}
@@ -460,6 +562,9 @@ private:
/// That is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
};
/// @brief Parser for option data value.
@@ -475,7 +580,7 @@ private:
/// (see tickets #2319, #2314). When option spaces are implemented
/// there will be a way to reference the particular option using
/// its type (code) or option name.
-class OptionDataParser : public Dhcp4ConfigParser {
+class OptionDataParser : public DhcpConfigParser {
public:
/// @brief Constructor.
@@ -512,31 +617,45 @@ public:
ParserPtr parser;
if (param.first == "name") {
boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
name_parser->setStorage(&string_values_);
parser = name_parser;
}
} else if (param.first == "code") {
boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
if (code_parser) {
code_parser->setStorage(&uint32_values_);
parser = code_parser;
}
} else if (param.first == "data") {
boost::shared_ptr<StringParser>
- value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ value_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (value_parser) {
value_parser->setStorage(&string_values_);
parser = value_parser;
}
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
+ if (value_parser) {
+ value_parser->setStorage(&boolean_values_);
+ parser = value_parser;
+ }
} else {
isc_throw(Dhcp4ConfigError,
"Parser error: option-data parameter not supported: "
<< param.first);
}
parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
}
// Try to create the option instance.
createOption();
@@ -627,16 +746,27 @@ private:
}
// Get option data from the configuration database ('data' field).
- // Option data is specified by the user as case insensitive string
- // of hexadecimal digits for each option.
- std::string option_data = getStringParam("data");
+ const std::string option_data = getStringParam("data");
+ const bool csv_format = getBooleanParam("csv-format");
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(Dhcp4ConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(Dhcp4ConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
}
// Get all existing DHCPv4 option definitions. The one that matches
// our option will be picked and used to create it.
@@ -656,6 +786,13 @@ private:
<< " for the same option code. This will be supported once"
<< " there option spaces are implemented.");
} else if (num_defs == 0) {
+ if (csv_format) {
+ isc_throw(Dhcp4ConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently an error will be issued if an option definition does not exist
@@ -673,7 +810,9 @@ private:
// use it to create the option instance.
const OptionDefinitionPtr& def = *(range.first);
try {
- OptionPtr option = def->optionFactory(Option::V4, option_code, binary);
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V4, option_code, data_tokens) :
+ def->optionFactory(Option::V4, option_code, binary);
Subnet::OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
@@ -711,10 +850,27 @@ private:
return (param->second);
}
+ /// @brief Get a parameter from the boolean values storage.
+ ///
+ /// @param param_id parameter identifier.
+ ///
+ /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
+ /// @return a value of the boolean parameter.
+ bool getBooleanParam(const std::string& param_id) const {
+ BooleanStorage::const_iterator param = boolean_values_.find(param_id);
+ if (param == boolean_values_.end()) {
+ isc_throw(isc::dhcp::Dhcp4ConfigError, "parser error: option-data parameter"
+ << " '" << param_id << "' not specified");
+ }
+ return (param->second);
+ }
+
/// Storage for uint32 values (e.g. option code).
Uint32Storage uint32_values_;
/// Storage for string values (e.g. option name or data).
StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
/// Pointer to options storage. This storage is provided by
/// the calling class and is shared by all OptionDataParser objects.
OptionStorage* options_;
@@ -728,7 +884,7 @@ private:
/// data for a particular subnet and creates a collection of options.
/// If parsing is successful, all these options are added to the Subnet
/// object.
-class OptionDataListParser : public Dhcp4ConfigParser {
+class OptionDataListParser : public DhcpConfigParser {
public:
/// @brief Constructor.
@@ -785,7 +941,7 @@ public:
/// @param param_name param name.
///
/// @return DhcpConfigParser object.
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new OptionDataListParser(param_name));
}
@@ -804,7 +960,7 @@ public:
///
/// This class parses the whole subnet definition. It creates parsers
/// for received configuration parameters as needed.
-class Subnet4ConfigParser : public Dhcp4ConfigParser {
+class Subnet4ConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -844,7 +1000,20 @@ public:
isc_throw(Dhcp4ConfigError, "failed to find suitable parser");
}
}
- // Ok, we now have subnet parsed
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Create a subnet.
+ createSubnet();
}
/// @brief commits received configuration.
@@ -855,28 +1024,82 @@ public:
/// objects. Subnet4 are then added to DHCP CfgMgr.
/// @throw Dhcp4ConfigError if there are any issues encountered during commit
void commit() {
- // Invoke commit on all sub-data parsers.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
+ if (subnet_) {
+ CfgMgr::instance().addSubnet4(subnet_);
+ }
+ }
+
+private:
+
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
}
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::Dhcp4ConfigError if subnet configuration parsing failed.
+ void createSubnet() {
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
isc_throw(Dhcp4ConfigError,
"Mandatory subnet definition in subnet missing");
}
+ // Remove any spaces or tabs.
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
isc_throw(Dhcp4ConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> valid = getParam("valid-lifetime");
@@ -888,13 +1111,13 @@ public:
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
- Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid));
+ subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet->addPool4(*it);
+ subnet_->addPool4(*it);
}
- const Subnet::OptionContainer& options = subnet->getOptions();
+ const Subnet::OptionContainer& options = subnet_->getOptions();
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Add subnet specific options.
@@ -904,7 +1127,7 @@ public:
LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
.arg(desc.option->getType()).arg(addr.toText());
}
- subnet->addOption(desc.option);
+ subnet_->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
@@ -924,47 +1147,9 @@ public:
// want to issue a warning about dropping the configuration of
// a global option if one already exsists.
if (std::distance(range.first, range.second) == 0) {
- subnet->addOption(desc.option);
+ subnet_->addOption(desc.option);
}
}
-
- CfgMgr::instance().addSubnet4(subnet);
- }
-
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
}
/// @brief creates parsers for entries in subnet definition
@@ -974,15 +1159,15 @@ private:
/// @param config_id name od the entry
/// @return parser object for specified entry name
/// @throw NotImplemented if trying to create a parser for unknown config element
- Dhcp4ConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
+ DhcpConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories["valid-lifetime"] = Uint32Parser::Factory;
- factories["renew-timer"] = Uint32Parser::Factory;
- factories["rebind-timer"] = Uint32Parser::Factory;
- factories["subnet"] = StringParser::Factory;
- factories["pool"] = PoolParser::Factory;
- factories["option-data"] = OptionDataListParser::Factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -1043,6 +1228,9 @@ private:
/// parsers are stored here
ParserCollection parsers_;
+
+ /// @brief Pointer to the created subnet object.
+ isc::dhcp::Subnet4Ptr subnet_;
};
/// @brief this class parses list of subnets
@@ -1050,7 +1238,7 @@ private:
/// This is a wrapper parser that handles the whole list of Subnet4
/// definitions. It iterates over all entries and creates Subnet4ConfigParser
/// for each entry.
-class Subnets4ListConfigParser : public Dhcp4ConfigParser {
+class Subnets4ListConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -1098,14 +1286,20 @@ public:
/// @brief Returns Subnet4ListConfigParser object
/// @param param_name name of the parameter
/// @return Subnets4ListConfigParser object
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Subnets4ListConfigParser(param_name));
}
/// @brief collection of subnet parsers.
ParserCollection subnets_;
+
};
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
/// @brief creates global parsers
///
/// This method creates global parsers that parse global parameters, i.e.
@@ -1114,16 +1308,16 @@ public:
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv4 parameter
/// @throw NotImplemented if trying to create a parser for unknown config element
-Dhcp4ConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
+DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories["valid-lifetime"] = Uint32Parser::Factory;
- factories["renew-timer"] = Uint32Parser::Factory;
- factories["rebind-timer"] = Uint32Parser::Factory;
- factories["interface"] = InterfaceListConfigParser::Factory;
- factories["subnet4"] = Subnets4ListConfigParser::Factory;
- factories["option-data"] = OptionDataListParser::Factory;
- factories["version"] = StringParser::Factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet4"] = Subnets4ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -1150,44 +1344,117 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
- ParserCollection parsers;
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet6 structure
+ // depend on the global values. Thus we need to make sure that
+ // the global values are processed first and that they can be
+ // accessed by the subnet6 parsers. We separate parsers that
+ // should process data first (independent_parsers) from those
+ // that must process data when the independent data is already
+ // processed (dependent_parsers).
+ ParserCollection independent_parsers;
+ ParserCollection dependent_parsers;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storage. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
+
try {
+
+ // Iterate over all independent parsers first (all but subnet4)
+ // and try to parse the data.
BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ if (config_pair.first != "subnet4") {
+ ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
+ }
- ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
+ // Process dependent configuration data.
+ BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ if (config_pair.first == "subnet4") {
+ ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+ dependent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ }
}
+
} catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed: ") + ex.what());
- return (answer);
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed"));
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
}
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
+ parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+ rollback = true;
+
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ rollback = true;
+
}
}
- catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed: ") + ex.what());
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
return (answer);
- } catch (...) {
- // for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed"));
}
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
- ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}
+const std::map<std::string, uint32_t>& getUint32Defaults() {
+ return (uint32_defaults);
+}
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index cc4c690..3247241 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -28,14 +28,8 @@ namespace dhcp {
class Dhcpv4Srv;
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
-typedef std::map<std::string, uint32_t> Uint32Storage;
-
-/// @brief a collection of elements that store string values
-typedef std::map<std::string, std::string> StringStorage;
-
/// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv4Srv object.
+/// @c Dhcpv4Srv object.
class Dhcp4ConfigError : public isc::Exception {
public:
@@ -48,97 +42,12 @@ public:
: isc::Exception(file, line, what) {}
};
-/// @brief Base abstract class for all DHCPv4 parsers
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
///
-/// Each instance of a class derived from this class parses one specific config
-/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
-/// complex (e.g. a subnet). In such case, it is likely that a parser will
-/// spawn child parsers to parse child elements in the configuration.
-/// @todo: Merge this class with DhcpConfigParser in src/bin/dhcp6
-class Dhcp4ConfigParser {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private to make it explicit that this is a
- /// pure base class.
- //@{
-private:
-
- // Private construtor and assignment operator assures that nobody
- // will be able to copy or assign a parser. There are no defined
- // bodies for them.
- Dhcp4ConfigParser(const Dhcp4ConfigParser& source);
- Dhcp4ConfigParser& operator=(const Dhcp4ConfigParser& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class should
- /// never be instantiated (except as part of a derived class).
- Dhcp4ConfigParser() {}
-public:
- /// The destructor.
- virtual ~Dhcp4ConfigParser() {}
- //@}
-
- /// \brief Prepare configuration value.
- ///
- /// This method parses the "value part" of the configuration identifier
- /// that corresponds to this derived class and prepares a new value to
- /// apply to the server.
- ///
- /// This method must validate the given value both in terms of syntax
- /// and semantics of the configuration, so that the server will be
- /// validly configured at the time of \c commit(). Note: the given
- /// configuration value is normally syntactically validated, but the
- /// \c build() implementation must also expect invalid input. If it
- /// detects an error it may throw an exception of a derived class
- /// of \c isc::Exception.
- ///
- /// Preparing a configuration value will often require resource
- /// allocation. If it fails, it may throw a corresponding standard
- /// exception.
- ///
- /// This method is not expected to be called more than once in the
- /// life of the object. Although multiple calls are not prohibited
- /// by the interface, the behavior is undefined.
- ///
- /// \param config_value The configuration value for the identifier
- /// corresponding to the derived class.
- virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
- /// \brief Apply the prepared configuration value to the server.
- ///
- /// This method is expected to be exception free, and, as a consequence,
- /// it should normally not involve resource allocation.
- /// Typically it would simply perform exception free assignment or swap
- /// operation on the value prepared in \c build().
- /// In some cases, however, it may be very difficult to meet this
- /// condition in a realistic way, while the failure case should really
- /// be very rare. In such a case it may throw, and, if the parser is
- /// called via \c configureDhcp4Server(), the caller will convert the
- /// exception as a fatal error.
- ///
- /// This method is expected to be called after \c build(), and only once.
- /// The result is undefined otherwise.
- virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<Dhcp4ConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure DHCPv4 server (\c Dhcpv4Srv) with a set of configuration values.
-///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
+/// This function parses configuration information stored in @c config_set
+/// and configures the @c server by applying the configuration to it.
/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
+/// derived class implementations of @c DhcpConfigParser meet the assumption,
/// that is, it ensures that either configuration is fully applied or the
/// state of the server is intact.
///
@@ -154,7 +63,8 @@ typedef std::vector<ParserPtr> ParserCollection;
/// reconfiguration statuses. It may return the following response codes:
/// 0 - configuration successful
/// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
+/// 2 - commit failed (parsing was successful, but failed to store the
+/// values in to server's configuration)
///
/// @param config_set a new configuration (JSON) for DHCPv4 server
/// @return answer that contains result of reconfiguration
@@ -162,6 +72,16 @@ isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set);
+
+/// @brief Returns the global uint32_t values storage.
+///
+/// This function must be only used by unit tests that need
+/// to access uint32_t global storage to verify that the
+/// Uint32Parser works as expected.
+///
+/// @return a reference to a global uint32 values storage.
+const std::map<std::string, uint32_t>& getUint32Defaults();
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 46192de..13acf83 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -61,6 +61,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
} ]
}
},
@@ -141,6 +146,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
} ]
}
} ]
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 3dd75d7..498cedb 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -17,9 +17,10 @@
#include <arpa/inet.h>
#include <gtest/gtest.h>
+#include <config/ccsession.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/config_parser.h>
-#include <config/ccsession.h>
+#include <dhcp/option4_addrlst.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
#include <boost/foreach.hpp>
@@ -35,12 +36,6 @@ using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::config;
-namespace isc {
-namespace dhcp {
-extern Uint32Storage uint32_defaults;
-}
-}
-
namespace {
class Dhcp4ParserTest : public ::testing::Test {
@@ -55,7 +50,9 @@ public:
// Checks if global parameter of name have expected_value
void checkGlobalUint32(string name, uint32_t expected_value) {
- Uint32Storage::const_iterator it = uint32_defaults.find(name);
+ const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
+ std::map<std::string, uint32_t>::const_iterator it =
+ uint32_defaults.find(name);
if (it == uint32_defaults.end()) {
ADD_FAILURE() << "Expected uint32 with name " << name
<< " not found";
@@ -81,7 +78,8 @@ public:
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
- /// option value. These parameters are: "name", "code" and "data".
+ /// option value. These parameters are: "name", "code", "data" and
+ /// "csv-format".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
@@ -96,14 +94,22 @@ public:
params["name"] = param_value;
params["code"] = "56";
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "option_foo";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "option_foo";
params["code"] = "56";
params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "option_foo";
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
@@ -140,6 +146,8 @@ public:
stream << "\"code\": " << param.second << "";
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
}
}
stream <<
@@ -393,9 +401,9 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
- // returned value must be 2 (values error)
+ // returned value must be 1 (values error)
// as the pool does not belong to that subnet
- checkResult(status, 2);
+ checkResult(status, 1);
}
// Goal of this test is to verify if pools can be defined
@@ -439,12 +447,14 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\""
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
- " \"data\": \"01\""
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
@@ -502,7 +512,8 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
- " \"data\": \"AB\""
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
@@ -510,12 +521,14 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\""
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
- " \"data\": \"01\""
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -571,7 +584,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
- " \"data\": \"0102030405060708090A\""
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
" } ]"
" },"
" {"
@@ -580,7 +594,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
- " \"data\": \"FF\""
+ " \"data\": \"FF\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -724,10 +739,70 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
}
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp4ParserTest, stdOptionData) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "nis-servers";
+ // Option code 41 means nis-servers.
+ params["code"] = "41";
+ // Specify option values in a CSV (user friendly) format.
+ params["data"] = "192.0.2.10, 192.0.2.1, 192.0.2.3";
+ params["csv-format"] = "True";
+
+ std::string config = createConfigWithOption(params);
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ const Subnet::OptionContainer& options = subnet->getOptions();
+ ASSERT_EQ(1, options.size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(DHO_NIS_SERVERS);
+ // Expect single option with the code equal to NIS_SERVERS option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be Option6IA
+ // which is derived from Option. This class is dedicated to
+ // represent standard option IA_NA.
+ boost::shared_ptr<Option4AddrLst> option_addrs =
+ boost::dynamic_pointer_cast<Option4AddrLst>(option);
+ // If cast is unsuccessful than option returned was of a
+ // differnt type than Option6IA. This is wrong.
+ ASSERT_TRUE(option_addrs);
+
+ // Get addresses from the option.
+ Option4AddrLst::AddressContainer addrs = option_addrs->getAddresses();
+ // Verify that the addresses have been configured correctly.
+ ASSERT_EQ(3, addrs.size());
+ EXPECT_EQ("192.0.2.10", addrs[0].toText());
+ EXPECT_EQ("192.0.2.1", addrs[1].toText());
+ EXPECT_EQ("192.0.2.3", addrs[2].toText());
+}
+
/// This test checks if Uint32Parser can really parse the whole range
/// and properly err of out of range values. As we can't call Uint32Parser
/// directly, we are exploiting the fact that it is used to parse global
/// parameter renew-timer and the results are stored in uint32_defaults.
+/// We get the uint32_defaults using a getUint32Defaults functions which
+/// is defined only to access the values from this test.
TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
ConstElementPtr status;
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 944bb21..aec182a 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -19,11 +19,13 @@
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/triplet.h>
#include <log/logger_support.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
@@ -39,36 +41,41 @@
using namespace std;
using namespace isc::data;
+using namespace isc::dhcp;
using namespace isc::asiolink;
-namespace isc {
-namespace dhcp {
+namespace {
-/// @brief an auxiliary type used for storing an element name and its parser
+/// @brief Auxiliary type used for storing an element name and its parser.
typedef pair<string, ConstElementPtr> ConfigPair;
-/// @brief a factory method that will create a parser for a given element name
-typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
+/// @brief Factory method that will create a parser for a given element name
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-/// @brief a collection of factories that create parsers for specified element names
+/// @brief Collection of factories that create parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Collection of elements that store uint32 values (e.g. renew-timer = 900).
typedef std::map<string, uint32_t> Uint32Storage;
-/// @brief a collection of elements that store string values
+/// @brief Collection of elements that store string values.
typedef std::map<string, string> StringStorage;
-/// @brief a collection of pools
+/// @brief Collection of address pools.
///
/// This type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
-typedef std::vector<Pool6Ptr> PoolStorage;
+typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
-/// @brief Collection of option descriptors. This container allows searching for
-/// options using the option code or persistency flag. This is useful when merging
-/// existing options with newly configured options.
-typedef Subnet::OptionContainer OptionStorage;
+/// @brief Collection of option descriptors.
+///
+/// This container allows to search options using an option code
+/// or a persistency flag. This is useful when merging existing
+/// options with newly configured options.
+typedef isc::dhcp::Subnet::OptionContainer OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
@@ -90,7 +97,7 @@ public:
/// @brief Constructor
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@@ -99,7 +106,7 @@ public:
/// @brief builds parameter value
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@@ -108,12 +115,12 @@ public:
value_ = new_config;
}
- /// @brief pretends to apply the configuration
+ /// @brief Pretends to apply the configuration.
///
/// This is a method required by the base class. It pretends to apply the
/// configuration, but in fact it only prints the parameter out.
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@@ -125,7 +132,7 @@ public:
/// @brief factory that constructs DebugParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new DebugParser(param_name));
}
@@ -137,6 +144,86 @@ private:
ConstElementPtr value_;
};
+
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::Dhcp6ConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@@ -144,10 +231,10 @@ private:
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
///
/// @todo this class should be turned into the template class which
/// will handle all uintX_types of data (see ticket #2415).
@@ -155,18 +242,29 @@ class Uint32Parser : public DhcpConfigParser {
public:
/// @brief constructor for Uint32Parser
+ ///
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
- :storage_(&uint32_defaults), param_name_(param_name) {
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(Dhcp6ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
- /// @brief builds parameter value
- ///
- /// Parses configuration entry and stores it in a storage. See
- /// \ref Uint32Parser::setStorage() for details.
+ /// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
+ /// @throw isc::dhcp::Dhcp6ConfigError if failed to parse
+ /// the configuration parameter as uint32_t value.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
bool parse_error = false;
// Cast the provided value to int64 value to check.
int64_t int64value = 0;
@@ -180,51 +278,45 @@ public:
parse_error = true;
}
if (!parse_error) {
+ // Check that the value is not out of bounds.
if ((int64value < 0) ||
(int64value > std::numeric_limits<uint32_t>::max())) {
parse_error = true;
} else {
- try {
- value_ = boost::lexical_cast<uint32_t>(value->str());
- } catch (const boost::bad_lexical_cast &) {
- parse_error = true;
- }
+ // A value is not out of bounds so let's cast it to
+ // the uint32_t type.
+ value_ = static_cast<uint32_t>(int64value);
}
}
-
+ // Invalid value provided.
if (parse_error) {
- isc_throw(BadValue, "Failed to parse value " << value->str()
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "Failed to parse value " << value->str()
<< " as unsigned 32-bit integer.");
}
-
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by \ref Subnet6Parser when constructing
- /// the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed uint32_t value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs Uint32Parser objects
+ /// @brief Factory that constructs Uint32Parser objects.
///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ /// @param param_name name of the parameter to be parsed.
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Uint32Parser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
- /// @param storage pointer to the storage container
+ /// @param storage pointer to the storage container.
void setStorage(Uint32Storage* storage) {
storage_ = storage;
}
@@ -232,10 +324,8 @@ public:
private:
/// pointer to the storage, where parsed value will be stored
Uint32Storage* storage_;
-
/// name of the parameter to be parsed
std::string param_name_;
-
/// the actual parsed value
uint32_t value_;
};
@@ -247,54 +337,60 @@ private:
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using the
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
class StringParser : public DhcpConfigParser {
public:
/// @brief constructor for StringParser
+ ///
/// @param param_name name of the configuration parameter being parsed
StringParser(const std::string& param_name)
- :storage_(&string_defaults), param_name_(param_name) {
+ : storage_(&string_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(Dhcp6ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
/// @brief parses parameter value
///
- /// Parses configuration entry and stores it in storage. See
- /// \ref setStorage() for details.
+ /// Parses configuration parameter's value as string.
///
/// @param value pointer to the content of parsed values
+ /// @throws Dhcp6ConfigError if the parsed parameter's name is empty.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
value_ = value->str();
boost::erase_all(value_, "\"");
-
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by its parent parser when constructing
- /// an object, e.g. the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs StringParser objects
+ /// @brief Factory that constructs StringParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new StringParser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(StringStorage* storage) {
@@ -302,17 +398,14 @@ public:
}
private:
- /// pointer to the storage, where parsed value will be stored
+ /// Pointer to the storage, where parsed value will be stored
StringStorage* storage_;
-
- /// name of the parameter to be parsed
+ /// Name of the parameter to be parsed
std::string param_name_;
-
- /// the actual parsed value
+ /// The actual parsed value
std::string value_;
};
-
/// @brief parser for interface list definition
///
/// This parser handles Dhcp6/interface entry.
@@ -333,7 +426,7 @@ public:
/// @throw BadValue if supplied parameter name is not "interface"
InterfaceListConfigParser(const std::string& param_name) {
if (param_name != "interface") {
- isc_throw(BadValue, "Internal error. Interface configuration "
+ isc_throw(isc::BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
@@ -359,7 +452,7 @@ public:
/// @brief factory that constructs InterfaceListConfigParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new InterfaceListConfigParser(param_name));
}
@@ -383,7 +476,7 @@ public:
/// @brief constructor.
PoolParser(const std::string& /*param_name*/)
- :pools_(NULL) {
+ : pools_(NULL) {
// ignore parameter name, it is always Dhcp6/subnet6[X]/pool
}
@@ -393,11 +486,13 @@ public:
/// No validation is done at this stage, everything is interpreted as
/// interface name.
/// @param pools_list list of pools defined for a subnet
- /// @throw BadValue if storage was not specified (setStorage() not called)
+ /// @throw isc::InvalidOperation if storage was not specified
+ /// (setStorage() not called)
void build(ConstElementPtr pools_list) {
+
// setStorage() should have been called before build
if (!pools_) {
- isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+ isc_throw(isc::InvalidOperation, "parser logic error: no pool storage set,"
" but pool parser asked to parse pools");
}
@@ -433,12 +528,12 @@ public:
// be checked in Pool6 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+ isc_throw(Dhcp6ConfigError, "failed to parse pool "
"definition: " << text_pool->stringValue());
}
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -451,11 +546,11 @@ public:
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
- isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+ isc_throw(Dhcp6ConfigError, "failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -463,24 +558,29 @@ public:
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(PoolStorage* storage) {
pools_ = storage;
}
- /// @brief does nothing.
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers (for subnet) are expected
- /// to use values stored in the storage.
- virtual void commit() {}
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
/// @brief factory that constructs PoolParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new PoolParser(param_name));
}
@@ -490,8 +590,12 @@ private:
/// This is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
};
+
/// @brief Parser for option data value.
///
/// This parser parses configuration entries that specify value of
@@ -534,6 +638,7 @@ public:
/// calling build.
/// @throw isc::BadValue if option data storage is invalid.
virtual void build(ConstElementPtr option_data_entries) {
+
if (options_ == NULL) {
isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
"parsing option data.");
@@ -542,31 +647,45 @@ public:
ParserPtr parser;
if (param.first == "name") {
boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
name_parser->setStorage(&string_values_);
parser = name_parser;
}
} else if (param.first == "code") {
boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
if (code_parser) {
code_parser->setStorage(&uint32_values_);
parser = code_parser;
}
} else if (param.first == "data") {
boost::shared_ptr<StringParser>
- value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ value_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (value_parser) {
value_parser->setStorage(&string_values_);
parser = value_parser;
}
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
+ if (value_parser) {
+ value_parser->setStorage(&boolean_values_);
+ parser = value_parser;
+ }
} else {
isc_throw(Dhcp6ConfigError,
- "Parser error: option-data parameter not supported: "
+ "parser error: option-data parameter not supported: "
<< param.first);
}
parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
}
// Try to create the option instance.
createOption();
@@ -582,20 +701,20 @@ public:
/// remain un-modified.
virtual void commit() {
if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
"commiting option data.");
} else if (!option_descriptor_.option) {
// Before we can commit the new option should be configured. If it is not
// than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+ isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
" thus there is nothing to commit. Has build() been called?");
}
uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+ isc::dhcp::Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
// Try to find options with the particular option code in the main
// storage. If found, remove these options because they will be
// replaced with new one.
- Subnet::OptionContainerTypeRange range =
+ isc::dhcp::Subnet::OptionContainerTypeRange range =
idx.equal_range(opt_type);
if (std::distance(range.first, range.second) > 0) {
idx.erase(range.first, range.second);
@@ -632,6 +751,7 @@ private:
/// @throw Dhcp6ConfigError if parameters provided in the configuration
/// are invalid.
void createOption() {
+
// Option code is held in the uint32_t storage but is supposed to
// be uint16_t value. We need to check that value in the configuration
// does not exceed range of uint16_t and is not zero.
@@ -657,16 +777,25 @@ private:
}
// Get option data from the configuration database ('data' field).
- // Option data is specified by the user as case insensitive string
- // of hexadecimal digits for each option.
- std::string option_data = getStringParam("data");
- // Transform string of hexadecimal digits into binary format.
+ const std::string option_data = getStringParam("data");
+ const bool csv_format = getBooleanParam("csv-format");
std::vector<uint8_t> binary;
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
+ std::vector<std::string> data_tokens;
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ isc::util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
}
// Get all existing DHCPv6 option definitions. The one that matches
// our option will be picked and used to create it.
@@ -686,6 +815,12 @@ private:
<< " for the same option code. This will be supported once"
<< " there option spaces are implemented.");
} else if (num_defs == 0) {
+ if (csv_format) {
+ isc_throw(Dhcp6ConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently an error will be issued if an option definition does not exist
@@ -703,7 +838,9 @@ private:
// use it to create the option instance.
const OptionDefinitionPtr& def = *(range.first);
try {
- OptionPtr option = def->optionFactory(Option::V6, option_code, binary);
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V6, option_code, data_tokens) :
+ def->optionFactory(Option::V6, option_code, binary);
Subnet::OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
@@ -718,11 +855,13 @@ private:
/// @brief Get a parameter from the strings storage.
///
/// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
+ ///
+ /// @throw Dhcp6ConfigError if a parameter has not been found.
+ /// @return a value of the string parameter.
std::string getStringParam(const std::string& param_id) const {
StringStorage::const_iterator param = string_values_.find(param_id);
if (param == string_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
<< " '" << param_id << "' not specified");
}
return (param->second);
@@ -731,11 +870,28 @@ private:
/// @brief Get a parameter from the uint32 values storage.
///
/// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
+ ///
+ /// @throw Dhcp6ConfigError if a parameter has not been found.
+ /// @return a value of the uint32_t parameter.
uint32_t getUint32Param(const std::string& param_id) const {
Uint32Storage::const_iterator param = uint32_values_.find(param_id);
if (param == uint32_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
+ << " '" << param_id << "' not specified");
+ }
+ return (param->second);
+ }
+
+ /// @brief Get a parameter from the boolean values storage.
+ ///
+ /// @param param_id parameter identifier.
+ ///
+ /// @throw isc::dhcp::Dhcp6ConfigError if a parameter has not been found.
+ /// @return a value of the boolean parameter.
+ bool getBooleanParam(const std::string& param_id) const {
+ BooleanStorage::const_iterator param = boolean_values_.find(param_id);
+ if (param == boolean_values_.end()) {
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "parser error: option-data parameter"
<< " '" << param_id << "' not specified");
}
return (param->second);
@@ -745,11 +901,13 @@ private:
Uint32Storage uint32_values_;
/// Storage for string values (e.g. option name or data).
StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
/// Pointer to options storage. This storage is provided by
/// the calling class and is shared by all OptionDataParser objects.
OptionStorage* options_;
/// Option descriptor holds newly configured option.
- Subnet::OptionDescriptor option_descriptor_;
+ isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
};
/// @brief Parser for option data values within a subnet.
@@ -815,7 +973,7 @@ public:
/// @param param_name param name.
///
/// @return DhcpConfigParser object.
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new OptionDataListParser(param_name));
}
@@ -846,6 +1004,8 @@ public:
/// @brief parses parameter value
///
/// @param subnet pointer to the content of subnet definition
+ ///
+ /// @throw isc::Dhcp6ConfigError if subnet configuration parsing failed.
void build(ConstElementPtr subnet) {
BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
@@ -874,38 +1034,102 @@ public:
isc_throw(Dhcp6ConfigError, "failed to find suitable parser");
}
}
- // Ok, we now have subnet parsed
- }
- /// @brief commits received configuration.
- ///
- /// This method does most of the configuration. Many other parsers are just
- /// storing the values that are actually consumed here. Pool definitions
- /// created in other parsers are used here and added to newly created Subnet6
- /// objects. Subnet6 are then added to DHCP CfgMgr.
- void commit() {
- // Invoke commit on all sub-data parsers.
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
BOOST_FOREACH(ParserPtr parser, parsers_) {
parser->commit();
}
+ // Create a subnet.
+ createSubnet();
+ }
+
+ /// @brief Adds the created subnet to a server's configuration.
+ void commit() {
+ if (subnet_) {
+ isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
+ }
+ }
+
+private:
+
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
+ }
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::Dhcp6ConfigError if subnet configuration parsing failed.
+ void createSubnet() {
+
+ // Find a subnet string.
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
isc_throw(Dhcp6ConfigError,
"Mandatory subnet definition in subnet missing");
}
+ // Remove any spaces or tabs.
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
-
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
isc_throw(Dhcp6ConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> pref = getParam("preferred-lifetime");
@@ -919,13 +1143,16 @@ public:
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
- Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Create a new subnet.
+ subnet_.reset(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Add pools to it.
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet->addPool6(*it);
+ subnet_->addPool6(*it);
}
- const Subnet::OptionContainer& options = subnet->getOptions();
+ // Get the options search index.
+ const Subnet::OptionContainer& options = subnet_->getOptions();
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Add subnet specific options.
@@ -935,7 +1162,7 @@ public:
LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
.arg(desc.option->getType()).arg(addr.toText());
}
- subnet->addOption(desc.option);
+ subnet_->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
@@ -955,91 +1182,42 @@ public:
// want to issue a warning about dropping the configuration of
// a global option if one already exsists.
if (std::distance(range.first, range.second) == 0) {
- subnet->addOption(desc.option);
+ subnet_->addOption(desc.option);
}
}
-
- CfgMgr::instance().addSubnet6(subnet);
- }
-
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
}
/// @brief creates parsers for entries in subnet definition
///
- /// @todo Add subnet-specific things here (e.g. subnet-specific options)
- ///
/// @param config_id name od the entry
+ ///
/// @return parser object for specified entry name
- /// @throw NotImplemented if trying to create a parser for unknown config element
+ /// @throw isc::dhcp::Dhcp6ConfigError if trying to create a parser
+ /// for unknown config element
DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "subnet", StringParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "pool", PoolParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
// Used for debugging only.
// return new DebugParser(config_id);
- isc_throw(NotImplemented,
- "Parser error: Subnet6 parameter not supported: "
+ isc_throw(isc::dhcp::Dhcp6ConfigError,
+ "parser error: subnet6 parameter not supported: "
<< config_id);
}
return (f->second(config_id));
}
- /// @brief returns value for a given parameter (after using inheritance)
+ /// @brief Returns value for a given parameter (after using inheritance)
///
/// This method implements inheritance. For a given parameter name, it first
/// checks if there is a global value for it and overwrites it with specific
@@ -1048,7 +1226,7 @@ private:
/// @param name name of the parameter
/// @return triplet with the parameter name
/// @throw Dhcp6ConfigError when requested parameter is not present
- Triplet<uint32_t> getParam(const std::string& name) {
+ isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
Uint32Storage::iterator global = uint32_defaults.find(name);
@@ -1064,9 +1242,9 @@ private:
}
if (found) {
- return (Triplet<uint32_t>(value));
+ return (isc::dhcp::Triplet<uint32_t>(value));
} else {
- isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+ isc_throw(isc::dhcp::Dhcp6ConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -1086,6 +1264,9 @@ private:
/// parsers are stored here
ParserCollection parsers_;
+
+ /// Pointer to the created subnet object.
+ isc::dhcp::Subnet6Ptr subnet_;
};
/// @brief this class parses a list of subnets
@@ -1131,7 +1312,7 @@ public:
// the old one and replace with the new one.
// remove old subnets
- CfgMgr::instance().deleteSubnets6();
+ isc::dhcp::CfgMgr::instance().deleteSubnets6();
BOOST_FOREACH(ParserPtr subnet, subnets_) {
subnet->commit();
@@ -1142,7 +1323,7 @@ public:
/// @brief Returns Subnet6ListConfigParser object
/// @param param_name name of the parameter
/// @return Subnets6ListConfigParser object
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Subnets6ListConfigParser(param_name));
}
@@ -1150,6 +1331,11 @@ public:
ParserCollection subnets_;
};
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
/// @brief creates global parsers
///
/// This method creates global parsers that parse global parameters, i.e.
@@ -1161,25 +1347,14 @@ public:
DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "interface", InterfaceListConfigParser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "subnet6", Subnets6ListConfigParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "version", StringParser::Factory));
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet6"] = Subnets6ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -1193,21 +1368,6 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
return (f->second(config_id));
}
-/// @brief configures DHCPv6 server
-///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
-///
-/// This method does not throw. It catches all exceptions and returns them as
-/// reconfiguration statuses. It may return the following response codes:
-/// 0 - configuration successful
-/// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
-///
-/// @param config_set a new configuration for DHCPv6 server
-/// @return answer that contains result of reconfiguration
-/// @throw Dhcp6ConfigError if trying to create a parser for NULL config
ConstElementPtr
configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
if (!config_set) {
@@ -1221,42 +1381,108 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
- ParserCollection parsers;
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet6 structure
+ // depend on the global values. Thus we need to make sure that
+ // the global values are processed first and that they can be
+ // accessed by the subnet6 parsers. We separate parsers that
+ // should process data first (independent_parsers) from those
+ // that must process data when the independent data is already
+ // processed (dependent_parsers).
+ ParserCollection independent_parsers;
+ ParserCollection dependent_parsers;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storages. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
try {
+ // Iterate over all independent parsers first (all but subnet6)
+ // and try to parse the data.
BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ if (config_pair.first != "subnet6") {
+ ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
+ }
- ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
+ // Process dependent configuration data.
+ BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ if (config_pair.first == "subnet6") {
+ ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+ dependent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ }
}
+
} catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed:") + ex.what());
- return (answer);
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed"));
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
}
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ BOOST_FOREACH(ParserPtr parser, dependent_parsers) {
+ parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed:")
+ + ex.what());
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
}
}
- catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed:") + ex.what());
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
return (answer);
- } catch (...) {
- // for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed"));
}
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
- ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index ed44bb9..408d01f 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -28,7 +28,7 @@ namespace dhcp {
class Dhcpv6Srv;
/// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv6Srv object.
+/// @c Dhcpv6Srv object.
class Dhcp6ConfigError : public isc::Exception {
public:
@@ -41,115 +41,25 @@ public:
: isc::Exception(file, line, what) {}
};
-/// @brief Base abstract class for all DHCPv6 parsers
+/// @brief Configures DHCPv6 server
///
-/// Each instance of a class derived from this class parses one specific config
-/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
-/// complex (e.g. a subnet). In such case, it is likely that a parser will
-/// spawn child parsers to parse child elements in the configuration.
-/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
-class DhcpConfigParser {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private to make it explicit that this is a
- /// pure base class.
- //@{
-private:
- DhcpConfigParser(const DhcpConfigParser& source);
- DhcpConfigParser& operator=(const DhcpConfigParser& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class should
- /// never be instantiated (except as part of a derived class).
- DhcpConfigParser() {}
-public:
- /// The destructor.
- virtual ~DhcpConfigParser() {}
- //@}
-
- /// \brief Prepare configuration value.
- ///
- /// This method parses the "value part" of the configuration identifier
- /// that corresponds to this derived class and prepares a new value to
- /// apply to the server.
- ///
- /// This method must validate the given value both in terms of syntax
- /// and semantics of the configuration, so that the server will be
- /// validly configured at the time of \c commit(). Note: the given
- /// configuration value is normally syntactically validated, but the
- /// \c build() implementation must also expect invalid input. If it
- /// detects an error it may throw an exception of a derived class
- /// of \c isc::Exception.
- ///
- /// Preparing a configuration value will often require resource
- /// allocation. If it fails, it may throw a corresponding standard
- /// exception.
- ///
- /// This method is not expected to be called more than once in the
- /// life of the object. Although multiple calls are not prohibited
- /// by the interface, the behavior is undefined.
- ///
- /// \param config_value The configuration value for the identifier
- /// corresponding to the derived class.
- virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
- /// \brief Apply the prepared configuration value to the server.
- ///
- /// This method is expected to be exception free, and, as a consequence,
- /// it should normally not involve resource allocation.
- /// Typically it would simply perform exception free assignment or swap
- /// operation on the value prepared in \c build().
- /// In some cases, however, it may be very difficult to meet this
- /// condition in a realistic way, while the failure case should really
- /// be very rare. In such a case it may throw, and, if the parser is
- /// called via \c configureDhcp6Server(), the caller will convert the
- /// exception as a fatal error.
- ///
- /// This method is expected to be called after \c build(), and only once.
- /// The result is undefined otherwise.
- virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
-///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
-/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
-/// that is, it ensures that either configuration is fully applied or the
-/// state of the server is intact.
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv6 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
///
-/// If a syntax or semantics level error happens during the configuration
-/// (such as malformed configuration or invalid configuration parameter),
-/// this function throws an exception of class \c Dhcp6ConfigError.
-/// If the given configuration requires resource allocation and it fails,
-/// a corresponding standard exception will be thrown.
-/// Other exceptions may also be thrown, depending on the implementation of
-/// the underlying derived class of \c Dhcp6ConfigError.
-/// In any case the strong guarantee is provided as described above except
-/// in the very rare cases where the \c commit() method of a parser throws
-/// an exception. If that happens this function converts the exception
-/// into a \c FatalError exception and rethrows it. This exception is
-/// expected to be caught at the highest level of the application to terminate
-/// the program gracefully.
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - commit failed (parsing was successful, but the values could not be
+/// stored in the configuration).
///
-/// \param server The \c Dhcpv6Srv object to be configured.
-/// \param config_set A JSON style configuration to apply to \c server.
+/// @param server DHCPv6 server object.
+/// @param config_set a new configuration for DHCPv6 server.
+/// @return answer that contains result of the reconfiguration.
+/// @throw Dhcp6ConfigError if trying to create a parser for NULL config.
isc::data::ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& server,
- isc::data::ConstElementPtr config_set);
+configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index 5350fd8..fa37769 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -35,19 +35,19 @@
This method iterates over list of received configuration elements and creates a
list of parsers for each received entry. Parser is an object that is derived
- from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
+ from a DhcpConfigParser class. Once a parser is created
(constructor), its value is set (using build() method). Once all parsers are
build, the configuration is then applied ("commited") and commit() method is
called.
All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
- are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
- unsigned 32 bit integer), but some are very specialized (e.g. \ref
- isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
- some cases, e.g. subnet6 definitions, the configuration entry is not a simple
- value, but a map or a list itself. In such case, the parser iterates over all
- elements and creates parsers for a given scope. This process may be repeated
- (sort of) recursively.
+ are generic (e.g. Uint32Parser that is able to handle any
+ unsigned 32 bit integer), but some are very specialized (e.g.
+ Subnets6ListConfigParser parses definitions of Subnet6 lists). In some cases,
+ e.g. subnet6 definitions, the configuration entry is not a simple value, but
+ a map or a list itself. In such case, the parser iterates over all elements
+ and creates parsers for a given scope. This process may be repeated (sort of)
+ recursively.
@section dhcpv6ConfigInherit DHCPv6 Configuration Inheritance
@@ -55,16 +55,16 @@
For example, renew-timer value may be specified at a global scope and it then
applies to all subnets. However, some subnets may have it overwritten with more
specific values that takes precedence over global values that are considered
- defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
- isc::dhcp::StringParser) implement that inheritance. By default, they store
- values in global uint32_defaults and string_defaults storages. However, it is
- possible to instruct them to store parsed values in more specific
- storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
- that has its own storage that is unique for each subnet. Finally, during commit
- phase (commit() method), appropriate parsers can use apply parameter inheritance.
+ defaults. Some parsers (e.g. Uint32Parser and StringParser) implement that
+ inheritance. By default, they store values in global uint32_defaults and
+ string_defaults storages. However, it is possible to instruct them to store
+ parsed values in more specific storages. That capability is used, e.g. in
+ Subnet6ConfigParser that has its own storage that is unique for each subnet.
+ Finally, during commit phase (commit() method), appropriate parsers can use
+ apply parameter inheritance.
Debugging configuration parser may be confusing. Therefore there is a special
- class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
+ class called DebugParser. It does not configure anything, but just
accepts any parameter of any type. If requested to commit configuration, it will
print out received parameter name and its value. This class is not currently used,
but it is convenient to have it every time a new parameter is added to DHCP
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index c5e9565..350b530 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -67,6 +67,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
} ]
}
},
@@ -152,6 +157,11 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
} ]
}
} ]
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index eb8923e..1913bb5 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -65,6 +65,13 @@ This informational message is printed every time DHCPv6 is started.
It indicates what database backend type is being to store lease and
other information.
+% DHCP6_LEASE_WITHOUT_DUID lease for address %1 does not have a DUID
+This error message indicates a database consistency failure. The lease
+database has an entry indicating that the given address is in use,
+but the lease does not contain any client identification. This is most
+likely due to a software error: please raise a bug report. As a temporary
+workaround, manually remove the lease entry from the database.
+
% DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
This debug message indicates that the server successfully advertised
a lease. It is up to the client to choose one server out of the
@@ -86,6 +93,37 @@ This message indicates that the server failed to grant (in response to
received REQUEST) a lease for a given client. There may be many reasons for
such failure. Each specific failure is logged in a separate log entry.
+% DHCP6_RELEASE address %1 belonging to client duid=%2, iaid=%3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP6_RELEASE_FAIL failed to remove lease for address %1 for duid=%2, iaid=%3
+This error message indicates that the software failed to remove a
+lease from the lease database. It probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP6_RELEASE_FAIL_WRONG_DUID client (duid=%1) tried to release address %2, but it belongs to client (duid=%3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client. However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP6_RELEASE_FAIL_WRONG_IAID client (duid=%1) tried to release address %2, but it used wrong IAID (expected %3, but got %4)
+This warning message indicates that client tried to release an address
+that does belong to it, but the address was expected to be in a different
+IA (identity association) container. This probably means that the client's
+support for multiple addresses is flawed.
+
+% DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id
+This warning message indicates that client sent RELEASE message without
+mandatory client-id option. This is most likely caused by a buggy client
+(or a relay that malformed forwarded message). This request will not be
+processed and a response with error status code will be sent back.
+
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv6 DHCP server but it is not running.
@@ -216,3 +254,8 @@ recently and does not recognize its well-behaving clients. This is more
probable if you see many such messages. Clients will recover from this,
but they will most likely get a different IP addresses and experience
a brief service interruption.
+
+% DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
+This warning message is printed when client attempts to release a lease,
+but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for
+possible reasons for such behavior.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index a50be03..ad8fb8c 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -436,6 +436,8 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(question);
@@ -604,7 +606,7 @@ OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
// Insert status code NoAddrsAvail.
- ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
"Sorry, no known leases for this duid/iaid."));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
@@ -640,6 +642,8 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
// We need to renew addresses for all IA_NA options in the client's
// RENEW message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(renew);
@@ -688,11 +692,176 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
break;
}
}
+}
+
+void Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
+
+ // We need to release addresses for all IA_NA options in the client's
+ // RELEASE message.
+ // @todo Add support for IA_TA
+ // @todo Add support for IA_PD
+ // @todo Consider supporting more than one address in a single IA_NA.
+ // That was envisaged by RFC3315, but it never happened. The only
+ // software that supports that is Dibbler, but its author seriously doubts
+ // if anyone is really using it. Clients that want more than one address
+ // just include more instances of IA_NA options.
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ // This should not happen. We have checked this before.
+ // see sanityCheck() called from processRelease()
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
+ .arg(release->getRemoteAddr().toText());
+
+ reply->addOption(createStatusCode(STATUS_UnspecFail,
+ "You did not include mandatory client-id"));
+ return;
+ }
+ DuidPtr duid(new DUID(opt_duid->getData()));
+
+ int general_status = STATUS_Success;
+ for (Option::OptionCollection::iterator opt = release->options_.begin();
+ opt != release->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ reply->addOption(answer_opt);
+ }
+ break;
+ }
+ // @todo: add support for IA_PD
+ // @todo: add support for IA_TA
+ default:
+ // remaining options are stateless and thus ignored in this context
+ ;
+ }
+ }
+
+ // To be pedantic, we should also include status code in the top-level
+ // scope, not just in each IA_NA. See RFC3315, section 18.2.6.
+ // This behavior will likely go away in RFC3315bis.
+ reply->addOption(createStatusCode(general_status,
+ "Summary status for all processed IA_NAs"));
+}
+
+OptionPtr Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia) {
+ // Release can be done in one of two ways:
+ // Approach 1: extract address from client's IA_NA and see if it belongs
+ // to this particular client.
+ // Approach 2: find a subnet for this client, get a lease for
+ // this subnet/duid/iaid and check if its content matches to what the
+ // client is asking us to release.
+ //
+ // This method implements approach 1.
+
+ // That's our response
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ boost::shared_ptr<Option6IAAddr> release_addr = boost::dynamic_pointer_cast<Option6IAAddr>
+ (ia->getOption(D6O_IAADDR));
+ if (!release_addr) {
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "You did not include address in your RELEASE"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(release_addr->getAddress());
+
+ if (!lease) {
+ // client releasing a lease that we don't know about.
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "Sorry, no known leases for this duid/iaid, can't release."));
+ general_status = STATUS_NoBinding;
+
+ LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE)
+ .arg(duid->toText())
+ .arg(ia->getIAID());
+
+ return (ia_rsp);
+ }
+
+ if (!lease->duid_) {
+ // Something is gravely wrong here. We do have a lease, but it does not
+ // have mandatory DUID information attached. Someone was messing with our
+ // database.
+
+ LOG_ERROR(dhcp6_logger, DHCP6_LEASE_WITHOUT_DUID)
+ .arg(release_addr->getAddress().toText());
+
+ general_status = STATUS_UnspecFail;
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Database consistency check failed when trying to RELEASE"));
+ return (ia_rsp);
+ }
+
+ if (*duid != *(lease->duid_)) {
+ // Sorry, it's not your address. You can't release it.
+
+ LOG_INFO(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_DUID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->duid_->toText());
+
+ general_status = STATUS_NoBinding;
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This address does not belong to you, you can't release it"));
+ return (ia_rsp);
+ }
+
+ if (ia->getIAID() != lease->iaid_) {
+ // This address belongs to this client, but to a different IA
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_IAID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->iaid_)
+ .arg(ia->getIAID());
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This is your address, but you used wrong IAID"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+
+ // It is not necessary to check if the address matches as we used
+ // getLease6(addr) method that is supposed to return a proper lease.
+ // Ok, we've passed all checks. Let's release this address.
+ if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Server failed to release a lease"));
+
+ LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+ general_status = STATUS_UnspecFail;
+
+ return (ia_rsp);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+ ia_rsp->addOption(createStatusCode(STATUS_Success,
+ "Lease released. Thank you, please come again."));
+
+ return (ia_rsp);
+ }
}
+
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
sanityCheck(solicit, MANDATORY, FORBIDDEN);
@@ -751,8 +920,16 @@ Pkt6Ptr Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
}
Pkt6Ptr Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
- /// @todo: Implement this
+
+ sanityCheck(release, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
+
+ copyDefaultOptions(release, reply);
+ appendDefaultOptions(release, reply);
+
+ releaseLeases(release, reply);
+
return reply;
}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index b6b0306..0f3e641 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -212,17 +212,39 @@ protected:
/// @brief Renews specific IA_NA option
///
- /// Generates response to IA_NA. This typically includes finding a lease that
- /// corresponds to the received address. If no such lease is found, an IA_NA
- /// response is generated with an appropriate status code.
+ /// Generates response to IA_NA in Renew. This typically includes finding a
+ /// lease that corresponds to the received address. If no such lease is
+ /// found, an IA_NA response is generated with an appropriate status code.
///
/// @param subnet subnet the sender belongs to
/// @param duid client's duid
/// @param question client's message
/// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
+ /// @brief Releases specific IA_NA option
+ ///
+ /// Generates response to IA_NA in Release message. This covers finding and
+ /// removal of a lease that corresponds to the received address. If no such
+ /// lease is found, an IA_NA response is generated with an appropriate
+ /// status code.
+ ///
+ /// As RFC 3315 requires that a single status code be sent for the whole message,
+ /// this method may update the passed general_status: it is set to SUCCESS when
+ /// message processing begins, but may be updated to some error code if the
+ /// release process fails.
+ ///
+ /// @param duid client's duid
+ /// @param question client's message
+ /// @param general_status a global status (it may be updated in case of errors)
+ /// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
+ OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia);
+
/// @brief Copies required options from client message to server answer.
///
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
@@ -271,6 +293,17 @@ protected:
/// @param reply server's response
void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+ /// @brief Attempts to release received addresses
+ ///
+ /// It iterates through received IA_NA options and attempts to release
+ /// received addresses. If no such leases are found, or the lease fails
+ /// proper checks (e.g. belongs to someone else), a proper status
+ /// code is added to reply message. Released addresses are not added
+ /// to REPLY packet, just its IA_NA containers.
+ /// @param release client's message asking to release
+ /// @param reply server's response
+ void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply);
+
/// @brief Sets server-identifier.
///
/// This method attempts to set server-identifier DUID. It loads it
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 7083598..e40f4cc 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -56,7 +56,8 @@ public:
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
- /// option value. These parameters are: "name", "code" and "data".
+ /// option value. These parameters are: "name", "code", "data" and
+ /// "csv-format".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
@@ -69,14 +70,22 @@ public:
params["name"] = param_value;
params["code"] = "80";
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "option_foo";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "option_foo";
params["code"] = "80";
params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "option_foo";
+ params["code"] = "80";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
@@ -105,9 +114,11 @@ public:
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
} else if (param.first == "code") {
- stream << "\"code\": " << param.second << "";
+ stream << "\"code\": " << param.second;;
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
}
}
stream <<
@@ -374,11 +385,12 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
- // returned value must be 2 (values error)
+ // returned value must be 1 (values error)
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
- EXPECT_EQ(2, rcode_);
+
+ EXPECT_EQ(1, rcode_);
}
// Goal of this test is to verify if pools can be defined
@@ -427,12 +439,14 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 101,"
- " \"data\": \"01\""
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -497,7 +511,8 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 100,"
- " \"data\": \"AB\""
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -505,12 +520,14 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 101,"
- " \"data\": \"01\""
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -567,7 +584,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 100,"
- " \"data\": \"0102030405060708090A\""
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
" } ]"
" },"
" {"
@@ -576,7 +594,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"option-data\": [ {"
" \"name\": \"option_foo2\","
" \"code\": 101,"
- " \"data\": \"FFFEFDFCFB\""
+ " \"data\": \"FFFEFDFCFB\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -737,7 +756,8 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
params["name"] = "OPTION_IA_NA";
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
- params["data"] = "ABCDEF01 02030405 06070809";
+ params["data"] = "12345, 6789, 1516";
+ params["csv-format"] = "True";
std::string config = createConfigWithOption(params);
ElementPtr json = Element::fromJSON(config);
@@ -778,9 +798,9 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
// If cast was successful we may use accessors exposed by
// Option6IA to validate that the content of this option
// has been set correctly.
- EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
- EXPECT_EQ(0x02030405, optionIA->getT1());
- EXPECT_EQ(0x06070809, optionIA->getT2());
+ EXPECT_EQ(12345, optionIA->getIAID());
+ EXPECT_EQ(6789, optionIA->getT1());
+ EXPECT_EQ(1516, optionIA->getT2());
}
};
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 4c02dde..ee0c725 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -59,6 +59,7 @@ public:
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
using Dhcpv6Srv::processRenew;
+ using Dhcpv6Srv::processRelease;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
using Dhcpv6Srv::sanityCheck;
@@ -143,11 +144,14 @@ public:
}
// Checks that server rejected IA_NA, i.e. that it has no addresses and
- // that expected status code really appears there.
+ // that expected status code really appears there. In some limited cases
+ // (reply to RELEASE) it may be used to verify positive case, where
+ // IA_NA response is expected to not include address.
+ //
// Status code indicates type of error encountered (in theory it can also
// indicate success, but servers typically don't send success status
// as this is the default result and it saves bandwidth)
- void checkRejectedIA_NA(const boost::shared_ptr<Option6IA>& ia,
+ void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
uint16_t expected_status_code) {
// Make sure there is no address assigned.
EXPECT_FALSE(ia->getOption(D6O_IAADDR));
@@ -158,6 +162,12 @@ public:
boost::shared_ptr<OptionCustom> status =
boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status_code == STATUS_Success && !status) {
+ return;
+ }
+
EXPECT_TRUE(status);
if (status) {
@@ -169,6 +179,26 @@ public:
}
}
+
+ void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+ boost::shared_ptr<OptionCustom> status =
+ boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
// Check that generated IAADDR option contains expected address.
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
@@ -340,23 +370,23 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
" \"option-data\": [ {"
" \"name\": \"OPTION_DNS_SERVERS\","
" \"code\": 23,"
- " \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
- "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+ " \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
+ " \"csv-format\": True"
" },"
" {"
" \"name\": \"OPTION_FOO\","
" \"code\": 1000,"
- " \"data\": \"1234\""
+ " \"data\": \"1234\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv(0)));
+ NakedDhcpv6Srv srv(0);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
@@ -369,7 +399,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- boost::shared_ptr<Pkt6> adv = srv->processSolicit(sol);
+ boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
// check if we get response at all
ASSERT_TRUE(adv);
@@ -393,7 +423,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(option_oro);
// Need to process SOLICIT again after requesting new option.
- adv = srv->processSolicit(sol);
+ adv = srv.processSolicit(sol);
ASSERT_TRUE(adv);
OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
@@ -444,8 +474,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -454,7 +483,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -467,7 +496,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -487,8 +516,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -505,7 +533,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -521,7 +549,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -541,8 +569,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -557,7 +584,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -571,7 +598,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -586,8 +613,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManySolicits) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
@@ -611,9 +637,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
sol3->addOption(clientid3);
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processSolicit(sol1);
- Pkt6Ptr reply2 = srv->processSolicit(sol2);
- Pkt6Ptr reply3 = srv->processSolicit(sol3);
+ Pkt6Ptr reply1 = srv.processSolicit(sol1);
+ Pkt6Ptr reply2 = srv.processSolicit(sol2);
+ Pkt6Ptr reply3 = srv.processSolicit(sol3);
// check if we get response at all
checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
@@ -634,9 +660,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -666,8 +692,7 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, RequestBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a REQUEST
Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
@@ -684,10 +709,10 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
req->addOption(clientid);
// server-id is mandatory in REQUEST
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRequest(req);
+ Pkt6Ptr reply = srv.processRequest(req);
// check if we get response at all
checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -703,7 +728,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
// check that the lease is really in the database
@@ -720,8 +745,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManyRequests) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
@@ -745,14 +769,14 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
req3->addOption(clientid3);
// server-id is mandatory in REQUEST
- req1->addOption(srv->getServerID());
- req2->addOption(srv->getServerID());
- req3->addOption(srv->getServerID());
+ req1->addOption(srv.getServerID());
+ req2->addOption(srv.getServerID());
+ req3->addOption(srv.getServerID());
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processRequest(req1);
- Pkt6Ptr reply2 = srv->processRequest(req2);
- Pkt6Ptr reply3 = srv->processRequest(req3);
+ Pkt6Ptr reply1 = srv.processRequest(req1);
+ Pkt6Ptr reply2 = srv.processRequest(req2);
+ Pkt6Ptr reply3 = srv.processRequest(req3);
// check if we get response at all
checkResponse(reply1, DHCPV6_REPLY, 1234);
@@ -773,9 +797,9 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -799,8 +823,7 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
// - returned REPLY message has IA that includes IAADDR
// - lease is actually renewed in LeaseMgr
TEST_F(Dhcpv6SrvTest, RenewBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
const IOAddress addr("2001:db8:1:1::cafe:babe");
const uint32_t iaid = 234;
@@ -841,10 +864,10 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
req->addOption(clientid);
// Server-id is mandatory in RENEW
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRenew(req);
+ Pkt6Ptr reply = srv.processRenew(req);
// Check if we get response at all
checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -860,7 +883,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
// Check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
// Check that the lease is really in the database
@@ -895,9 +918,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
// - returned REPLY message has IA that includes STATUS-CODE
// - No lease in LeaseMgr
TEST_F(Dhcpv6SrvTest, RenewReject) {
-
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
const IOAddress addr("2001:db8:1:1::dead");
const uint32_t transid = 1234;
@@ -925,12 +946,12 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
req->addOption(clientid);
// Server-id is mandatory in RENEW
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Case 1: No lease known to server
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRenew(req);
+ Pkt6Ptr reply = srv.processRenew(req);
// Check if we get response at all
checkResponse(reply, DHCPV6_REPLY, transid);
@@ -939,7 +960,7 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
// Check that IA_NA was returned and that there's an address included
ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(ia);
- checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
// Check that there is no lease added
l = LeaseMgrFactory::instance().getLease6(addr);
@@ -956,14 +977,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// Pass it to the server and hope for a REPLY
- reply = srv->processRenew(req);
+ reply = srv.processRenew(req);
checkResponse(reply, DHCPV6_REPLY, transid);
tmp = reply->getOption(D6O_IA_NA);
ASSERT_TRUE(tmp);
// Check that IA_NA was returned and that there's an address included
ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(ia);
- checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
// There is a iaid mis-match, so server should respond that there is
// no such address to renew.
@@ -975,14 +996,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
req->addOption(generateClientId(13)); // generate different DUID
// (with length 13)
- reply = srv->processRenew(req);
+ reply = srv.processRenew(req);
checkResponse(reply, DHCPV6_REPLY, transid);
tmp = reply->getOption(D6O_IA_NA);
ASSERT_TRUE(tmp);
// Check that IA_NA was returned and that there's an address included
ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(ia);
- checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
lease = LeaseMgrFactory::instance().getLease6(addr);
ASSERT_TRUE(lease);
@@ -992,10 +1013,198 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
}
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ checkIA_NAStatusCode(ia, STATUS_Success);
+ checkMsgStatusCode(reply, STATUS_Success);
+
+ // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
+ EXPECT_FALSE(tmp->getOption(D6O_IAADDR));
+
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+ ASSERT_FALSE(l);
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes STATUS-CODE
+// - No lease in LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseReject) {
+
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::dead");
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, transid));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: No lease known to server");
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // CASE 2: Lease is known and belongs to this client, but to a different IAID
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but to a different IAID");
+
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Pass it to the server and hope for a REPLY
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ req->delOption(D6O_CLIENTID);
+ ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+ ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+ req->addOption(generateClientId(13)); // generate different DUID
+ // (with length 13)
+
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Finally, let's cleanup the database
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
// This test verifies if the status code option is generated properly.
TEST_F(Dhcpv6SrvTest, StatusCode) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// a dummy content for client-id
uint8_t expected[] = {
@@ -1005,7 +1214,7 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
};
// Create the option.
- OptionPtr status = srv->createStatusCode(3, "ABCDE");
+ OptionPtr status = srv.createStatusCode(3, "ABCDE");
// Allocate an output buffer. We will store the option
// in wire format here.
OutputBuffer buf(sizeof(expected));
@@ -1019,34 +1228,34 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
// This test verifies if the sanityCheck() really checks options presence.
TEST_F(Dhcpv6SrvTest, sanityCheck) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
- // check that the packets originating from local addresses can be
+ // Set link-local sender address, so appropriate subnet can be
+ // selected for this packet.
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
// client-id is optional for information-request, so
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
// empty packet, no client-id, no server-id
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
RFCViolation);
// This doesn't make much sense, but let's check it for completeness
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
OptionPtr clientid = generateClientId();
pkt->addOption(clientid);
// client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
- pkt->addOption(srv->getServerID());
+ pkt->addOption(srv.getServerID());
// both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
// sane section ends here, let's do some negative tests as well
@@ -1054,13 +1263,13 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
pkt->addOption(clientid);
// with more than one client-id it should throw, no matter what
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
pkt->delOption(D6O_CLIENTID);
@@ -1069,20 +1278,21 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
// again we have only one client-id
// let's try different type of insanity - several server-ids
- pkt->addOption(srv->getServerID());
- pkt->addOption(srv->getServerID());
+ pkt->addOption(srv.getServerID());
+ pkt->addOption(srv.getServerID());
// with more than one server-id it should throw, no matter what
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
-
-
}
+/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
+/// to call processX() methods.
+
} // end of anonymous namespace
diff --git a/src/bin/loadzone/.gitignore b/src/bin/loadzone/.gitignore
index 286abba..41e280a 100644
--- a/src/bin/loadzone/.gitignore
+++ b/src/bin/loadzone/.gitignore
@@ -1,4 +1,5 @@
/b10-loadzone
+/b10-loadzone.py
/loadzone.py
/run_loadzone.sh
/b10-loadzone.8
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index f138e4f..b3d61d3 100755
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -18,7 +18,7 @@
PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
export PYTHON_EXEC
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/loadzone/tests/correct/known.test.out b/src/bin/loadzone/tests/correct/known.test.out
index eec692e..377158d 100644
--- a/src/bin/loadzone/tests/correct/known.test.out
+++ b/src/bin/loadzone/tests/correct/known.test.out
@@ -80,6 +80,6 @@ ns5.example.com. 90 IN A 4.4.4.4
comment.example.com. 60 IN SOA ns1.example.com. hostmaster.example.com. 1 43200 900 1814400 7200
comment.example.com. 60 IN NS ns1.example.com.
comment.example.com. 60 IN TXT "Simple text"
-comment.example.com. 60 IN TXT "; No comment"
+comment.example.com. 60 IN TXT "\; No comment"
comment.example.com. 60 IN TXT "Also no comment here"
-comment.example.com. 60 IN TXT "A combination ; see?"
+comment.example.com. 60 IN TXT "A combination \; see?"
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index cc0f09f..725aa85 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -200,14 +200,14 @@ public:
/**
* \short Get info about timeouts.
*
- * \returns Timeout and retries (as described in setTimeouts).
+ * \return Timeout and retries (as described in setTimeouts).
*/
std::pair<int, unsigned> getTimeouts() const;
/**
* \brief Get the timeout for outgoing queries
*
- * \returns Timeout for outgoing queries
+ * \return Timeout for outgoing queries
*/
int getQueryTimeout() const;
@@ -218,7 +218,7 @@ public:
* (internal resolving on the query will continue, see
* \c getLookupTimeout())
*
- * \returns Timeout for outgoing queries
+ * \return Timeout for outgoing queries
*/
int getClientTimeout() const;
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index bb84ae2..db25d9f 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -109,8 +109,7 @@ public:
/// \name pure virtuals, every derived class must implement these
- /// \returns true if the other ElementPtr has the same type and
- /// value
+ /// \return true if the other ElementPtr has the same type and value
virtual bool equals(const Element& other) const = 0;
/// Converts the Element to JSON format and appends it to
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 0bb1bfd..1be7c7a 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -93,8 +93,8 @@ public:
void setLocalConfig(isc::data::ElementPtr config) { _config = config; }
/// Returns the local (i.e. non-default) configuration.
- /// \returns An ElementPtr pointing to a MapElement containing all
- /// non-default configuration options.
+ /// \return An ElementPtr pointing to a MapElement containing all
+ /// non-default configuration options.
isc::data::ElementPtr getLocalConfig() { return (_config); }
/// Returns a list of all possible configuration options as specified
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index a4edd4e..ea70d92 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -38,6 +38,7 @@ libb10_datasrc_la_SOURCES += client_list.h client_list.cc
libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
libb10_datasrc_la_SOURCES += master_loader_callbacks.h
libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
+libb10_datasrc_la_SOURCES += rrset_collection.h rrset_collection.cc
libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
diff --git a/src/lib/datasrc/rrset_collection.cc b/src/lib/datasrc/rrset_collection.cc
new file mode 100644
index 0000000..e9a5fd8
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection.cc
@@ -0,0 +1,51 @@
+// 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/rrset_collection.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+ConstRRsetPtr
+RRsetCollection::find(const isc::dns::Name& name,
+ const isc::dns::RRClass&,
+ const isc::dns::RRType& rrtype) const
+{
+ // TODO: RRClass needs to be checked here to see if it is as
+ // expected.
+
+ ZoneFinder& finder = updater_->getFinder();
+ ZoneFinderContextPtr result =
+ finder.find(name, rrtype,
+ ZoneFinder::NO_WILDCARD | ZoneFinder::FIND_GLUE_OK);
+
+ return (result->rrset);
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getBeginning() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getEnd() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/rrset_collection.h b/src/lib/datasrc/rrset_collection.h
new file mode 100644
index 0000000..c8a6add
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection.h
@@ -0,0 +1,89 @@
+// 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 RRSET_COLLECTION_DATASRC_H
+#define RRSET_COLLECTION_DATASRC_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+#include <datasrc/zone.h>
+
+namespace isc {
+namespace datasrc {
+
+/// \brief datasrc implementation of RRsetCollectionBase.
+class RRsetCollection : public isc::dns::RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ /// \param updater The ZoneUpdater to wrap around.
+ RRsetCollection(ZoneUpdaterPtr updater) :
+ updater_(updater)
+ {}
+
+ /// \brief Destructor
+ virtual ~RRsetCollection() {}
+
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// \param name The name of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \returns The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const;
+
+private:
+ ZoneUpdaterPtr updater_;
+
+protected:
+ class DsIter : public RRsetCollectionBase::Iter {
+ public:
+ DsIter()
+ {}
+
+ virtual const isc::dns::AbstractRRset& getValue() {
+ isc_throw(isc::NotImplemented, "This method is not implemented.");
+ }
+
+ virtual IterPtr getNext() {
+ isc_throw(isc::NotImplemented, "This method is not implemented.");
+ }
+
+ virtual bool equals(Iter& other) {
+ const DsIter* other_real = dynamic_cast<DsIter*>(&other);
+ if (other_real == NULL) {
+ return (false);
+ }
+
+ isc_throw(isc::NotImplemented, "This method is not implemented.");
+ }
+ };
+
+ virtual RRsetCollectionBase::IterPtr getBeginning();
+ virtual RRsetCollectionBase::IterPtr getEnd();
+};
+
+} // end of namespace datasrc
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 174da04..9d81f5e 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -28,6 +28,7 @@
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/rrset_collection.h>
#include <testutils/dnsmessage_test.h>
@@ -4151,4 +4152,43 @@ TYPED_TEST(DatabaseClientTest, createZoneRollbackOnExists) {
ASSERT_TRUE(this->client_->createZone(new_name));
}
+class RRsetCollectionTest : public DatabaseClientTest<TestSQLite3Accessor> {
+public:
+ RRsetCollectionTest() :
+ DatabaseClientTest<TestSQLite3Accessor>(),
+ collection(this->client_->getUpdater(this->zname_, false))
+ {}
+
+ RRsetCollection collection;
+};
+
+TEST_F(RRsetCollectionTest, find) {
+ // Test the find() that returns ConstRRsetPtr
+ ConstRRsetPtr rrset = collection.find(Name("www.example.org."),
+ RRClass::IN(), RRType::A());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset->getClass());
+ EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+ // foo.example.org doesn't exist
+ rrset = collection.find(Name("foo.example.org"), qclass_, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, but not with MX
+ rrset = collection.find(Name("www.example.org"), qclass_, RRType::MX());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, with AAAA
+ rrset = collection.find(Name("www.example.org"), qclass_, RRType::AAAA());
+ EXPECT_TRUE(rrset);
+}
+
+TEST_F(RRsetCollectionTest, iteratorTest) {
+ // Iterators are currently not implemented.
+ EXPECT_THROW(collection.begin(), isc::NotImplemented);
+ EXPECT_THROW(collection.end(), isc::NotImplemented);
+}
+
}
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 6844712..85b167e 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -81,7 +81,7 @@ addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
}
DataSourceClientPtr
-createSQLite3Client(RRClass zclass, const Name& zname) {
+createSQLite3Client(RRClass zclass, const Name& zname, stringstream& ss) {
// We always begin with an empty template SQLite3 DB file and install
// the zone data from the zone file to ensure both cases have the
// same test data.
@@ -93,7 +93,6 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
// Note that neither updater nor SQLite3 accessor checks this condition,
// so this should succeed.
ZoneUpdaterPtr updater = client->getUpdater(zname, false);
- stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
masterLoad(ss, Name::ROOT_NAME(), zclass,
boost::bind(addRRset, updater, _1));
updater->commit();
@@ -101,6 +100,12 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
return (client);
}
+DataSourceClientPtr
+createSQLite3ClientWithNS(RRClass zclass, const Name& zname) {
+ stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
+ return (createSQLite3Client(zclass, zname, ss));
+}
+
// The test class. Its parameterized so we can share the test scnearios
// for any concrete data source implementaitons.
class ZoneFinderContextTest :
@@ -134,7 +139,7 @@ protected:
// We test the in-memory and SQLite3 data source implementations.
INSTANTIATE_TEST_CASE_P(, ZoneFinderContextTest,
::testing::Values(createInMemoryClient,
- createSQLite3Client));
+ createSQLite3ClientWithNS));
TEST_P(ZoneFinderContextTest, getAdditionalAuthNS) {
ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::NS());
@@ -430,4 +435,25 @@ TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
result_sets_.begin(), result_sets_.end());
}
+TEST(ZoneFinderContextSQLite3Test, escapedText) {
+ // This test checks that TXTLike data, when written to a database,
+ // is escaped correctly before stored in the database. The actual
+ // escaping is done in the toText() method of TXTLike objects, but
+ // we check anyway if this also carries over to the ZoneUpdater.
+ RRClass zclass(RRClass::IN());
+ Name zname("example.org");
+ stringstream ss("escaped.example.org. 3600 IN TXT Hello~World\\;\\\"");
+ DataSourceClientPtr client = createSQLite3Client(zclass, zname, ss);
+ ZoneFinderPtr finder = client->findZone(zname).zone_finder;
+
+ // If there is no escaping, the following will throw an exception
+ // when it tries to construct a TXT RRset using the data from the
+ // database.
+ ZoneFinderContextPtr ctx = finder->find(Name("escaped.example.org"),
+ RRType::TXT());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+ EXPECT_EQ("escaped.example.org. 3600 IN TXT \"Hello~World\\;\\\"\"\n",
+ ctx->rrset->toText());
+}
+
}
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index fc17361..81b4efd 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -27,6 +27,7 @@
#include <gtest/gtest.h>
#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
#include <string>
#include <vector>
@@ -34,6 +35,7 @@ using isc::dns::RRClass;
using isc::dns::Name;
using isc::dns::RRType;
using isc::dns::ConstRRsetPtr;
+using isc::dns::RRsetPtr;
using std::string;
using std::vector;
using boost::shared_ptr;
@@ -64,10 +66,9 @@ public:
// since many client methods are const, but we still want to know they
// were called.
mutable vector<Name> provided_updaters_;
- // We store string representations of the RRsets. This is simpler than
- // copying them and we can't really put them into shared pointers, because
- // we get them as references.
- vector<string> rrsets_;
+ vector<RRsetPtr> rrsets_;
+ // List of rrsets as texts, for easier manipulation
+ vector<string> rrset_texts_;
bool commit_called_;
// If set to true, getUpdater returns NULL
bool missing_zone_;
@@ -84,7 +85,7 @@ class Updater : public ZoneUpdater {
public:
Updater(MockClient* client, const Name& name) :
client_(client),
- finder_(client_->rrclass_, name)
+ finder_(client_->rrclass_, name, client_->rrsets_)
{}
virtual ZoneFinder& getFinder() {
return (finder_);
@@ -93,7 +94,18 @@ public:
if (client_->commit_called_) {
isc_throw(DataSourceError, "Add after commit");
}
- client_->rrsets_.push_back(rrset.toText());
+ // We need to copy the RRset. We don't do it properly (we omit the
+ // signature, for example), because we don't need to.
+ RRsetPtr newRRset(new isc::dns::BasicRRset(rrset.getName(),
+ rrset.getClass(),
+ rrset.getType(),
+ rrset.getTTL()));
+ for (isc::dns::RdataIteratorPtr i(rrset.getRdataIterator());
+ !i->isLast(); i->next()) {
+ newRRset->addRdata(i->getCurrent());
+ }
+ client_->rrsets_.push_back(newRRset);
+ client_->rrset_texts_.push_back(rrset.toText());
}
virtual void deleteRRset(const isc::dns::AbstractRRset&) {
isc_throw(isc::NotImplemented, "Method not used in tests");
@@ -105,9 +117,11 @@ private:
MockClient* client_;
class Finder : public ZoneFinder {
public:
- Finder(const RRClass& rrclass, const Name& name) :
+ Finder(const RRClass& rrclass, const Name& name,
+ const vector<RRsetPtr> &rrsets) :
class_(rrclass),
- name_(name)
+ name_(name),
+ rrsets_(rrsets)
{}
virtual RRClass getClass() const {
return (class_);
@@ -115,10 +129,23 @@ private:
virtual Name getOrigin() const {
return (name_);
}
- virtual shared_ptr<Context> find(const Name&, const RRType&,
- const FindOptions)
+ virtual shared_ptr<Context> find(const Name& name, const RRType& type,
+ const FindOptions options)
{
- isc_throw(isc::NotImplemented, "Method not used in tests");
+ // The method is not completely correct. It ignores many special
+ // cases and also the options except for the result. But this is
+ // enough for the tests. We care only about exact match here.
+ BOOST_FOREACH(const RRsetPtr& rrset, rrsets_) {
+ if (rrset->getName() == name && rrset->getType() == type) {
+ return (shared_ptr<Context>(
+ new GenericContext(*this, options,
+ ResultContext(SUCCESS,
+ rrset))));
+ }
+ }
+ return (shared_ptr<Context>(
+ new GenericContext(*this, options,
+ ResultContext(NXRRSET, ConstRRsetPtr()))));
}
virtual shared_ptr<Context> findAll(const Name&,
vector<ConstRRsetPtr>&,
@@ -132,6 +159,7 @@ private:
private:
const RRClass class_;
const Name name_;
+ const vector<RRsetPtr>& rrsets_;
} finder_;
};
@@ -197,12 +225,12 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
// The count is 34 because we expect the RRs to be separated.
EXPECT_EQ(34, destination_client_.rrsets_.size());
// Ensure known order.
- std::sort(destination_client_.rrsets_.begin(),
- destination_client_.rrsets_.end());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
- destination_client_.rrsets_.front());
+ destination_client_.rrset_texts_.front());
EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
- destination_client_.rrsets_.back());
+ destination_client_.rrset_texts_.back());
// It isn't possible to try again now
EXPECT_THROW(loader.load(), isc::InvalidOperation);
@@ -251,18 +279,18 @@ TEST_F(ZoneLoaderTest, copySigned) {
EXPECT_EQ(14, destination_client_.rrsets_.size());
EXPECT_TRUE(destination_client_.commit_called_);
// Same trick with sorting to know where they are
- std::sort(destination_client_.rrsets_.begin(),
- destination_client_.rrsets_.end());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
// Due to the R at the beginning, this one should be last
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
"1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
- destination_client_.rrsets_[0]);
+ destination_client_.rrset_texts_[0]);
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
"NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
" EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
"KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
"/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
- destination_client_.rrsets_[1]);
+ destination_client_.rrset_texts_[1]);
}
// If the destination zone does not exist, it throws
@@ -303,12 +331,12 @@ TEST_F(ZoneLoaderTest, loadUnsigned) {
// The count is 34 because we expect the RRs to be separated.
EXPECT_EQ(34, destination_client_.rrsets_.size());
// Ensure known order.
- std::sort(destination_client_.rrsets_.begin(),
- destination_client_.rrsets_.end());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
- destination_client_.rrsets_.front());
+ destination_client_.rrset_texts_.front());
EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
- destination_client_.rrsets_.back());
+ destination_client_.rrset_texts_.back());
// It isn't possible to try again now
EXPECT_THROW(loader.load(), isc::InvalidOperation);
@@ -361,18 +389,18 @@ TEST_F(ZoneLoaderTest, loadSigned) {
EXPECT_EQ(14, destination_client_.rrsets_.size());
EXPECT_TRUE(destination_client_.commit_called_);
// Same trick with sorting to know where they are
- std::sort(destination_client_.rrsets_.begin(),
- destination_client_.rrsets_.end());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
// Due to the R at the beginning, this one should be last
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
"1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
- destination_client_.rrsets_[0]);
+ destination_client_.rrset_texts_[0]);
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
"NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
" EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
"KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
"/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
- destination_client_.rrsets_[1]);
+ destination_client_.rrset_texts_[1]);
}
// Test it throws when there's no such file
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
index 10a22ac..d9204d1 100644
--- a/src/lib/datasrc/zone_loader.cc
+++ b/src/lib/datasrc/zone_loader.cc
@@ -20,9 +20,9 @@
#include <datasrc/iterator.h>
#include <datasrc/zone.h>
#include <datasrc/logger.h>
+#include <datasrc/rrset_collection.h>
#include <dns/rrset.h>
-#include <dns/rrset_collection.h>
#include <dns/zone_checker.h>
#include <boost/bind.hpp>
@@ -30,8 +30,6 @@
using isc::dns::Name;
using isc::dns::ConstRRsetPtr;
using isc::dns::MasterLoader;
-// XXX: Remove once the datasrc one exists
-using isc::dns::RRsetCollection;
namespace isc {
namespace datasrc {
@@ -147,9 +145,7 @@ ZoneLoader::loadIncremental(size_t limit) {
if (complete_) {
// Everything is loaded. Perform some basic sanity checks on the zone.
- // XXX: Pass the updater_ (or its finder) once we have the datasrc
- // collection
- RRsetCollection collection;
+ RRsetCollection collection(updater_);
dns::Name zone_name(updater_->getFinder().getOrigin());
dns::RRClass zone_class(updater_->getFinder().getClass());
dns::ZoneCheckerCallbacks
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index 91efe94..f1c8866 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -95,6 +95,16 @@ const std::vector<uint8_t> ClientId::getClientId() const {
return (duid_);
}
+// Returns the Client ID in text form
+std::string ClientId::toText() const {
+
+ // As DUID is a private base class of ClientId, we can't access
+ // its public toText() method through inheritance: instead we
+ // need the interface of a ClientId::toText() that calls the
+ // equivalent method in the base class.
+ return (DUID::toText());
+}
+
// Compares two client-ids
bool ClientId::operator==(const ClientId& other) const {
return (this->duid_ == other.duid_);
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 60b9706..a4e32b3 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -107,6 +107,9 @@ public:
/// @brief Returns reference to the client-id data
const std::vector<uint8_t> getClientId() const;
+ /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
+ std::string toText() const;
+
/// @brief Compares two client-ids for equality
bool operator==(const ClientId& other) const;
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index e1d7cb6..d97ca71 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -22,6 +22,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
using namespace std;
using namespace isc::util;
@@ -176,10 +177,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
if (values.empty()) {
isc_throw(InvalidOptionValue, "no option value specified");
}
- writeToBuffer(values[0], type_, buf);
+ writeToBuffer(util::str::trim(values[0]), type_, buf);
} else if (array_type_ && type_ != OPT_RECORD_TYPE) {
for (size_t i = 0; i < values.size(); ++i) {
- writeToBuffer(values[i], type_, buf);
+ writeToBuffer(util::str::trim(values[i]), type_, buf);
}
} else if (type_ == OPT_RECORD_TYPE) {
const RecordFieldsCollection& records = getRecordFields();
@@ -189,7 +190,8 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
<< " provided.");
}
for (size_t i = 0; i < records.size(); ++i) {
- writeToBuffer(values[i], records[i], buf);
+ writeToBuffer(util::str::trim(values[i]),
+ records[i], buf);
}
}
return (optionFactory(u, type, buf.begin(), buf.end()));
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 9f6bef2..468210f 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -361,7 +361,7 @@ public:
/// @brief Factory function to create option with array of integer values.
///
- /// @param universe (V4 or V6).
+ /// @param u universe (V4 or V6).
/// @param type option type.
/// @param begin iterator pointing to the beginning of the buffer.
/// @param end iterator pointing to the end of the buffer.
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 9db4c35..de20e51 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -108,6 +108,14 @@ TEST(DuidTest, getType) {
EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
}
+// Test checks if the toText() returns valid texual representation
+TEST(DuidTest, toText) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+ DUID duid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+}
+
// This test checks if the comparison operators are sane.
TEST(DuidTest, operators) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
@@ -174,8 +182,8 @@ TEST(ClientIdTest, operators) {
TEST(ClientIdTest, toText) {
uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
- DUID duid(data1, sizeof(data1));
- EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+ ClientId clientid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index b44fa7d..db7ea6a 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -8,19 +8,35 @@ endif
AM_CXXFLAGS = $(B10_CXXFLAGS)
+# Define rule to build logging source files from message file
+dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+# Tell Automake that the dhcpsrv_messages.{cc,h} source files are created in the
+# build process, so it must create these before doing anything else. Although
+# they are a dependency of the library (so will be created from the message file
+# anyway), there is no guarantee as to exactly _when_ in the build they will be
+# created. As the .h file is included in other sources file (so must be
+# present when they are compiled), the safest option is to create it first.
+BUILT_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+
# Some versions of GCC warn about some versions of Boost regarding
# missing initializer for members in its posix_time.
# https://svn.boost.org/trac/boost/ticket/3477
# But older GCC compilers don't have the flag.
AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
-CLEANFILES = *.gcno *.gcda
+# Make sure the generated files are deleted in a "clean" operation
+CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc
lib_LTLIBRARIES = libb10-dhcpsrv.la
libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
+libb10_dhcpsrv_la_SOURCES += hwaddr.cc hwaddr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
@@ -31,6 +47,8 @@ libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
+nodist_libb10_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+
libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
@@ -47,6 +65,9 @@ if USE_CLANGPP
libb10_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
endif
+# The message file should be in the distribution
+EXTRA_DIST = dhcpsrv_messages.mes
+
# Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST = dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
+EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
dist_pkgdata_DATA = dhcpdb_create.mysql
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 7dc5f55..cd41b4e 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -14,6 +14,7 @@
#include <asiolink/io_address.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
using namespace isc::asiolink;
using namespace isc::util;
@@ -21,9 +22,6 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
-
-
-
CfgMgr&
CfgMgr::instance() {
static CfgMgr cfg_mgr;
@@ -42,6 +40,9 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if ((subnets6_.size() == 1) && hint.getAddress().to_v6().is_link_local()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET6)
+ .arg(subnets6_[0]->toText()).arg(hint.toText());
return (subnets6_[0]);
}
@@ -49,11 +50,16 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET6)
+ .arg(hint.toText());
return (Subnet6Ptr());
}
@@ -65,6 +71,8 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+ .arg(subnet->toText());
subnets6_.push_back(subnet);
}
@@ -80,6 +88,9 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if (subnets4_.size() == 1) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET4)
+ .arg(subnets4_[0]->toText()).arg(hint.toText());
return (subnets4_[0]);
}
@@ -87,25 +98,34 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
for (Subnet4Collection::iterator subnet = subnets4_.begin();
subnet != subnets4_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET4)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET4)
+ .arg(hint.toText());
return (Subnet4Ptr());
}
void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
+ .arg(subnet->toText());
subnets4_.push_back(subnet);
}
void CfgMgr::deleteSubnets4() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET4);
subnets4_.clear();
}
void CfgMgr::deleteSubnets6() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET6);
subnets6_.clear();
}
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
new file mode 100644
index 0000000..7077552
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2013 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 DHCP_CONFIG_PARSER_H
+#define DHCP_CONFIG_PARSER_H
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration to DhcpConfigParser class.
+///
+/// It is only needed here to define types that are
+/// based on this class before the class definition.
+class DhcpConfigParser;
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
+
+/// @brief Collection of parsers.
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+/// @brief Base abstract class for all DHCP parsers
+///
+/// Each instance of a class derived from this class parses one specific config
+/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
+/// complex (e.g. a subnet). In such case, it is likely that a parser will
+/// spawn child parsers to parse child elements in the configuration.
+class DhcpConfigParser {
+ ///
+ /// @name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private to make it explicit that this is a
+ /// pure base class.
+ //@{
+private:
+
+ // Private construtor and assignment operator assures that nobody
+ // will be able to copy or assign a parser. There are no defined
+ // bodies for them.
+ DhcpConfigParser(const DhcpConfigParser& source);
+ DhcpConfigParser& operator=(const DhcpConfigParser& source);
+protected:
+ /// @brief The default constructor.
+ ///
+ /// This is intentionally defined as @c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ DhcpConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~DhcpConfigParser() {}
+ //@}
+
+ /// @brief Prepare configuration value.
+ ///
+ /// This method parses the "value part" of the configuration identifier
+ /// that corresponds to this derived class and prepares a new value to
+ /// apply to the server.
+ ///
+ /// This method must validate the given value both in terms of syntax
+ /// and semantics of the configuration, so that the server will be
+ /// validly configured at the time of @c commit(). Note: the given
+ /// configuration value is normally syntactically validated, but the
+ /// @c build() implementation must also expect invalid input. If it
+ /// detects an error it may throw an exception of a derived class
+ /// of @c isc::Exception.
+ ///
+ /// Preparing a configuration value will often require resource
+ /// allocation. If it fails, it may throw a corresponding standard
+ /// exception.
+ ///
+ /// This method is not expected to be called more than once in the
+ /// life of the object. Although multiple calls are not prohibited
+ /// by the interface, the behavior is undefined.
+ ///
+ /// @param config_value The configuration value for the identifier
+ /// corresponding to the derived class.
+ virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+ /// @brief Apply the prepared configuration value to the server.
+ ///
+ /// This method is expected to be exception free, and, as a consequence,
+ /// it should normally not involve resource allocation.
+ /// Typically it would simply perform exception free assignment or swap
+ /// operation on the value prepared in @c build().
+ /// In some cases, however, it may be very difficult to meet this
+ /// condition in a realistic way, while the failure case should really
+ /// be very rare. In such a case it may throw, and, if the parser is
+ /// called via @c configureDhcp4Server(), the caller will convert the
+ /// exception as a fatal error.
+ ///
+ /// This method is expected to be called after @c build(), and only once.
+ /// The result is undefined otherwise.
+ virtual void commit() = 0;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // DHCP_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.cc b/src/lib/dhcpsrv/dhcpsrv_log.cc
new file mode 100644
index 0000000..6cbfb0d
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "dhcpsrv/dhcpsrv_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger dhcpsrv_logger("dhcpsrv");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
new file mode 100644
index 0000000..9b6350a
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_LOG_H
+#define DHCPSRV_LOG_H
+
+#include <dhcpsrv/dhcpsrv_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief DHCP server library logging levels
+///
+/// Defines the levels used to output debug messages in the DHCP server
+/// library. Note that higher numbers equate to more verbose (and detailed)
+/// output.
+
+/// @brief Traces normal operations
+///
+/// E.g. sending a query to the database etc.
+const int DHCPSRV_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+/// @brief Records the results of the lookups
+///
+/// Using the example of tracing queries from the backend database, this will
+/// just record the summary results.
+const int DHCPSRV_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+
+/// @brief Additional information
+///
+/// Record detailed tracing. This is generally reserved for tracing access to
+/// the lease database.
+const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+
+/// @brief Additional information
+///
+/// Record detailed (and verbose) data on the server.
+const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+///@}
+
+
+/// \brief DHCP server library Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger dhcpsrv_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOG_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
new file mode 100644
index 0000000..27f12fc
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -0,0 +1,231 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv4 subnet to its database.
+
+% DHCPSRV_CFGMGR_ADD_SUBNET6 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv6 subnet to its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv4
+subnets in its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET6 deleting all IPv6 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv6
+subnets in its database.
+
+% DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv4 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_NO_SUBNET6 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv6 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_INVALID_ACCESS invalid database access string: %1
+This is logged when an attempt has been made to parse a database access string
+and the attempt ended in error. The access string in question - which
+should be of the form 'keyword=value keyword=value...' is included in
+the message.
+
+% DHCPSRV_MEMFILE_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_COMMIT commiting to memory file database
+The code has issued a commit call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_DB opening memory file lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a memory file lease database. The parameters of
+the connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MEMFILE_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease
+for the specified address from the memory file database for the specified
+address.
+
+% DHCPSRV_MEMFILE_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+client identification.
+
+% DHCPSRV_MEMFILE_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+hardware address.
+
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the memory file database for a client with the specified
+IAID (Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and client ID.
+
+% DHCPSRV_MEMFILE_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and hardware address.
+
+% DHCPSRV_MEMFILE_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the memory file database.
+
+% DHCPSRV_MEMFILE_ROLLBACK rolling back memory file database
+The code has issued a rollback call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_COMMIT commiting to MySQl database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the MySQL settings,
+the commital may not include a write to disk.
+
+% DHCPSRV_MYSQL_DB opening MySQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a MySQL lease database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MYSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+client identification.
+
+% DHCPSRV_MYSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_MYSQL_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the MySQL database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_MYSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_MYSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the MySQL database.
+
+% DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
+This is an error message, logged when an attempt has been made to access
+a database backend, but where no 'type' keyword has been included in
+the access string. The access string (less any passwords) is included
+in the message.
+
+% DHCPSRV_UNKNOWN_DB unknown database type: %1
+The database access string specified a database type (given in the
+message) that is unknown to the software. This is a configuration error.
diff --git a/src/lib/dhcpsrv/hwaddr.cc b/src/lib/dhcpsrv/hwaddr.cc
new file mode 100644
index 0000000..25716e0
--- /dev/null
+++ b/src/lib/dhcpsrv/hwaddr.cc
@@ -0,0 +1,42 @@
+// 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 <dhcpsrv/hwaddr.h>
+
+#include <string>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+std::string
+hardwareAddressString(const HWAddr& hwaddr) {
+ std::ostringstream stream;
+
+ for (size_t i = 0; i < hwaddr.size(); ++i) {
+ if (i > 0) {
+ stream << ":";
+ }
+ stream << std::setw(2) << std::hex << std::setfill('0')
+ << static_cast<unsigned int>(hwaddr[i]);
+ }
+
+ return (stream.str());
+}
+
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/hwaddr.h b/src/lib/dhcpsrv/hwaddr.h
new file mode 100644
index 0000000..776f37b
--- /dev/null
+++ b/src/lib/dhcpsrv/hwaddr.h
@@ -0,0 +1,44 @@
+// 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 HWADDR_H
+#define HWADDR_H
+
+#include <string>
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Hardware Address
+typedef std::vector<uint8_t> HWAddr;
+
+/// @brief Produce string representation of hardware address
+///
+/// Returns a string containing the hardware address. This is only used for
+/// logging.
+///
+/// @todo Create a "hardware address" class of which this will be a member.
+///
+/// @param hwaddr Hardware address to convert to string form
+///
+/// @return String form of the hardware address.
+std::string
+hardwareAddressString(const HWAddr& hwaddr);
+
+}; // namespace dhcp
+}; // namespace isc
+
+#endif // HWADDR_H
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index b3a605d..996fd58 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -63,7 +63,19 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
}
std::string
-Lease6::toText() {
+Lease4::toText() const {
+ ostringstream stream;
+
+ stream << "Address: " << addr_.toText() << "\n"
+ << "Valid life: " << valid_lft_ << "\n"
+ << "Cltt: " << cltt_ << "\n"
+ << "Subnet ID: " << subnet_id_ << "\n";
+
+ return (stream.str());
+}
+
+std::string
+Lease6::toText() const {
ostringstream stream;
stream << "Type: " << static_cast<int>(type_) << " (";
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 3fe11e6..375a586 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcpsrv/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
@@ -25,6 +26,7 @@
#include <boost/shared_ptr.hpp>
#include <fstream>
+#include <iostream>
#include <map>
#include <string>
#include <utility>
@@ -61,8 +63,6 @@
/// Nevertheless, we hope to have failover protocol eventually implemented in
/// the Kea.
-#include <iostream>
-
namespace isc {
namespace dhcp {
@@ -222,12 +222,17 @@ struct Lease4 {
comments_()
{}
- /// @brief Default Constructor
+ /// @brief Default constructor
///
/// Initialize fields that don't have a default constructor.
Lease4() : addr_(0), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false)
{}
+ /// @brief Convert lease to printable form
+ ///
+ /// @return Textual represenation of lease data
+ std::string toText() const;
+
/// @brief Compare two leases for equality
///
/// @param other lease6 object with which to compare
@@ -377,7 +382,7 @@ struct Lease6 {
/// @brief Convert Lease6 to Printable Form
///
/// @return String form of the lease
- std::string toText();
+ std::string toText() const;
/// @brief returns true if the lease is expired
/// @return true if the lease is expired
@@ -417,9 +422,6 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
/// see the documentation of those classes for details.
class LeaseMgr {
public:
- /// Client hardware address
- typedef std::vector<uint8_t> HWAddr;
-
/// Database configuration parameter map
typedef std::map<std::string, std::string> ParameterMap;
@@ -474,7 +476,7 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const = 0;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -486,7 +488,7 @@ public:
/// @param subnet_id identifier of the subnet that lease must belong to
///
/// @return a pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
SubnetID subnet_id) const = 0;
/// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc
index 47c53ed..9fd276d 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.cc
+++ b/src/lib/dhcpsrv/lease_mgr_factory.cc
@@ -14,6 +14,7 @@
#include "config.h"
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/memfile_lease_mgr.h>
#ifdef HAVE_MYSQL
@@ -43,17 +44,17 @@ LeaseMgrFactory::getLeaseMgrPtr() {
}
LeaseMgr::ParameterMap
-LeaseMgrFactory::parse(const std::string& dbconfig) {
+LeaseMgrFactory::parse(const std::string& dbaccess) {
LeaseMgr::ParameterMap mapped_tokens;
- if (!dbconfig.empty()) {
+ if (!dbaccess.empty()) {
vector<string> tokens;
// We need to pass a string to is_any_of, not just char*. Otherwise
// there are cryptic warnings on Debian6 running g++ 4.4 in
// /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
// array bounds"
- boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
+ boost::split(tokens, dbaccess, boost::is_any_of(string("\t ")));
BOOST_FOREACH(std::string token, tokens) {
size_t pos = token.find("=");
if (pos != string::npos) {
@@ -61,6 +62,7 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
string value = token.substr(pos + 1);
mapped_tokens.insert(make_pair(name, value));
} else {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_INVALID_ACCESS).arg(dbaccess);
isc_throw(InvalidParameter, "Cannot parse " << token
<< ", expected format is name=value");
}
@@ -70,31 +72,70 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
return (mapped_tokens);
}
+std::string
+LeaseMgrFactory::redactedAccessString(const LeaseMgr::ParameterMap& parameters) {
+ // Reconstruct the access string: start of with an empty string, then
+ // work through all the parameters in the original string and add them.
+ std::string access;
+ for (LeaseMgr::ParameterMap::const_iterator i = parameters.begin();
+ i != parameters.end(); ++i) {
+
+ // Separate second and subsequent tokens are preceded by a space.
+ if (!access.empty()) {
+ access += " ";
+ }
+
+ // Append name of parameter...
+ access += i->first;
+ access += "=";
+
+ // ... and the value, except in the case of the password, where a
+ // redacted value is appended.
+ if (i->first == std::string("password")) {
+ access += "*****";
+ } else {
+ access += i->second;
+ }
+ }
+
+ return (access);
+}
+
void
-LeaseMgrFactory::create(const std::string& dbconfig) {
+LeaseMgrFactory::create(const std::string& dbaccess) {
const std::string type = "type";
+ // Parse the access string and create a redacted string for logging.
+ LeaseMgr::ParameterMap parameters = parse(dbaccess);
+ std::string redacted = redactedAccessString(parameters);
+
// Is "type" present?
- LeaseMgr::ParameterMap parameters = parse(dbconfig);
if (parameters.find(type) == parameters.end()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
isc_throw(InvalidParameter, "Database configuration parameters do not "
"contain the 'type' keyword");
}
+
// Yes, check what it is.
#ifdef HAVE_MYSQL
if (parameters[type] == string("mysql")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
return;
}
#endif
if (parameters[type] == string("memfile")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MEMFILE_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new Memfile_LeaseMgr(parameters));
return;
}
// Get here on no match
- isc_throw(InvalidType, "Database configuration parameter 'type' does "
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+ isc_throw(InvalidType, "Database access parameter 'type' does "
"not specify a supported database backend");
}
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h
index 338c941..9218cf5 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.h
+++ b/src/lib/dhcpsrv/lease_mgr_factory.h
@@ -66,21 +66,21 @@ public:
/// @note When called, the current lease manager is <b>always</b> destroyed
/// and a new one created - even if the parameters are the same.
///
- /// dbconfig is a generic way of passing parameters. Parameters are passed
+ /// dbaccess is a generic way of passing parameters. Parameters are passed
/// in the "name=value" format, separated by spaces. The data MUST include
/// a keyword/value pair of the form "type=dbtype" giving the database
/// type, e.q. "mysql" or "sqlite3".
///
- /// @param dbconfig Database configuration parameters. These are in
- /// the form of "keyword=value" pairs, separated by spaces. These
- /// are back-end specific, although must include the "type" keyword
- /// which gives the backend in use.
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
///
- /// @throw isc::InvalidParameter dbconfig string does not contain the "type"
+ /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
/// keyword.
- /// @throw isc::dhcp::InvalidType The "type" keyword in dbconfig does not
+ /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
/// identify a supported backend.
- static void create(const std::string& dbconfig);
+ static void create(const std::string& dbaccess);
/// @brief Destroy lease manager
///
@@ -89,7 +89,7 @@ public:
/// lease manager is available.
static void destroy();
- /// @brief Return Current Lease Manager
+ /// @brief Return current lease manager
///
/// Returns an instance of the "current" lease manager. An exception
/// will be thrown if none is available.
@@ -98,15 +98,26 @@ public:
/// create() to create one before calling this method.
static LeaseMgr& instance();
- /// @brief Parse Database Parameters
+ /// @brief Parse database access string
///
/// Parses the string of "keyword=value" pairs and separates them
/// out into the map.
///
- /// @param dbconfig Database configuration string
+ /// @param dbaccess Database access string.
///
/// @return std::map<std::string, std::string> Map of keyword/value pairs.
- static LeaseMgr::ParameterMap parse(const std::string& dbconfig);
+ static LeaseMgr::ParameterMap parse(const std::string& dbaccess);
+
+ /// @brief Redact database access string
+ ///
+ /// Takes the database parameters and returns a database access string
+ /// passwords replaced by asterisks. This string is used in log messages.
+ ///
+ /// @param parameters Database access parameters (output of "parse").
+ ///
+ /// @return Redacted database access string.
+ static std::string redactedAccessString(
+ const LeaseMgr::ParameterMap& parameters);
private:
/// @brief Hold pointer to lease manager
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index 79cf1c7..6b2c847 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/memfile_lease_mgr.h>
#include <iostream>
@@ -27,11 +28,16 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
Memfile_LeaseMgr::~Memfile_LeaseMgr() {
}
-bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
+bool Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
return (false);
}
bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
+
if (getLease6(lease->addr_)) {
// there is a lease with specified address already
return (false);
@@ -40,30 +46,48 @@ bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
return (true);
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(
+ const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
+
return (Lease4Ptr());
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_HWADDR).arg(hardwareAddressString(hwaddr));
+
return (Lease4Collection());
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
- SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
+ .arg(hardwareAddressString(hwaddr));
return (Lease4Ptr());
}
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_CLIENTID).arg(clientid.toText());
+ return (Lease4Collection());
+}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
- SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& clientid,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
+ .arg(clientid.toText());
return (Lease4Ptr());
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
- return (Lease4Collection());
-}
+Lease6Ptr Memfile_LeaseMgr::getLease6(
+ const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR6).arg(addr.toText());
-Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Storage::iterator l = storage6_.find(addr);
if (l == storage6_.end()) {
return (Lease6Ptr());
@@ -72,12 +96,20 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) cons
}
}
-Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& duid,
+ uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_DUID).arg(iaid).arg(duid.toText());
+
return (Lease6Collection());
}
Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
+
/// @todo: Slow, naive implementation. Write it using additional indexes
for (Lease6Storage::iterator l = storage6_.begin(); l != storage6_.end(); ++l) {
if ( (*((*l)->duid_) == duid) &&
@@ -89,14 +121,22 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
return (Lease6Ptr());
}
-void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
+
}
-void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
+
}
bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
if (addr.isV4()) {
// V4 not implemented yet
return (false);
@@ -122,8 +162,11 @@ std::string Memfile_LeaseMgr::getDescription() const {
void
Memfile_LeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
}
void
Memfile_LeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ROLLBACK);
}
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 268b722..85813ec 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MEMFILE_LEASE_MGR_H
#define MEMFILE_LEASE_MGR_H
+#include <dhcpsrv/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/multi_index/indexed_by.hpp>
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index ad6e66c..a174aa0 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -16,14 +16,15 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/mysql_lease_mgr.h>
#include <boost/static_assert.hpp>
#include <mysql/mysqld_error.h>
-#include <algorithm>
#include <iostream>
#include <iomanip>
+#include <sstream>
#include <string>
#include <time.h>
@@ -193,6 +194,8 @@ TaggedStatement tagged_statements[] = {
}; // Anonymous namespace
+
+
namespace isc {
namespace dhcp {
@@ -1112,6 +1115,9 @@ MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
bool
MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_ADD_ADDR4).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the lease
std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
@@ -1121,6 +1127,9 @@ MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
bool
MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_ADD_ADDR6).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the lease
std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
@@ -1257,6 +1266,9 @@ void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
Lease4Ptr
MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR4).arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1276,6 +1288,9 @@ MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
Lease4Collection
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_HWADDR).arg(hardwareAddressString(hwaddr));
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1303,6 +1318,10 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id).arg(hardwareAddressString(hwaddr));
+
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
@@ -1334,6 +1353,9 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
Lease4Collection
MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_CLIENTID).arg(clientid.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1355,6 +1377,10 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_CLIENTID)
+ .arg(subnet_id).arg(clientid.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
@@ -1380,6 +1406,9 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
Lease6Ptr
MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR6).arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1403,6 +1432,8 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Collection
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_DUID).arg(iaid).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
@@ -1444,6 +1475,9 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
Lease6Ptr
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
@@ -1511,6 +1545,9 @@ void
MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE4;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR4).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the data being updated
std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
@@ -1533,6 +1570,9 @@ void
MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE6;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR6).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the data being updated
std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
@@ -1579,6 +1619,8 @@ MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
bool
MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_DELETE_ADDR).arg(addr.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
@@ -1632,6 +1674,9 @@ std::pair<uint32_t, uint32_t>
MySqlLeaseMgr::getVersion() const {
const StatementIndex stindex = GET_VERSION;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_VERSION);
+
uint32_t major; // Major version number
uint32_t minor; // Minor version number
@@ -1678,6 +1723,7 @@ MySqlLeaseMgr::getVersion() const {
void
MySqlLeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
if (mysql_commit(mysql_) != 0) {
isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_));
}
@@ -1686,6 +1732,7 @@ MySqlLeaseMgr::commit() {
void
MySqlLeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
if (mysql_rollback(mysql_) != 0) {
isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_));
}
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 68b0e1c..2103731 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MYSQL_LEASE_MGR_H
#define MYSQL_LEASE_MGR_H
+#include <dhcpsrv/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/scoped_ptr.hpp>
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 3f8733f..c23ecda 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -77,8 +77,8 @@ void Subnet4::addPool4(const Pool4Ptr& pool) {
if (!inRange(first_addr) || !inRange(last_addr)) {
isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
- << ") subnet4");
+ << " does not belong in this (" << prefix_.toText() << "/"
+ << static_cast<int>(prefix_len_) << ") subnet4");
}
/// @todo: Check that pools do not overlap
@@ -148,10 +148,10 @@ void Subnet6::addPool6(const Pool6Ptr& pool) {
if (!inRange(first_addr) || !inRange(last_addr)) {
isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
+ << ") does not belong in this (" << prefix_.toText()
+ << "/" << static_cast<int>(prefix_len_)
<< ") subnet6");
}
-
/// @todo: Check that pools do not overlap
pools_.push_back(pool);
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 11e1d75..7f8d634 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -30,6 +30,7 @@ libdhcpsrv_unittests_SOURCES = run_unittests.cc
libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += hwaddr_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 7b23bcf..353d833 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -42,6 +42,7 @@ public:
}
~CfgMgrTest() {
+ CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
}
};
@@ -56,8 +57,8 @@ TEST_F(CfgMgrTest, subnet4) {
Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
cfg_mgr.addSubnet4(subnet1);
@@ -74,7 +75,13 @@ TEST_F(CfgMgrTest, subnet4) {
EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
// Try to find an address that does not belong to any subnet
- EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets4();
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
}
// This test verifies if the configuration manager is able to hold and return
@@ -87,8 +94,8 @@ TEST_F(CfgMgrTest, subnet6) {
Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("2000::1")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1")));
cfg_mgr.addSubnet6(subnet1);
@@ -104,12 +111,13 @@ TEST_F(CfgMgrTest, subnet6) {
EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ // Check that deletion of the subnets works.
cfg_mgr.deleteSubnets6();
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("200::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/hwaddr_unittest.cc b/src/lib/dhcpsrv/tests/hwaddr_unittest.cc
new file mode 100644
index 0000000..f6b769d
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/hwaddr_unittest.cc
@@ -0,0 +1,46 @@
+// 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 <dhcpsrv/hwaddr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc::dhcp;
+
+namespace {
+
+TEST(HwaddrTest, stringConversion) {
+
+ // Check that an empty vector returns an appropriate string
+ HWAddr hwaddr;
+ std::string result = hardwareAddressString(hwaddr);
+ EXPECT_EQ(std::string(""), result);
+
+ // ... that a single-byte string is OK
+ hwaddr.push_back(0xc3);
+ result = hardwareAddressString(hwaddr);
+ EXPECT_EQ(std::string("c3"), result);
+
+ // ... and that a multi-byte string works
+ hwaddr.push_back(0x7);
+ hwaddr.push_back(0xa2);
+ hwaddr.push_back(0xe8);
+ hwaddr.push_back(0x42);
+ result = hardwareAddressString(hwaddr);
+ EXPECT_EQ(std::string("c3:07:a2:e8:42"), result);
+}
+
+}; // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
index e343c44..9924476 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -37,16 +38,136 @@ public:
}
};
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
+// This test checks that a database access string can be parsed correctly.
TEST_F(LeaseMgrFactoryTest, parse) {
- std::map<std::string, std::string> parameters = LeaseMgrFactory::parse(
- "param1=value1 param2=value2 param3=value3");
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(
+ "user=me password=forbidden name=kea somethingelse= type=mysql");
- EXPECT_EQ("value1", parameters["param1"]);
- EXPECT_EQ("value2", parameters["param2"]);
- EXPECT_TRUE(parameters.find("type") == parameters.end());
+ EXPECT_EQ(5, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+ EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST_F(LeaseMgrFactoryTest, parseInvalid) {
+
+ // No tokens in the string, so we expect no parameters
+ std::string invalid = "";
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(0, parameters.size());
+
+ // With spaces, there are some tokens so we expect invalid parameter
+ // as there are no equals signs.
+ invalid = " \t ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ invalid = " noequalshere ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ // A single "=" is valid string, but is placed here as the result is
+ // expected to be nothing.
+ invalid = "=";
+ parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(1, parameters.size());
+ EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactConfigString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST_F(LeaseMgrFactoryTest, redactAccessString) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me password=forbidden name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringEmptyPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql password=");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // ... and again to check that the position of the empty password in the
+ // string does not matter.
+ parameters = LeaseMgrFactory::parse("user=me password= name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begion with.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringNoPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql");
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
}
}; // end of anonymous namespace
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 88846f0..01bf671 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -393,7 +393,7 @@ private:
shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
const RRClass zone_class_;
MasterLoaderCallbacks callbacks_;
- AddRRCallback add_callback_;
+ const AddRRCallback add_callback_;
boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
// unspecified. If NULL no default
// is known.
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
index f9cc18b..e725595 100644
--- a/src/lib/dns/master_loader_callbacks.h
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -100,7 +100,7 @@ public:
/// If the caller of the loader wants to abort, it is possible to throw
/// from the callback, which aborts the load.
void error(const std::string& source_name, size_t source_line,
- const std::string& reason)
+ const std::string& reason) const
{
error_(source_name, source_line, reason);
}
@@ -117,7 +117,7 @@ public:
/// may be false positives), it is possible to throw from inside the
/// callback.
void warning(const std::string& source_name, size_t source_line,
- const std::string& reason)
+ const std::string& reason) const
{
warning_(source_name, source_line, reason);
}
@@ -133,7 +133,7 @@ public:
static MasterLoaderCallbacks getNullCallbacks();
private:
- IssueCallback error_, warning_;
+ const IssueCallback error_, warning_;
};
}
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
index fb4c9b4..e2857b3 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.cc
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -57,8 +57,8 @@ decimalToNumber(const char* s, const char* s_end) {
}
void
-strToCharString(const MasterToken::StringRegion& str_region,
- CharString& result)
+stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result)
{
// make a space for the 1-byte length field; filled in at the end
result.push_back(0);
@@ -91,6 +91,29 @@ strToCharString(const MasterToken::StringRegion& str_region,
result[0] = result.size() - 1;
}
+std::string
+charStringToString(const CharString& char_string) {
+ std::string s;
+ for (CharString::const_iterator it = char_string.begin() + 1;
+ it != char_string.end(); ++it) {
+ const uint8_t ch = *it;
+ if ((ch < 0x20) || (ch >= 0x7f)) {
+ // convert to escaped \xxx (decimal) format
+ s.push_back('\\');
+ s.push_back('0' + ((ch / 100) % 10));
+ s.push_back('0' + ((ch / 10) % 10));
+ s.push_back('0' + (ch % 10));
+ continue;
+ }
+ if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+ s.push_back('\\');
+ }
+ s.push_back(ch);
+ }
+
+ return (s);
+}
+
} // end of detail
} // end of generic
} // end of rdata
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
index 702af04..4a146db 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.h
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -17,6 +17,7 @@
#include <dns/master_lexer.h>
+#include <string>
#include <vector>
#include <stdint.h>
@@ -48,8 +49,22 @@ typedef std::vector<uint8_t> CharString;
/// \brief str_region A string that represents a character-string.
/// \brief result A placeholder vector where the resulting data are to be
/// stored. Expected to be empty, but it's not checked.
-void strToCharString(const MasterToken::StringRegion& str_region,
- CharString& result);
+void stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result);
+
+/// \brief Convert a CharString into a textual DNS character-string.
+///
+/// This method converts a binary 8-bit representation of a DNS
+/// character string into a textual string representation, escaping any
+/// special characters in the process. For example, characters like
+/// double-quotes, semi-colon and backspace are prefixed with backspace
+/// character, and characters not in the printable range of [0x20, 0x7e]
+/// (inclusive) are converted to the \xxx 3-digit decimal
+/// representation.
+///
+/// \param char_string The \c CharString to convert.
+/// \return A string representation of \c char_string.
+std::string charStringToString(const CharString& char_string);
} // namespace detail
} // namespace generic
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
index d1916e3..b48d109 100644
--- a/src/lib/dns/rdata/generic/detail/txt_like.h
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -119,7 +119,7 @@ private:
break;
}
string_list_.push_back(std::vector<uint8_t>());
- strToCharString(token.getStringRegion(), string_list_.back());
+ stringToCharString(token.getStringRegion(), string_list_.back());
}
// Let upper layer handle eol/eof.
@@ -177,18 +177,14 @@ public:
toText() const {
std::string s;
- // XXX: this implementation is not entirely correct. for example, it
- // should escape double-quotes if they appear in the character string.
for (std::vector<std::vector<uint8_t> >::const_iterator it =
- string_list_.begin();
- it != string_list_.end();
- ++it)
+ string_list_.begin(); it != string_list_.end(); ++it)
{
if (!s.empty()) {
s.push_back(' ');
}
s.push_back('"');
- s.insert(s.end(), (*it).begin() + 1, (*it).end());
+ s.append(charStringToString(*it));
s.push_back('"');
}
diff --git a/src/lib/dns/rrcollator.cc b/src/lib/dns/rrcollator.cc
index 4b12222..153de04 100644
--- a/src/lib/dns/rrcollator.cc
+++ b/src/lib/dns/rrcollator.cc
@@ -42,7 +42,7 @@ public:
const RdataPtr& rdata);
RRsetPtr current_rrset_;
- AddRRsetCallback callback_;
+ const AddRRsetCallback callback_;
};
namespace {
diff --git a/src/lib/dns/rrset_collection.cc b/src/lib/dns/rrset_collection.cc
index f8cfbc3..8711c3f 100644
--- a/src/lib/dns/rrset_collection.cc
+++ b/src/lib/dns/rrset_collection.cc
@@ -65,7 +65,7 @@ RRsetCollection::constructHelper(T source, const isc::dns::Name& origin,
RRsetCollection::RRsetCollection(const char* filename, const Name& origin,
const RRClass& rrclass)
{
- constructHelper<const char*>(filename, origin, rrclass);
+ constructHelper(filename, origin, rrclass);
}
RRsetCollection::RRsetCollection(std::istream& input_stream, const Name& origin,
diff --git a/src/lib/dns/rrset_collection.h b/src/lib/dns/rrset_collection.h
index 49dc820..62dd9a9 100644
--- a/src/lib/dns/rrset_collection.h
+++ b/src/lib/dns/rrset_collection.h
@@ -42,9 +42,9 @@ public:
/// The \c origin and \c rrclass arguments are required for the zone
/// loading, but \c RRsetCollection itself does not do any
/// validation, and the collection of RRsets does not have to form a
- /// valid zone. The constructor throws MasterLoaderError if there is
- /// an error during loading.
+ /// valid zone.
///
+ /// \throws MasterLoaderError if there is an error during loading.
/// \param filename Name of a file containing a collection of RRs in
/// the master file format (which may or may not form a valid zone).
/// \param origin The zone origin.
@@ -56,9 +56,9 @@ public:
///
/// This constructor is similar to the previous one, but instead of
/// taking a filename to load a zone from, it takes an input
- /// stream. The constructor throws MasterLoaderError if there is an
- /// error during loading.
+ /// stream.
///
+ /// \throws MasterLoaderError if there is an error during loading.
/// \param input_stream The input stream to load from.
/// \param origin The zone origin.
/// \param rrclass The zone class.
@@ -88,7 +88,7 @@ public:
/// RRset(s) matching the \c name, \c rrclass and \c rrtype are
/// removed from the collection.
///
- /// \returns \c true if a matching RRset was deleted, \c false if no
+ /// \return \c true if a matching RRset was deleted, \c false if no
/// such RRset exists.
bool removeRRset(const isc::dns::Name& name,
const isc::dns::RRClass& rrclass,
@@ -103,7 +103,7 @@ public:
/// \param name The name of the RRset to search for.
/// \param rrclass The class of the RRset to search for.
/// \param rrtype The type of the RRset to search for.
- /// \returns The RRset if found, \c NULL otherwise.
+ /// \return The RRset if found, \c NULL otherwise.
virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
const isc::dns::RRClass& rrclass,
const isc::dns::RRType& rrtype) const;
@@ -142,7 +142,7 @@ protected:
virtual IterPtr getNext() {
CollectionMap::iterator it = iter_;
- it++;
+ ++it;
return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
}
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
index 38dc76c..27e46d1 100644
--- a/src/lib/dns/rrset_collection_base.h
+++ b/src/lib/dns/rrset_collection_base.h
@@ -47,7 +47,7 @@ public:
/// \param name The name of the RRset to search for.
/// \param rrtype The type of the RRset to search for.
/// \param rrclass The class of the RRset to search for.
- /// \returns The RRset if found, \c NULL otherwise.
+ /// \return The RRset if found, \c NULL otherwise.
virtual isc::dns::ConstRRsetPtr find
(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
const isc::dns::RRType& rrtype)
@@ -88,7 +88,7 @@ protected:
/// method must return \c false.
///
/// \param other The other iterator to compare against.
- /// \returns \c true if equal, \c false otherwise.
+ /// \return \c true if equal, \c false otherwise.
virtual bool equals(Iter& other) = 0;
};
diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc
index 9d23622..44bc979 100644
--- a/src/lib/dns/tests/rdata_char_string_unittest.cc
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -25,7 +25,8 @@
using namespace isc::dns;
using namespace isc::dns::rdata;
using isc::dns::rdata::generic::detail::CharString;
-using isc::dns::rdata::generic::detail::strToCharString;
+using isc::dns::rdata::generic::detail::stringToCharString;
+using isc::dns::rdata::generic::detail::charStringToString;
using isc::util::unittests::matchWireData;
namespace {
@@ -60,19 +61,19 @@ createStringRegion(const std::string& str) {
TEST_F(CharStringTest, normalConversion) {
uint8_t tmp[3]; // placeholder for expected sequence
- strToCharString(str_region, chstr);
+ stringToCharString(str_region, chstr);
matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
// Empty string
chstr.clear();
- strToCharString(createStringRegion(""), chstr);
+ stringToCharString(createStringRegion(""), chstr);
tmp[0] = 0;
matchWireData(tmp, 1, &chstr[0], chstr.size());
// Possible largest char string
chstr.clear();
std::string long_str(255, 'x');
- strToCharString(createStringRegion(long_str), chstr);
+ stringToCharString(createStringRegion(long_str), chstr);
std::vector<uint8_t> expected;
expected.push_back(255); // len of char string
expected.insert(expected.end(), long_str.begin(), long_str.end());
@@ -83,32 +84,32 @@ TEST_F(CharStringTest, normalConversion) {
chstr.clear();
long_str.at(254) = '\\'; // replace the last 'x' with '\'
long_str.append("120"); // 'x' = 120
- strToCharString(createStringRegion(long_str), chstr);
+ stringToCharString(createStringRegion(long_str), chstr);
matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
// Escaped '\'
chstr.clear();
tmp[0] = 1;
tmp[1] = '\\';
- strToCharString(createStringRegion("\\\\"), chstr);
+ stringToCharString(createStringRegion("\\\\"), chstr);
matchWireData(tmp, 2, &chstr[0], chstr.size());
// Boundary values for \DDD
chstr.clear();
tmp[0] = 1;
tmp[1] = 0;
- strToCharString(createStringRegion("\\000"), chstr);
+ stringToCharString(createStringRegion("\\000"), chstr);
matchWireData(tmp, 2, &chstr[0], chstr.size());
chstr.clear();
- strToCharString(createStringRegion("\\255"), chstr);
+ stringToCharString(createStringRegion("\\255"), chstr);
tmp[0] = 1;
tmp[1] = 255;
matchWireData(tmp, 2, &chstr[0], chstr.size());
// Another digit follows DDD; it shouldn't cause confusion
chstr.clear();
- strToCharString(createStringRegion("\\2550"), chstr);
+ stringToCharString(createStringRegion("\\2550"), chstr);
tmp[0] = 2; // string len is now 2
tmp[2] = '0';
matchWireData(tmp, 3, &chstr[0], chstr.size());
@@ -116,13 +117,13 @@ TEST_F(CharStringTest, normalConversion) {
TEST_F(CharStringTest, badConversion) {
// string cannot exceed 255 bytes
- EXPECT_THROW(strToCharString(createStringRegion(std::string(256, 'a')),
- chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion(std::string(256, 'a')),
+ chstr),
CharStringTooLong);
// input string ending with (non escaped) '\'
chstr.clear();
- EXPECT_THROW(strToCharString(createStringRegion("foo\\"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("foo\\"), chstr),
InvalidRdataText);
}
@@ -130,18 +131,44 @@ TEST_F(CharStringTest, badDDD) {
// Check various type of bad form of \DDD
// Not a number
- EXPECT_THROW(strToCharString(createStringRegion("\\1a2"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\1a2"), chstr),
InvalidRdataText);
- EXPECT_THROW(strToCharString(createStringRegion("\\12a"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\12a"), chstr),
InvalidRdataText);
// Not in the range of uint8_t
- EXPECT_THROW(strToCharString(createStringRegion("\\256"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\256"), chstr),
InvalidRdataText);
// Short buffer
- EXPECT_THROW(strToCharString(createStringRegion("\\42"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\42"), chstr),
InvalidRdataText);
}
+const struct TestData {
+ const char *data;
+ const char *expected;
+} conversion_data[] = {
+ {"Test\"Test", "Test\\\"Test"},
+ {"Test;Test", "Test\\;Test"},
+ {"Test\\Test", "Test\\\\Test"},
+ {"Test\x1fTest", "Test\\031Test"},
+ {"Test ~ Test", "Test ~ Test"},
+ {"Test\x7fTest", "Test\\127Test"},
+ {NULL, NULL}
+};
+
+TEST_F(CharStringTest, charStringToString) {
+ for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+ uint8_t idata[32];
+ size_t length = std::strlen(cur->data);
+ // length (1 byte) + string (length bytes)
+ assert(sizeof(idata) > length);
+ idata[0] = static_cast<uint8_t>(length);
+ std::memcpy(idata + 1, cur->data, length);
+ const CharString test_data(idata, idata + length + 1);
+ EXPECT_EQ(cur->expected, charStringToString(test_data));
+ }
+}
+
} // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index d045875..ef9bdfe 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -334,6 +334,24 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
+ EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText());
+ EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText());
+
+ // Check escape behavior
+ const TypeParam double_quotes("Test-String\"Test-String\"");
+ EXPECT_EQ("\"Test-String\\\"Test-String\\\"\"", double_quotes.toText());
+ const TypeParam semicolon("Test-String\\;Test-String");
+ EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
+ const TypeParam backslash("Test-String\\\\Test-String");
+ EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText());
+ const TypeParam before_x20("Test-String\\031Test-String");
+ EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText());
+ const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\"");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText());
+ const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText());
+ const TypeParam after_x7e("Test-String\\127Test-String");
+ EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText());
}
TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
diff --git a/src/lib/dns/tests/rrset_collection_unittest.cc b/src/lib/dns/tests/rrset_collection_unittest.cc
index 183ec51..e17e9e7 100644
--- a/src/lib/dns/tests/rrset_collection_unittest.cc
+++ b/src/lib/dns/tests/rrset_collection_unittest.cc
@@ -47,12 +47,12 @@ TEST_F(RRsetCollectionTest, istreamConstructor) {
RRsetCollectionBase::Iterator iter = collection.begin();
RRsetCollectionBase::Iterator iter2 = collection2.begin();
while (iter != collection.end()) {
- EXPECT_TRUE(iter2 != collection2.end());
+ ASSERT_TRUE(iter2 != collection2.end());
EXPECT_EQ((*iter).toText(), (*iter2).toText());
++iter;
++iter2;
}
- EXPECT_TRUE(iter2 == collection2.end());
+ ASSERT_TRUE(iter2 == collection2.end());
}
template <typename T, typename TP>
@@ -125,19 +125,16 @@ doAddAndRemove(RRsetCollection& collection, const RRClass& rrclass) {
}, isc::InvalidParameter);
// Remove foo.example.org/A, which should pass
- bool exists = collection.removeRRset(Name("foo.example.org"),
- rrclass, RRType::A());
- EXPECT_TRUE(exists);
-
+ EXPECT_TRUE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
// foo.example.org/A should not exist now
rrset_found = collection.find(Name("foo.example.org"), rrclass,
RRType::A());
EXPECT_FALSE(rrset_found);
// Removing foo.example.org/A should fail now
- exists = collection.removeRRset(Name("foo.example.org"),
- rrclass, RRType::A());
- EXPECT_FALSE(exists);
+ EXPECT_FALSE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
}
TEST_F(RRsetCollectionTest, addAndRemove) {
@@ -204,7 +201,7 @@ protected:
virtual IterPtr getNext() {
MyCollection::iterator it = iter_;
- it++;
+ ++it;
return (RRsetCollectionBase::IterPtr(new MyIter(it)));
}
diff --git a/src/lib/dns/tests/zone_checker_unittest.cc b/src/lib/dns/tests/zone_checker_unittest.cc
index 9a10f34..dbe204d 100644
--- a/src/lib/dns/tests/zone_checker_unittest.cc
+++ b/src/lib/dns/tests/zone_checker_unittest.cc
@@ -64,6 +64,8 @@ protected:
}
public:
+ // This one is passed to boost::bind. Some compilers seem to require
+ // it be public.
void callback(const std::string& reason, bool is_error) {
if (is_error) {
errors_.push_back(reason);
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 1f7006c..febeb10 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -177,8 +177,14 @@ class BaseComponent:
self._start_internal()
except Exception as e:
logger.error(BIND10_COMPONENT_START_EXCEPTION, self.name(), e)
- self.failed(None)
- raise
+ try:
+ self.failed(None)
+ finally:
+ # Even failed() can fail if this happens during initial startup
+ # time. In that case we'd rather propagate the original reason
+ # for the failure than the fact that failed() failed. So we
+ # always re-raise the original exception.
+ raise e
def stop(self):
"""
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index 688ccf5..dcd9b64 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -17,11 +17,7 @@ from isc.bind10.component import Component, BaseComponent
import isc.bind10.sockcreator
from bind10_config import LIBEXECPATH
import os
-import posix
import isc.log
-from isc.log_messages.bind10_messages import *
-
-logger = isc.log.Logger("boss")
class SockCreator(BaseComponent):
"""
@@ -36,8 +32,6 @@ class SockCreator(BaseComponent):
def __init__(self, process, boss, kind, address=None, params=None):
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'
@@ -46,12 +40,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)
+
+ # We are now ready for switching user.
+ self._boss.change_user()
def _stop_internal(self):
self.__creator.terminate()
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index 18efea7..8603201 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -104,10 +104,10 @@ 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
+ self.__change_user_called = False
+
+ def change_user(self):
+ self.__change_user_called = True # just record the fact it's called
def __start(self):
"""
@@ -624,12 +624,6 @@ 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
-
class FakeCreator:
def pid(self):
return 42
@@ -655,35 +649,19 @@ 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
# Just ignore the creator call
isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
lambda path: self.FakeCreator()
component.start()
- # No gid/uid set in boss, nothing called.
- self.assertIsNone(self.__gid_set)
- self.assertIsNone(self.__uid_set)
+ self.assertTrue(self.__change_user_called)
# 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
class TestComponent(BaseComponent):
"""
diff --git a/src/lib/python/isc/datasrc/tests/.gitignore b/src/lib/python/isc/datasrc/tests/.gitignore
index 58ea8cd..0d94a86 100644
--- a/src/lib/python/isc/datasrc/tests/.gitignore
+++ b/src/lib/python/isc/datasrc/tests/.gitignore
@@ -1 +1,2 @@
/*.sqlite3.copied
+/zoneloadertest.sqlite3
diff --git a/src/lib/testutils/dnsmessage_test.h b/src/lib/testutils/dnsmessage_test.h
index 262d177..6de01ec 100644
--- a/src/lib/testutils/dnsmessage_test.h
+++ b/src/lib/testutils/dnsmessage_test.h
@@ -208,10 +208,9 @@ pullSigs(std::vector<isc::dns::ConstRRsetPtr>& rrsets,
{
for (ITERATOR it = begin; it != end; ++it) {
rrsets.push_back(*it);
- text += (*it)->toText();
+ text += (*it)->toText(); // this will include RRSIG, if attached.
if ((*it)->getRRsig()) {
rrsets.push_back((*it)->getRRsig());
- text += (*it)->getRRsig()->toText();
}
}
}
diff --git a/tests/tools/perfdhcp/tests/.gitignore b/tests/tools/perfdhcp/tests/.gitignore
index d6d1ec8..5697f95 100644
--- a/tests/tools/perfdhcp/tests/.gitignore
+++ b/tests/tools/perfdhcp/tests/.gitignore
@@ -1 +1,6 @@
/run_unittests
+/test1.hex
+/test2.hex
+/test3.hex
+/test4.hex
+/test5.hex
More information about the bind10-changes
mailing list