BIND 10 trac826, updated. 748f816045f31e5f629f54c0d7f05403d63a5059 update src/lib/exceptions
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Jun 26 15:24:55 UTC 2012
The branch, trac826 has been updated
via 748f816045f31e5f629f54c0d7f05403d63a5059 (commit)
via 95b49e133232fa1e5d7b836bcb1d8159b2f96ffc (commit)
via 66945209108d70e300e4aea84e679c2d474ff38d (commit)
from 1633faa4eefc1cd69e3547eeccd2f0ca4b4248a0 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 748f816045f31e5f629f54c0d7f05403d63a5059
Author: Francis Dupont <fdupont at isc.org>
Date: Tue Jun 26 17:24:36 2012 +0200
update src/lib/exceptions
commit 95b49e133232fa1e5d7b836bcb1d8159b2f96ffc
Author: Francis Dupont <fdupont at isc.org>
Date: Tue Jun 26 17:07:25 2012 +0200
update tests (but not perfdhcp)
commit 66945209108d70e300e4aea84e679c2d474ff38d
Author: Francis Dupont <fdupont at isc.org>
Date: Tue Jun 26 13:28:30 2012 +0200
update tools
-----------------------------------------------------------------------
Summary of changes:
src/Makefile.am | 5 +
src/cppcheck-suppress.lst | 16 +-
src/lib/Makefile.am | 2 +-
src/lib/exceptions/Makefile.am | 2 +-
src/lib/exceptions/exceptions.h | 81 +
src/lib/exceptions/tests/.gitignore | 1 +
src/lib/exceptions/tests/Makefile.am | 3 +
src/valgrind-suppressions | 11 +
src/valgrind-suppressions.revisit | 17 +
tests/lettuce/.gitignore | 2 +
tests/lettuce/README | 123 +
tests/lettuce/README.tutorial | 157 +
tests/lettuce/configurations/.gitignore | 2 +
.../configurations/DO_NOT_USE_127.0.0.1:47807 | 6 +
tests/lettuce/configurations/bindctl/.gitignore | 1 +
.../configurations/bindctl/bindctl.config.orig | 22 +
.../configurations/bindctl_commands.config.orig | 34 +
tests/lettuce/configurations/ddns/.gitignore | 2 +
tests/lettuce/configurations/ddns/ddns.config.orig | 78 +
.../lettuce/configurations/ddns/noddns.config.orig | 43 +
tests/lettuce/configurations/default.config | 16 +
.../lettuce/configurations/example.org.config.orig | 23 +
.../configurations/example.org.inmem.config | 8 +
tests/lettuce/configurations/example2.org.config | 24 +
.../inmemory_over_sqlite3/secondary.conf | 32 +
.../configurations/ixfr-out/testset1-config.db | 11 +
.../configurations/multi_instance/.gitignore | 1 +
.../multi_instance/multi_auth.config.orig | 24 +
tests/lettuce/configurations/no_db_file.config | 24 +
.../lettuce/configurations/nsec3/nsec3_auth.config | 1 +
.../nsec3/rfc5155-example.zone.signed | 72 +
tests/lettuce/configurations/resolver/.gitignore | 1 +
.../resolver/resolver_basic.config.orig | 1 +
.../lettuce/configurations/xfrin/inmem_slave.conf | 34 +
.../configurations/xfrin/retransfer_master.conf | 30 +
.../configurations/xfrin/retransfer_slave.conf | 25 +
tests/lettuce/data/.gitignore | 2 +
tests/lettuce/data/commands/bad_command | 9 +
tests/lettuce/data/commands/directives | 19 +
NEWS => tests/lettuce/data/commands/empty | 0
tests/lettuce/data/commands/nested | 2 +
tests/lettuce/data/commands/nested1 | 2 +
tests/lettuce/data/ddns/.gitignore | 1 +
tests/lettuce/data/ddns/example.org.sqlite3.orig | Bin 0 -> 15360 bytes
tests/lettuce/data/empty_db.sqlite3 | Bin 0 -> 14336 bytes
tests/lettuce/data/example.org | 13 +
tests/lettuce/data/example.org.sqlite3 | Bin 0 -> 15360 bytes
tests/lettuce/data/inmem-xfrin | 7 +
tests/lettuce/data/inmem-xfrin.sqlite3.orig | Bin 0 -> 13312 bytes
tests/lettuce/data/ixfr-out/.gitignore | 1 +
tests/lettuce/data/ixfr-out/zones.sqlite3 | Bin 0 -> 468992 bytes
tests/lettuce/features/bindctl_commands.feature | 156 +
tests/lettuce/features/ddns_system.feature | 144 +
tests/lettuce/features/default.feature | 21 +
tests/lettuce/features/example.feature | 193 ++
.../lettuce/features/inmemory_over_sqlite3.feature | 43 +
tests/lettuce/features/ixfr_out_bind10.feature | 209 ++
tests/lettuce/features/multi_instance.feature | 59 +
tests/lettuce/features/nsec3_auth.feature | 466 +++
tests/lettuce/features/queries.feature | 146 +
tests/lettuce/features/resolver_basic.feature | 36 +
tests/lettuce/features/terrain/.gitignore | 1 +
tests/lettuce/features/terrain/bind10_control.py | 365 ++
tests/lettuce/features/terrain/nsupdate.py | 168 +
tests/lettuce/features/terrain/querying.py | 338 ++
tests/lettuce/features/terrain/steps.py | 91 +
tests/lettuce/features/terrain/terrain.py | 406 +++
tests/lettuce/features/terrain/transfer.py | 140 +
tests/lettuce/features/xfrin_bind10.feature | 39 +
tests/lettuce/run_lettuce.sh | 25 +
tests/lettuce/setup_intree_bind10.sh.in | 48 +
tests/lettuce/setup_intree_bind10.sh.win32 | 48 +
tests/system/.gitignore | 2 +
tests/system/bindctl/nsx1/.gitignore | 3 +
.../system/bindctl/nsx1/b10-config.db.template.in | 3 -
tests/system/bindctl/tests.sh | 111 +-
tests/system/glue/.gitignore | 1 +
tests/system/glue/nsx1/.gitignore | 3 +
tests/system/glue/nsx1/b10-config.db.in | 11 +-
tests/system/ixfr/.gitignore | 8 +
tests/system/ixfr/b10-config.db.in | 10 +
tests/system/ixfr/in-1/.gitignore | 1 +
tests/system/ixfr/in-2/.gitignore | 1 +
tests/system/ixfr/in-2/ns1/.gitignore | 1 +
tests/system/ixfr/in-2/nsx2/.gitignore | 1 +
tests/system/ixfr/in-2/tests.sh | 2 +-
tests/system/ixfr/in-3/.gitignore | 1 +
tests/system/ixfr/in-3/tests.sh | 21 +-
tests/system/ixfr/in-4/.gitignore | 1 +
tests/system/ixfr/named_noixfr.conf | 1 +
tests/system/start.pl | 7 +-
tests/tools/Makefile.am | 6 +-
tests/tools/badpacket/.gitignore | 1 +
tests/tools/badpacket/tests/.gitignore | 1 +
tests/tools/badpacket/tests/Makefile.am | 3 +
tests/tools/perfdhcp/.gitignore | 1 +
tests/tools/perfdhcp/Makefile.am | 40 +
tests/tools/perfdhcp/command_options.cc | 699 ++++
tests/tools/perfdhcp/command_options.h | 412 +++
tests/tools/perfdhcp/localized_option.h | 123 +
tests/tools/perfdhcp/perf_pkt4.cc | 62 +
tests/tools/perfdhcp/perf_pkt4.h | 113 +
tests/tools/perfdhcp/perf_pkt6.cc | 64 +
tests/tools/perfdhcp/perf_pkt6.h | 113 +
tests/tools/perfdhcp/perfdhcp.c | 3559 ++++++++++++++++++++
tests/tools/perfdhcp/pkt_transform.cc | 222 ++
tests/tools/perfdhcp/pkt_transform.h | 139 +
tests/tools/perfdhcp/tests/.gitignore | 1 +
tests/tools/perfdhcp/tests/Makefile.am | 46 +
.../perfdhcp/tests/command_options_unittest.cc | 454 +++
.../perfdhcp/tests/localized_option_unittest.cc | 48 +
tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc | 384 +++
tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc | 327 ++
.../{badpacket => perfdhcp}/tests/run_unittests.cc | 0
tools/git-obsolete-branch.py | 198 ++
tools/reorder_message_file.py | 196 ++
tools/system_messages.py | 2 +-
117 files changed, 11498 insertions(+), 38 deletions(-)
create mode 100644 src/lib/exceptions/tests/.gitignore
create mode 100644 src/valgrind-suppressions
create mode 100644 src/valgrind-suppressions.revisit
create mode 100644 tests/lettuce/.gitignore
create mode 100644 tests/lettuce/README
create mode 100644 tests/lettuce/README.tutorial
create mode 100644 tests/lettuce/configurations/.gitignore
create mode 100644 tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:47807
create mode 100644 tests/lettuce/configurations/bindctl/.gitignore
create mode 100644 tests/lettuce/configurations/bindctl/bindctl.config.orig
create mode 100644 tests/lettuce/configurations/bindctl_commands.config.orig
create mode 100644 tests/lettuce/configurations/ddns/.gitignore
create mode 100644 tests/lettuce/configurations/ddns/ddns.config.orig
create mode 100644 tests/lettuce/configurations/ddns/noddns.config.orig
create mode 100644 tests/lettuce/configurations/default.config
create mode 100644 tests/lettuce/configurations/example.org.config.orig
create mode 100644 tests/lettuce/configurations/example.org.inmem.config
create mode 100644 tests/lettuce/configurations/example2.org.config
create mode 100644 tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
create mode 100644 tests/lettuce/configurations/ixfr-out/testset1-config.db
create mode 100644 tests/lettuce/configurations/multi_instance/.gitignore
create mode 100644 tests/lettuce/configurations/multi_instance/multi_auth.config.orig
create mode 100644 tests/lettuce/configurations/no_db_file.config
create mode 100644 tests/lettuce/configurations/nsec3/nsec3_auth.config
create mode 100644 tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed
create mode 100644 tests/lettuce/configurations/resolver/.gitignore
create mode 100644 tests/lettuce/configurations/resolver/resolver_basic.config.orig
create mode 100644 tests/lettuce/configurations/xfrin/inmem_slave.conf
create mode 100644 tests/lettuce/configurations/xfrin/retransfer_master.conf
create mode 100644 tests/lettuce/configurations/xfrin/retransfer_slave.conf
create mode 100644 tests/lettuce/data/.gitignore
create mode 100644 tests/lettuce/data/commands/bad_command
create mode 100644 tests/lettuce/data/commands/directives
copy NEWS => tests/lettuce/data/commands/empty (100%)
create mode 100644 tests/lettuce/data/commands/nested
create mode 100644 tests/lettuce/data/commands/nested1
create mode 100644 tests/lettuce/data/ddns/.gitignore
create mode 100644 tests/lettuce/data/ddns/example.org.sqlite3.orig
create mode 100644 tests/lettuce/data/empty_db.sqlite3
create mode 100644 tests/lettuce/data/example.org
create mode 100644 tests/lettuce/data/example.org.sqlite3
create mode 100644 tests/lettuce/data/inmem-xfrin
create mode 100644 tests/lettuce/data/inmem-xfrin.sqlite3.orig
create mode 100644 tests/lettuce/data/ixfr-out/.gitignore
create mode 100644 tests/lettuce/data/ixfr-out/zones.sqlite3
create mode 100644 tests/lettuce/features/bindctl_commands.feature
create mode 100644 tests/lettuce/features/ddns_system.feature
create mode 100644 tests/lettuce/features/default.feature
create mode 100644 tests/lettuce/features/example.feature
create mode 100644 tests/lettuce/features/inmemory_over_sqlite3.feature
create mode 100644 tests/lettuce/features/ixfr_out_bind10.feature
create mode 100644 tests/lettuce/features/multi_instance.feature
create mode 100644 tests/lettuce/features/nsec3_auth.feature
create mode 100644 tests/lettuce/features/queries.feature
create mode 100644 tests/lettuce/features/resolver_basic.feature
create mode 100644 tests/lettuce/features/terrain/.gitignore
create mode 100644 tests/lettuce/features/terrain/bind10_control.py
create mode 100644 tests/lettuce/features/terrain/nsupdate.py
create mode 100644 tests/lettuce/features/terrain/querying.py
create mode 100644 tests/lettuce/features/terrain/steps.py
create mode 100644 tests/lettuce/features/terrain/terrain.py
create mode 100644 tests/lettuce/features/terrain/transfer.py
create mode 100644 tests/lettuce/features/xfrin_bind10.feature
create mode 100755 tests/lettuce/run_lettuce.sh
create mode 100644 tests/lettuce/setup_intree_bind10.sh.in
create mode 100644 tests/lettuce/setup_intree_bind10.sh.win32
create mode 100644 tests/system/.gitignore
create mode 100644 tests/system/bindctl/nsx1/.gitignore
create mode 100644 tests/system/glue/.gitignore
create mode 100644 tests/system/glue/nsx1/.gitignore
create mode 100644 tests/system/ixfr/.gitignore
create mode 100644 tests/system/ixfr/in-1/.gitignore
create mode 100644 tests/system/ixfr/in-2/.gitignore
create mode 100644 tests/system/ixfr/in-2/ns1/.gitignore
create mode 100644 tests/system/ixfr/in-2/nsx2/.gitignore
create mode 100644 tests/system/ixfr/in-3/.gitignore
create mode 100644 tests/system/ixfr/in-4/.gitignore
create mode 100644 tests/tools/badpacket/.gitignore
create mode 100644 tests/tools/badpacket/tests/.gitignore
create mode 100644 tests/tools/perfdhcp/.gitignore
create mode 100644 tests/tools/perfdhcp/Makefile.am
create mode 100644 tests/tools/perfdhcp/command_options.cc
create mode 100644 tests/tools/perfdhcp/command_options.h
create mode 100644 tests/tools/perfdhcp/localized_option.h
create mode 100644 tests/tools/perfdhcp/perf_pkt4.cc
create mode 100644 tests/tools/perfdhcp/perf_pkt4.h
create mode 100644 tests/tools/perfdhcp/perf_pkt6.cc
create mode 100644 tests/tools/perfdhcp/perf_pkt6.h
create mode 100644 tests/tools/perfdhcp/perfdhcp.c
create mode 100644 tests/tools/perfdhcp/pkt_transform.cc
create mode 100644 tests/tools/perfdhcp/pkt_transform.h
create mode 100644 tests/tools/perfdhcp/tests/.gitignore
create mode 100644 tests/tools/perfdhcp/tests/Makefile.am
create mode 100644 tests/tools/perfdhcp/tests/command_options_unittest.cc
create mode 100644 tests/tools/perfdhcp/tests/localized_option_unittest.cc
create mode 100644 tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc
create mode 100644 tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
copy tests/tools/{badpacket => perfdhcp}/tests/run_unittests.cc (100%)
create mode 100755 tools/git-obsolete-branch.py
create mode 100644 tools/reorder_message_file.py
-----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index ca4a702..395553c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1 +1,6 @@
SUBDIRS = lib bin
+
+EXTRA_DIST = \
+ cppcheck-suppress.lst \
+ valgrind-suppressions \
+ valgrind-suppressions.revisit
diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
index 1020ffe..ff4a79a 100644
--- a/src/cppcheck-suppress.lst
+++ b/src/cppcheck-suppress.lst
@@ -2,10 +2,12 @@
// the following two will suppress, depending on the cppcheck version
debug
missingInclude
-// This is a template, and should be excluded from the check
-unreadVariable:src/lib/dns/rdata/template.cc:61
-// Intentional self assignment tests. Suppress warning about them.
-selfAssignment:src/lib/dns/tests/name_unittest.cc:293
-selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
-selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:137
-selfAssignment:src/lib/dns/tests/rdata_txt_like_unittest.cc:222
+
+// Please don't add any suppressions here. We now use inline
+// suppressions (in the .cc files) so that we don't have to
+// maintain line numbers in this file.
+//
+// See the cppcheck manual for syntax. It is something like:
+//
+// // cppcheck-suppress duplicateExpression
+// EXPECT_FALSE(small_name < small_name);
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index a569ea7..9ebd541 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,3 +1,3 @@
SUBDIRS = exceptions util log cryptolink dns cc config acl xfr bench \
asiolink asiodns nsas cache resolve testutils datasrc \
- server_common python dhcp
+ server_common python dhcp statistics
diff --git a/src/lib/exceptions/Makefile.am b/src/lib/exceptions/Makefile.am
index eff83b2..1d0ce2d 100644
--- a/src/lib/exceptions/Makefile.am
+++ b/src/lib/exceptions/Makefile.am
@@ -8,5 +8,5 @@ libexceptions_la_SOURCES = exceptions.h exceptions.cc
CLEANFILES = *.gcno *.gcda
-libexceptions_includedir = $(includedir)/exceptions
+libexceptions_includedir = $(includedir)/$(PACKAGE_NAME)/exceptions
libexceptions_include_HEADERS = exceptions.h
diff --git a/src/lib/exceptions/exceptions.h b/src/lib/exceptions/exceptions.h
index 7819052..8d45796 100644
--- a/src/lib/exceptions/exceptions.h
+++ b/src/lib/exceptions/exceptions.h
@@ -126,6 +126,17 @@ public:
isc::Exception(file, line, what) {}
};
+/// \brief A generic exception that is thrown if a function is called
+/// in a prohibited way.
+///
+/// For example, this can happen if a class method is called when the object's
+/// state does not allow that particular method.
+class InvalidOperation : public Exception {
+public:
+ InvalidOperation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
///
/// \brief A generic exception that is thrown when an unexpected
/// error condition occurs.
@@ -210,6 +221,76 @@ public:
} while (1)
#endif
+///
+/// Similar as isc_throw, but allows the exception to have two additional
+/// parameters (the stream/text goes first)
+#ifdef _MSC_VER
+#define isc_throw_2(type, stream, param1, param2) \
+ __pragma(warning(push)) \
+ __pragma(warning(disable: 4127)) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2); \
+ } while (1) \
+ __pragma(warning(pop))
+#else
+#define isc_throw_2(type, stream, param1, param2) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2); \
+ } while (1)
+#endif
+
+///
+/// Similar as isc_throw, but allows the exception to have three additional
+/// parameters (the stream/text goes first)
+#ifdef _MSC_VER
+#define isc_throw_3(type, stream, param1, param2, param3) \
+ __pragma(warning(push)) \
+ __pragma(warning(disable: 4127)) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3); \
+ } while (1) \
+ __pragma(warning(pop))
+#else
+#define isc_throw_3(type, stream, param1, param2, param3) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3); \
+ } while (1)
+#endif
+
+///
+/// Similar as isc_throw, but allows the exception to have four additional
+/// parameters (the stream/text goes first)
+#ifdef _MSC_VER
+#define isc_throw_4(type, stream, param1, param2, param3, param4) \
+ __pragma(warning(push)) \
+ __pragma(warning(disable: 4127)) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3, param4); \
+ } while (1) \
+ __pragma(warning(pop))
+#else
+#define isc_throw_4(type, stream, param1, param2, param3, param4) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3, param4); \
+ } while (1)
+#endif
+
}
#endif // __EXCEPTIONS_H
diff --git a/src/lib/exceptions/tests/.gitignore b/src/lib/exceptions/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/exceptions/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/exceptions/tests/Makefile.am b/src/lib/exceptions/tests/Makefile.am
index 2444b02..1e31353 100644
--- a/src/lib/exceptions/tests/Makefile.am
+++ b/src/lib/exceptions/tests/Makefile.am
@@ -9,6 +9,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/src/valgrind-suppressions b/src/valgrind-suppressions
new file mode 100644
index 0000000..06257e5
--- /dev/null
+++ b/src/valgrind-suppressions
@@ -0,0 +1,11 @@
+# Valgrind suppressions file. Place permanent suppressions that we never
+# want to reconsider again into this file. For temporary suppressions
+# that we want to revisit in the future, use
+# valgrind-suppressions.revisit.
+#
+# Don't add any "obj:" lines in suppressions as these are likely
+# site-specific. Use "..." instead to match these. Look at the other
+# suppressions as examples.
+#
+# In case you want to make sense of the following symbols, demangle them
+# with a command like: c++filt < valgrind-suppressions
diff --git a/src/valgrind-suppressions.revisit b/src/valgrind-suppressions.revisit
new file mode 100644
index 0000000..8b4a8c7
--- /dev/null
+++ b/src/valgrind-suppressions.revisit
@@ -0,0 +1,17 @@
+# Place temporary suppressions that we want to revisit in the future
+# into this file. For permanent suppressions that we don't want to look
+# at again, use valgrind-suppressions.
+#
+# Don't add any "obj:" lines in suppressions as these are likely
+# site-specific. Use "..." instead to match these. Look at the other
+# suppressions as examples.
+#
+# In case you want to make sense of the following symbols, demangle them
+# with a command like: c++filt < valgrind-suppressions.revisit
+
+############################################################################
+#### beginning of suppressions for existing issues that we want to fix. ####
+
+
+####### end of suppressions for existing issues that we want to fix. #######
+############################################################################
diff --git a/tests/lettuce/.gitignore b/tests/lettuce/.gitignore
new file mode 100644
index 0000000..f41154c
--- /dev/null
+++ b/tests/lettuce/.gitignore
@@ -0,0 +1,2 @@
+/output/
+/setup_intree_bind10.sh
diff --git a/tests/lettuce/README b/tests/lettuce/README
new file mode 100644
index 0000000..94bf82b
--- /dev/null
+++ b/tests/lettuce/README
@@ -0,0 +1,123 @@
+BIND10 system testing with Lettuce
+or: to BDD or not to BDD
+
+In this directory, we define a set of behavioral tests for BIND 10. Currently,
+these tests are specific for BIND10, but we are keeping in mind that RFC-related
+tests could be separated, so that we can test other systems as well.
+
+Prerequisites:
+- BIND 10 must be compiled or installed
+- dig
+- lettuce (http://lettuce.it)
+
+To install lettuce, if you have the python pip installation tool, simply do
+pip install lettuce
+See http://lettuce.it/intro/install.html
+
+Most systems have the pip tool in a separate package; on Debian-based systems
+it is called python-pip. On FreeBSD the port is devel/py-pip.
+
+Running the tests
+-----------------
+
+At this moment, we have a fixed port for local tests in our setups, port 47806.
+This port must be free. (TODO: can we make this run-time discovered?).
+Port 47805 is used for cmdctl, and must also be available.
+(note, we will need to extend this to a range, or if possible, we will need to
+do some on-the-fly available port finding)
+
+You can run the lettuce tests with the provided run_lettuce.sh script.
+
+By default it will use the build tree, but you can use an installed version
+of bind10 by passing -I as the first argument of run_lettuce.sh
+
+The tool 'dig' must be in the default search path of your environment. If
+you specified -I, so must the main bind10 program, as well as bindctl.
+
+Due to the default way lettuce prints its output, it is advisable to run it
+in a terminal that is wide than the default. If you see a lot of lines twice
+in different colors, the terminal is not wide enough.
+
+If you just want to run one specific feature test, use
+run_lettuce.sh [-I] features/<feature file>
+
+To run a specific scenario from a feature, use
+run_lettuce.sh [-I] features/<feature file> -s <scenario number>
+
+We have set up the tests to assume that lettuce is run from this directory,
+so even if you specify a specific feature file, you should do it from this
+directory.
+
+What to do when a test fails
+----------------------------
+
+First of all, look at the error it printed and see what step it occurred in.
+If written well, the output should explain most of what went wrong.
+
+The stacktrace that is printed is *not* of bind10, but of the testing
+framework; this helps in finding more information about what exactly the test
+tried to achieve when it failed (as well as help debug the tests themselves).
+
+Furthermore, if any scenario fails, the output from long-running processes
+will be stored in the directory output/. The name of the files will be
+<Feature name>-<Scenario name>-<Process name>.stdout and
+<Feature name>-<Scenario name>-<Process name>.stderr
+Where spaces and other non-standard characters are replaced by an underscore.
+The process name is either the standard name for said process (e.g. 'bind10'),
+or the name given to it by the test ('when i run bind10 as <name>').
+
+These files *will* be overwritten or deleted if the same scenarios are run
+again, so if you want to inspect them after a failed test, either do so
+immediately or move the files.
+
+Adding and extending tests
+--------------------------
+
+If you want to add tests, it is advisable to first go through the examples to
+see what is possible, and read the documentation on http://www.lettuce.it
+
+There is also a README.tutorial file here.
+
+We have a couple of conventions to keep things manageable.
+
+Configuration files go into the configurations/ directory.
+Data files go into the data/ directory.
+Step definition go into the features/terrain/ directory (the name terrain is
+chosen for the same reason Lettuce chose terrain.py, this is the place the
+tests 'live' in).
+Feature definitions go directly into the features/ directory.
+
+These directories are currently not divided further; we may want to consider
+this as the set grows. Due to a (current?) limitation of Lettuce, for
+feature files this is currently not possible; the python files containing
+steps and terrain must be below or at the same level of the feature files.
+
+Long-running processes should be started through the world.RunningProcesses
+instance. If you want to add a process (e.g. bind9), create start, stop and
+control steps in terrain/<base_name>_control.py, and let it use the
+RunningProcesses API (defined in terrain.py). See bind10_control.py for an
+example.
+
+For sending queries and checking the results, steps have been defined in
+terrain/querying.py. These use dig and store the results split up into text
+strings. This is intentionally not parsed through our own library (as that way
+we might run into a 'symmetric bug'). If you need something more advanced from
+query results, define it here.
+
+Some very general steps are defined in terrain/steps.py.
+Initialization code, cleanup code, and helper classes are defined in
+terrain/terrain.py.
+
+To find the right steps, case insensitive matching is used. Parameters taken
+from the steps are case-sensitive though. So a step defined as
+'do foo with value (bar)' will be matched when using
+'Do Foo with value xyz', but xyz will be taken as given.
+
+If you need to add steps that are very particular to one test, create a new
+file with a name relevant for that test in terrain. We may want to consider
+creating a specific subdirectory for these, but at this moment it is unclear
+whether we need to.
+
+We should try to keep steps as general as possible, while not making them to
+complex and error-prone.
+
diff --git a/tests/lettuce/README.tutorial b/tests/lettuce/README.tutorial
new file mode 100644
index 0000000..7d1c801
--- /dev/null
+++ b/tests/lettuce/README.tutorial
@@ -0,0 +1,157 @@
+Quick tutorial and overview
+---------------------------
+
+Lettuce is a framework for doing Behaviour Driven Development (BDD).
+
+The idea behind BDD is that you first write down your requirements in
+the form of scenarios, then implement their behaviour.
+
+We do not plan on doing full BDD, but such a system should also help
+us make system tests. And, hopefully, being able to better identify
+what exactly is going wrong when a test fails.
+
+Lettuce is a python implementation of the Cucumber framework, which is
+a ruby system. So far we chose lettuce because we already need python
+anyway, so chances are higher that any system we want to run it on
+supports it. It only supports a subset of cucumber, but more cucumber
+features are planned. As I do not know much details of cucumber, I
+can't really say what is there and what is not.
+
+A slight letdown is that the current version does not support python 3.
+However, as long as the tool-calling glue is python2, this should not
+cause any problems, since these aren't unit tests; We do not plan to use
+our libraries directly, but only through the runnable scripts and
+executables.
+
+-----
+
+Features, Scenarios, Steps.
+
+Lettuce makes a distinction between features, scenarios, and steps.
+
+Features are general, well, features. Each 'feature' has its own file
+ending in .feature. A feature file contains a description and a number
+of scenarios. Each scenario tests one or more particular parts of the
+feature. Each scenario consists of a number of steps.
+
+So let's open up a simple one.
+
+-- example.feature
+Feature: showing off BIND 10
+ This is to show BIND 10 running and that it answer queries
+
+ Scenario: Starting bind10
+ # steps go here
+--
+
+I have predefined a number of steps we can use, as we build test we
+will need to expand these, but we will look at them shortly.
+
+This file defines a feature, just under the feature name we can
+provide a description of the feature.
+
+The one scenario we have has no steps, so if we run it we should
+see something like:
+
+-- output
+> ./run_lettuce.sh
+Feature: showing off BIND 10
+ This is to show BIND 10 running and that it answer queries
+
+ Scenario: Starting bind10
+
+1 feature (1 passed)
+1 scenario (1 passed)
+0 step (0 passed)
+--
+
+Let's first add some steps that send queries.
+
+--
+ A query for www.example.com should have rcode REFUSED
+ A query for www.example.org should have rcode NOERROR
+--
+
+Since we didn't start any bind10, dig will time out and the result
+should be an error saying it got no answer. Errors are in the
+form of stack traces (trigger by failed assertions), so we can find
+out easily where in the tests they occurred. Especially when the total
+set of steps gets bigger we might need that.
+
+So let's add a step that starts bind10.
+
+--
+ When I start bind10 with configuration example.org.config
+--
+
+This is not good enough; it will start the process, but setting up
+b10-auth may take a few moments, so we need to add a step to wait for
+it to be started before we continue.
+
+--
+ Then wait for bind10 auth to start
+--
+
+And let's run the tests again.
+
+--
+> ./run_lettuce.sh
+
+Feature: showing off BIND 10
+ This is to show BIND 10 running and that it answer queries
+
+ Scenario: Starting bind10
+ When I start bind10 with configuration example.org.config
+ Then wait for bind10 auth to start
+ A query for www.example.com should have rcode REFUSED
+ A query for www.example.org should have rcode NOERROR
+
+1 feature (1 passed)
+1 scenario (1 passed)
+4 steps (4 passed)
+(finished within 2 seconds)
+--
+
+So take a look at one of those steps, let's pick the first one.
+
+A step is defined through a python decorator, which in essence is a regular
+expression; lettuce searches through all defined steps to find one that
+matches. These are 'partial' matches (unless specified otherwise in the
+regular expression itself), so if the step is defined with "do foo bar", the
+scenario can add words for readability "When I do foo bar".
+
+Each captured group will be passed as an argument to the function we define.
+For bind10, I defined a configuration file, a cmdctl port, and a process
+name. The first two should be self-evident, and the process name is an
+optional name we give it, should we want to address it in the rest of the
+tests. This is most useful if we want to start multiple instances. In the
+next step (the wait for auth to start), I added a 'of <instance>'. So if we
+define the bind10 'as b10_second_instance', we can specify that one here as
+'of b10_second_instance'.
+
+--
+ When I start bind10 with configuration second.config
+ with cmdctl port 12345 as b10_second_instance
+--
+(line wrapped for readability)
+
+But notice how we needed two steps, which we probably always need (but
+not entirely always)? We can also combine steps; for instance:
+
+--
+ at step('have bind10 running(?: with configuration ([\w.]+))?')
+def have_bind10_running(step, config_file):
+ step.given('start bind10 with configuration ' + config_file)
+ step.given('wait for bind10 auth to start')
+--
+
+Now we can replace the two steps with one:
+
+--
+ Given I have bind10 running
+--
+
+That's it for the quick overview. For some more examples, with comments,
+take a look at features/example.feature. You can read more about lettuce and
+its features on http://www.lettuce.it, and if you plan on adding tests and
+scenarios, please consult the last section of the main README first.
diff --git a/tests/lettuce/configurations/.gitignore b/tests/lettuce/configurations/.gitignore
new file mode 100644
index 0000000..69d136f
--- /dev/null
+++ b/tests/lettuce/configurations/.gitignore
@@ -0,0 +1,2 @@
+/bindctl_commands.config
+/example.org.config
diff --git a/tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:47807 b/tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:47807
new file mode 100644
index 0000000..d8f0b5f
--- /dev/null
+++ b/tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:47807
@@ -0,0 +1,6 @@
+Note well:
+
+In some configuration we intentionally use an IPv6 address (::1) with
+port 47807. DO NOT CHANGE THAT; at least do not change it to
+127.0.0.1:47807. See git e3f4b290d17a68db728166cdffcbe93517966e8b
+for why.
diff --git a/tests/lettuce/configurations/bindctl/.gitignore b/tests/lettuce/configurations/bindctl/.gitignore
new file mode 100644
index 0000000..e14ae76
--- /dev/null
+++ b/tests/lettuce/configurations/bindctl/.gitignore
@@ -0,0 +1 @@
+/bindctl.config
diff --git a/tests/lettuce/configurations/bindctl/bindctl.config.orig b/tests/lettuce/configurations/bindctl/bindctl.config.orig
new file mode 100644
index 0000000..bf623c5
--- /dev/null
+++ b/tests/lettuce/configurations/bindctl/bindctl.config.orig
@@ -0,0 +1,22 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/bindctl_commands.config.orig b/tests/lettuce/configurations/bindctl_commands.config.orig
new file mode 100644
index 0000000..d74b96e
--- /dev/null
+++ b/tests/lettuce/configurations/bindctl_commands.config.orig
@@ -0,0 +1,34 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "StatsHttpd": {
+ "listen_on": [ {
+ "port": 47811,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "dispensable", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-stats-httpd": { "address": "StatsHttpd", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/ddns/.gitignore b/tests/lettuce/configurations/ddns/.gitignore
new file mode 100644
index 0000000..f4f3945
--- /dev/null
+++ b/tests/lettuce/configurations/ddns/.gitignore
@@ -0,0 +1,2 @@
+/ddns.config
+/noddns.config
diff --git a/tests/lettuce/configurations/ddns/ddns.config.orig b/tests/lettuce/configurations/ddns/ddns.config.orig
new file mode 100644
index 0000000..80b92f7
--- /dev/null
+++ b/tests/lettuce/configurations/ddns/ddns.config.orig
@@ -0,0 +1,78 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [
+ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ }
+ ]
+ },
+ "Zonemgr": {
+ "secondary_zones": [
+ {
+ "class": "IN",
+ "name": "secondary.org"
+ }
+ ]
+ },
+ "Auth": {
+ "database_file": "data/ddns/example.org.sqlite3",
+ "listen_on": [
+ {
+ "port": 47806,
+ "address":
+ "127.0.0.1"
+ }
+ ]
+ },
+ "Boss": {
+ "components": {
+ "b10-xfrout": {
+ "kind": "dispensable",
+ "address": "Xfrout"
+ },
+ "b10-zonemgr": {
+ "kind": "dispensable",
+ "address": "ZoneMgr"
+ },
+ "b10-ddns": {
+ "kind": "dispensable",
+ "address": "DDNS"
+ },
+ "b10-auth": {
+ "kind": "needed",
+ "special": "auth"
+ },
+ "b10-cmdctl": {
+ "kind": "needed",
+ "special": "cmdctl"
+ }
+ }
+ },
+ "DDNS": {
+ "zones": [
+ {
+ "origin": "example.org.",
+ "update_acl": [
+ {
+ "action": "ACCEPT",
+ "from": "127.0.0.1"
+ }
+ ],
+ "class": "IN"
+ },
+ {
+ "origin": "secondary.org.",
+ "update_acl": [
+ {
+ "action": "ACCEPT",
+ "from": "127.0.0.1"
+ }
+ ],
+ "class": "IN"
+ }
+ ]
+ }
+}
diff --git a/tests/lettuce/configurations/ddns/noddns.config.orig b/tests/lettuce/configurations/ddns/noddns.config.orig
new file mode 100644
index 0000000..bf89537
--- /dev/null
+++ b/tests/lettuce/configurations/ddns/noddns.config.orig
@@ -0,0 +1,43 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [
+ {
+ "severity": "DEBUG",
+ "name": "*",
+ "debuglevel": 99
+ }
+ ]
+ },
+ "DDNS": {"zones": []},
+ "Auth": {
+ "database_file": "data/ddns/example.org.sqlite3",
+ "listen_on": [
+ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ }
+ ],
+ "datasources": [
+ {
+ "type": "memory",
+ "class": "IN",
+ "zones": [
+ {
+ "origin": "example.org",
+ "filetype": "sqlite3",
+ "file": "data/ddns/example.org.sqlite3"
+ }
+ ]
+ }
+ ]
+ },
+ "Boss": {
+ "components": {
+ "b10-xfrout": {"kind": "dispensable"},
+ "b10-auth": {"kind": "needed", "special": "auth"},
+ "b10-zonemgr": {"kind": "dispensable", "address": "ZoneMgr" },
+ "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/default.config b/tests/lettuce/configurations/default.config
new file mode 100644
index 0000000..9e1d3d1
--- /dev/null
+++ b/tests/lettuce/configurations/default.config
@@ -0,0 +1,16 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "StatsHttpd": {
+ "listen_on": [ {
+ "port": 47811,
+ "address": "127.0.0.1"
+ } ]
+ }
+}
diff --git a/tests/lettuce/configurations/example.org.config.orig b/tests/lettuce/configurations/example.org.config.orig
new file mode 100644
index 0000000..fadb3e2
--- /dev/null
+++ b/tests/lettuce/configurations/example.org.config.orig
@@ -0,0 +1,23 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/example.org.inmem.config b/tests/lettuce/configurations/example.org.inmem.config
new file mode 100644
index 0000000..6418c65
--- /dev/null
+++ b/tests/lettuce/configurations/example.org.inmem.config
@@ -0,0 +1,8 @@
+{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Auth": {"database_file": "", "listen_on": [{"port": 47806, "address": "127.0.0.1"}], "datasources": [{"zones": [{"origin": "example.org", "file": "data/example.org"}], "type": "memory"}]},
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/example2.org.config b/tests/lettuce/configurations/example2.org.config
new file mode 100644
index 0000000..25314dc
--- /dev/null
+++ b/tests/lettuce/configurations/example2.org.config
@@ -0,0 +1,24 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "severity": "DEBUG",
+ "name": "*",
+ "debuglevel": 99
+ }
+ ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "port": 47807,
+ "address": "::1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
new file mode 100644
index 0000000..8571015
--- /dev/null
+++ b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
@@ -0,0 +1,32 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "datasources": [ {
+ "type": "memory",
+ "zones": [ {
+ "origin": "example.org",
+ "file": "data/example.org.sqlite3",
+ "filetype": "sqlite3"
+ } ]
+ } ],
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/ixfr-out/testset1-config.db b/tests/lettuce/configurations/ixfr-out/testset1-config.db
new file mode 100644
index 0000000..1c1b990
--- /dev/null
+++ b/tests/lettuce/configurations/ixfr-out/testset1-config.db
@@ -0,0 +1,11 @@
+{"Xfrin": {"zones": [{"use_ixfr": true, "class": "IN", "name": "example.com.", "master_addr": "178.18.82.80"}]}, "version": 2, "Logging": {"loggers": [{"debuglevel": 99, "severity": "DEBUG", "output_options": [{"output": "stderr", "flush": true}], "name": "*"}]}, "Auth": {"database_file": "data/ixfr-out/zones.sqlite3", "listen_on": [{"port": 47806, "address": "::"}, {"port": 47806, "address": "0.0.0.0"}]},
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/multi_instance/.gitignore b/tests/lettuce/configurations/multi_instance/.gitignore
new file mode 100644
index 0000000..9509290
--- /dev/null
+++ b/tests/lettuce/configurations/multi_instance/.gitignore
@@ -0,0 +1 @@
+/multi_auth.config
diff --git a/tests/lettuce/configurations/multi_instance/multi_auth.config.orig b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
new file mode 100644
index 0000000..35b8e72
--- /dev/null
+++ b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
@@ -0,0 +1,24 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/test_nonexistent_db.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth-2": {"kind": "dispensable", "special": "auth"},
+ "b10-auth": {"kind": "dispensable", "special": "auth"},
+ "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/no_db_file.config b/tests/lettuce/configurations/no_db_file.config
new file mode 100644
index 0000000..fc0a25d
--- /dev/null
+++ b/tests/lettuce/configurations/no_db_file.config
@@ -0,0 +1,24 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "severity": "DEBUG",
+ "name": "*",
+ "debuglevel": 99
+ }
+ ]
+ },
+ "Auth": {
+ "database_file": "data/test_nonexistent_db.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/nsec3/nsec3_auth.config b/tests/lettuce/configurations/nsec3/nsec3_auth.config
new file mode 100644
index 0000000..94514c0
--- /dev/null
+++ b/tests/lettuce/configurations/nsec3/nsec3_auth.config
@@ -0,0 +1 @@
+{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Auth": {"datasources": [{"zones": [{"origin": "example.", "file": "configurations/nsec3/rfc5155-example.zone.signed"}], "type": "memory"}], "listen_on": [{"port": 47806, "address": "0.0.0.0"}]}, "Boss": {"components": {"b10-auth": {"kind": "needed", "special": "auth"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}
diff --git a/tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed b/tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed
new file mode 100644
index 0000000..4120224
--- /dev/null
+++ b/tests/lettuce/configurations/nsec3/rfc5155-example.zone.signed
@@ -0,0 +1,72 @@
+;; The example NSEC3-signed zone used in RFC5155.
+
+example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+example. 3600 IN NS ns1.example.
+example. 3600 IN NS ns2.example.
+example. 3600 IN RRSIG NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+example. 3600 IN MX 1 xx.example.
+example. 3600 IN RRSIG MX 7 1 3600 20150420235959 20051021000000 40430 example. GgQ1A9xs47k42VPvpL/a1BWUz/6XsnHkjotw9So8MQtZtl2wJBsnOQsa oHrRCrRbyriEl/GZn9Mto/Kx+wBo+w==
+example. 3600 IN DNSKEY 256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=
+example. 3600 IN DNSKEY 257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJj7IommWSpJABVfW8Q0rO vXdM6kzt+TAu92L9AbsUdblMFin8CVF3n4s=
+example. 3600 IN RRSIG DNSKEY 7 1 3600 20150420235959 20051021000000 12708 example. AuU4juU9RaxescSmStrQks3Gh9FblGBlVU31uzMZ/U/FpsUb8aC6QZS+ sTsJXnLnz7flGOsmMGQZf3bH+QsCtg==
+example. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD
+example. 3600 IN RRSIG NSEC3PARAM 7 1 3600 20150420235959 20051021000000 40430 example. C1Gl8tPZNtnjlrYWDeeUV/sGLCyy/IHie2rerN05XSA3Pq0U3+4VvGWY WdUMfflOdxqnXHwJTLQsjlkynhG6Cg==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN A 192.0.2.127
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. h6c++bzhRuWWt2bykN6mjaTNBcXNq5UuL5EdK+iDP4eY8I0kSiKaCjg3 tC1SQkeloMeub2GWk8p6xHMPZumXlw==
+a.example. 3600 IN NS ns1.a.example.
+a.example. 3600 IN NS ns2.a.example.
+a.example. 3600 IN DS 58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C
+a.example. 3600 IN RRSIG DS 7 2 3600 20150420235959 20051021000000 40430 example. XacFcQVHLVzdoc45EJhN616zQ4mEXtE8FzUhM2KWjfy1VfRKD9r1MeVG wwoukOKgJxBPFsWoo722vZ4UZ2dIdA==
+ns1.a.example. 3600 IN A 192.0.2.5
+ns2.a.example. 3600 IN A 192.0.2.6
+ai.example. 3600 IN A 192.0.2.9
+ai.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ai.example. 3600 IN HINFO "KLH-10" "ITS"
+ai.example. 3600 IN RRSIG HINFO 7 2 3600 20150420235959 20051021000000 40430 example. Yi42uOq43eyO6qXHNvwwfFnIustWgV5urFcxenkLvs6pKRh00VBjODmf 3Z4nMO7IOl6nHSQ1v0wLHpEZG7Xj2w==
+ai.example. 3600 IN AAAA 2001:db8::f00:baa9
+ai.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+c.example. 3600 IN NS ns1.c.example.
+c.example. 3600 IN NS ns2.c.example.
+ns1.c.example. 3600 IN A 192.0.2.7
+ns2.c.example. 3600 IN A 192.0.2.8
+ns1.example. 3600 IN A 192.0.2.1
+ns1.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ns2.example. 3600 IN A 192.0.2.2
+ns2.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+*.w.example. 3600 IN MX 1 ai.example.
+*.w.example. 3600 IN RRSIG MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+x.w.example. 3600 IN MX 1 xx.example.
+x.w.example. 3600 IN RRSIG MX 7 3 3600 20150420235959 20051021000000 40430 example. IrK3tq/tHFIBF0scHiE/1IwMAvckS/55hAVvQyxTFbkAdDloP3NbZzu+ yoSsr3b3OX6qbBpY7WCtwwekLKRAwQ==
+x.y.w.example. 3600 IN MX 1 xx.example.
+x.y.w.example. 3600 IN RRSIG MX 7 4 3600 20150420235959 20051021000000 40430 example. MqSt5HqJIN8+SLlzTOImrh5h9Xa6gDvAW/GnnbdPc6Z7nXvCpLPJj/5l Cwx3VuzVOjkbvXze8/8Ccl2Zn2hbug==
+xx.example. 3600 IN A 192.0.2.10
+xx.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. T35hBWEZ017VC5u2c4OriKyVn/pu+fVK4AlXYOxJ6iQylfV2HQIKjv6b 7DzINB3aF/wjJqgXpQvhq+Ac6+ZiFg==
+xx.example. 3600 IN HINFO "KLH-10" "TOPS-20"
+xx.example. 3600 IN RRSIG HINFO 7 2 3600 20150420235959 20051021000000 40430 example. KimG+rDd+7VA1zRsu0ITNAQUTRlpnsmqWrihFRnU+bRa93v2e5oFNFYC s3Rqgv62K93N7AhW6Jfqj/8NzWjvKg==
+xx.example. 3600 IN AAAA 2001:db8::f00:baaa
+xx.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. IXBcXORITNwd8h3gNwyxtYFvAupS/CYWufVeuBUX0O25ivBCULjZjpDx FSxfohb/KA7YRdxENzYfMItpILl/Xw==
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S A RRSIG
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OmBvJ1Vgg1hCKMXHFiNeIYHK9XVW0iLDLwJN4TFoNxZuP03gAXEI634Y wOc4YBNITrj413iqNI6mRk/r1dOSUw==
+2vptu5timamqttgl4luu9kg21e0aor3s.example. 3600 IN NSEC3 1 1 12 AABBCCDD 35MTHGPGCU1QG68FAB165KLNSNK3DPVL MX RRSIG
+2vptu5timamqttgl4luu9kg21e0aor3s.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. KL1V2oFYghNV0Hm7Tf2vpJjM6l+0g1JCcVYGVfI0lKrhPmTsOA96cLEA Cgo1x8I7kApJX+obTuktZ+sdsZPY1w==
+35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG
+35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN NSEC3 1 1 12 AABBCCDD GJEQE526PLBF1G8MKLP59ENFD789NJGI MX RRSIG
+b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN NSEC3 1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN NSEC3 1 1 12 AABBCCDD K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. gPkFp1s2QDQ6wQzcg1uSebZ61W33rUBDcTj72F3kQ490fEdp7k1BUIfb cZtPbX3YCpE+sIt0MpzVSKfTwx4uYA==
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3 1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example. 3600 IN NSEC3 1 1 12 AABBCCDD Q04JKCEVQVMU85R014C7DKBA38O0JI5R A RRSIG
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. VrDXs2uVW21N08SyQIz88zml+y4ZCInTwgDr6zz43yAg+LFERjOrj3Oj ct51ac7Dp4eZbf9FQJazmASFKGxGXg==
+q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG
+q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+t644ebqk9bibcna874givr6joj62mlhv.example. 3600 IN NSEC3 1 1 12 AABBCCDD 0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM A HINFO AAAA RRSIG
+t644ebqk9bibcna874givr6joj62mlhv.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. RAjGECB8P7O+F4Pa4Dx3tC0M+Z3KmlLKImcafb9XWwx+NWUNz7NBEDBQ HivIyKPVDkChcePIX1xPl1ATNa+8Dw==
diff --git a/tests/lettuce/configurations/resolver/.gitignore b/tests/lettuce/configurations/resolver/.gitignore
new file mode 100644
index 0000000..8d60553
--- /dev/null
+++ b/tests/lettuce/configurations/resolver/.gitignore
@@ -0,0 +1 @@
+/resolver_basic.config
diff --git a/tests/lettuce/configurations/resolver/resolver_basic.config.orig b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
new file mode 100644
index 0000000..0adca9f
--- /dev/null
+++ b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
@@ -0,0 +1 @@
+{"version": 2, "Logging": {"loggers": [{"severity": "DEBUG", "name": "*", "debuglevel": 99}]}, "Resolver": {"query_acl": [{"action": "REJECT", "from": "127.0.0.1"}], "listen_on": [{"port": 47806, "address": "127.0.0.1"}]}, "Boss": {"components": {"b10-resolver": {"kind": "needed"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}
diff --git a/tests/lettuce/configurations/xfrin/inmem_slave.conf b/tests/lettuce/configurations/xfrin/inmem_slave.conf
new file mode 100644
index 0000000..a6d88ee
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/inmem_slave.conf
@@ -0,0 +1,34 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/inmem-xfrin.sqlite3",
+ "datasources": [ {
+ "type": "memory",
+ "class": "IN",
+ "zones": [ {
+ "origin": "example.org",
+ "file": "data/inmem-xfrin.sqlite3",
+ "filetype": "sqlite3"
+ } ]
+ } ],
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master.conf b/tests/lettuce/configurations/xfrin/retransfer_master.conf
new file mode 100644
index 0000000..eae47a6
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf
@@ -0,0 +1,30 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "port": 47807,
+ "address": "::1"
+ } ]
+ },
+ "Xfrout": {
+ "zone_config": [ {
+ "origin": "example.org"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave.conf b/tests/lettuce/configurations/xfrin/retransfer_slave.conf
new file mode 100644
index 0000000..2296b8f
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave.conf
@@ -0,0 +1,25 @@
+{
+ "version": 2,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/test_nonexistent_db.sqlite3",
+ "listen_on": [ {
+ "port": 47806,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/data/.gitignore b/tests/lettuce/data/.gitignore
new file mode 100644
index 0000000..8c54200
--- /dev/null
+++ b/tests/lettuce/data/.gitignore
@@ -0,0 +1,2 @@
+/inmem-xfrin.sqlite3
+/test_nonexistent_db.sqlite3
diff --git a/tests/lettuce/data/commands/bad_command b/tests/lettuce/data/commands/bad_command
new file mode 100644
index 0000000..95d1694
--- /dev/null
+++ b/tests/lettuce/data/commands/bad_command
@@ -0,0 +1,9 @@
+!echo shouldshow
+# just add something so the test can verify it's reverted
+config add /Boss/components b10-auth
+config set /Boss/components/b10-auth/kind needed
+config set /Boss/components/b10-auth/special auth
+bad command
+# this should not be reached
+!echo shouldnotshow
+config commit
diff --git a/tests/lettuce/data/commands/directives b/tests/lettuce/data/commands/directives
new file mode 100644
index 0000000..4fe10f5
--- /dev/null
+++ b/tests/lettuce/data/commands/directives
@@ -0,0 +1,19 @@
+# this is a comment: commentexample1
+!echo this is an echo: echoexample2
+!verbose on
+# this is a comment with verbose on: verbosecommentexample3
+!verbose off
+# this is a comment with verbose off again: commentexample4
+# empty lines and lines with only whitespace should be ignored
+
+
+
+
+
+# directives are case insensitive, and should handle whitespace
+!ECHO echoexample5
+!eChO echoexample6
+!Verbose ON
+# verbosecommentexample7
+!verBOSE off
+# commentexample8
diff --git a/tests/lettuce/data/commands/empty b/tests/lettuce/data/commands/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/lettuce/data/commands/nested b/tests/lettuce/data/commands/nested
new file mode 100644
index 0000000..c153694
--- /dev/null
+++ b/tests/lettuce/data/commands/nested
@@ -0,0 +1,2 @@
+# include a different file
+execute file data/commands/nested1
diff --git a/tests/lettuce/data/commands/nested1 b/tests/lettuce/data/commands/nested1
new file mode 100644
index 0000000..8f984d5
--- /dev/null
+++ b/tests/lettuce/data/commands/nested1
@@ -0,0 +1,2 @@
+# this is included by nested
+!echo shouldshow
diff --git a/tests/lettuce/data/ddns/.gitignore b/tests/lettuce/data/ddns/.gitignore
new file mode 100644
index 0000000..60c9224
--- /dev/null
+++ b/tests/lettuce/data/ddns/.gitignore
@@ -0,0 +1 @@
+/example.org.sqlite3
diff --git a/tests/lettuce/data/ddns/example.org.sqlite3.orig b/tests/lettuce/data/ddns/example.org.sqlite3.orig
new file mode 100644
index 0000000..427fa24
Binary files /dev/null and b/tests/lettuce/data/ddns/example.org.sqlite3.orig differ
diff --git a/tests/lettuce/data/empty_db.sqlite3 b/tests/lettuce/data/empty_db.sqlite3
new file mode 100644
index 0000000..c434e30
Binary files /dev/null and b/tests/lettuce/data/empty_db.sqlite3 differ
diff --git a/tests/lettuce/data/example.org b/tests/lettuce/data/example.org
new file mode 100644
index 0000000..77f8f53
--- /dev/null
+++ b/tests/lettuce/data/example.org
@@ -0,0 +1,13 @@
+example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+example.org. 3600 IN NS ns1.example.org.
+example.org. 3600 IN NS ns2.example.org.
+example.org. 3600 IN MX 10 mail.example.org.
+www.example.org. 3600 IN A 192.0.2.1
+mail.example.org. 3600 IN A 192.0.2.10
+sub.example.org. 3600 IN NS ns.sub.example.org.
+ns.sub.example.org. 3600 IN A 192.0.2.101
+dname.example.org. 3600 IN DNAME dname.example.info.
+dname2.foo.example.org. 3600 IN DNAME dname2.example.info.
+ns1.example.org. 3600 IN A 192.0.2.3
+ns2.example.org. 3600 IN A 192.0.2.4
+shell.example.org. 3600 IN SSHFP 2 1 123456789abcdef67890123456789abcdef67890
diff --git a/tests/lettuce/data/example.org.sqlite3 b/tests/lettuce/data/example.org.sqlite3
new file mode 100644
index 0000000..f79a4e2
Binary files /dev/null and b/tests/lettuce/data/example.org.sqlite3 differ
diff --git a/tests/lettuce/data/inmem-xfrin b/tests/lettuce/data/inmem-xfrin
new file mode 100644
index 0000000..9e02591
--- /dev/null
+++ b/tests/lettuce/data/inmem-xfrin
@@ -0,0 +1,7 @@
+example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+example.org. 3600 IN NS ns1.example.org.
+example.org. 3600 IN NS ns2.example.org.
+example.org. 3600 IN MX 10 mail.example.org.
+www.example.org. 3600 IN A 192.0.2.63
+ns1.example.org. 3600 IN A 192.0.2.3
+ns2.example.org. 3600 IN A 192.0.2.4
diff --git a/tests/lettuce/data/inmem-xfrin.sqlite3.orig b/tests/lettuce/data/inmem-xfrin.sqlite3.orig
new file mode 100644
index 0000000..287d980
Binary files /dev/null and b/tests/lettuce/data/inmem-xfrin.sqlite3.orig differ
diff --git a/tests/lettuce/data/ixfr-out/.gitignore b/tests/lettuce/data/ixfr-out/.gitignore
new file mode 100644
index 0000000..f8de78e
--- /dev/null
+++ b/tests/lettuce/data/ixfr-out/.gitignore
@@ -0,0 +1 @@
+/zones.sqlite3
diff --git a/tests/lettuce/data/ixfr-out/zones.sqlite3 b/tests/lettuce/data/ixfr-out/zones.sqlite3
new file mode 100644
index 0000000..311d335
Binary files /dev/null and b/tests/lettuce/data/ixfr-out/zones.sqlite3 differ
diff --git a/tests/lettuce/features/bindctl_commands.feature b/tests/lettuce/features/bindctl_commands.feature
new file mode 100644
index 0000000..20a28fc
--- /dev/null
+++ b/tests/lettuce/features/bindctl_commands.feature
@@ -0,0 +1,156 @@
+Feature: control with bindctl
+ Assorted tests using bindctl for the administration of BIND 10.
+
+
+ Scenario: Removing modules
+ # This test runs the original example configuration, which has
+ # a number of modules. It then removes all non-essential modules,
+ # and checks whether they do disappear from the list of running
+ # modules (note that it 'misuses' the help command for this,
+ # there is a Boss command 'show_processes' but it's output is
+ # currently less standardized than 'help')
+ Given I have bind10 running with configuration bindctl_commands.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message STATS_STARTING
+ And wait for bind10 stderr message STATHTTPD_STARTED
+
+ Then remove bind10 configuration Boss/components/NOSUCHMODULE
+ last bindctl output should contain Error
+
+ bind10 module Xfrout should be running
+ bind10 module Stats should be running
+ bind10 module Zonemgr should be running
+ bind10 module Xfrin should be running
+ bind10 module Auth should be running
+ bind10 module StatsHttpd should be running
+ bind10 module Resolver should not be running
+
+ Then remove bind10 configuration Boss/components value b10-xfrout
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ # assuming it won't error for further modules (if it does, the final
+ # 'should not be running' tests would fail anyway)
+ Then remove bind10 configuration Boss/components value b10-stats-httpd
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-stats
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-zonemgr
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-xfrin
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ Then remove bind10 configuration Boss/components value b10-auth
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ last bindctl output should not contain Error
+
+ # After these ^^^ have been stopped...
+ bind10 module Xfrout should not be running
+ bind10 module Zonemgr should not be running
+ bind10 module Xfrin should not be running
+ bind10 module Auth should not be running
+ bind10 module StatsHttpd should not be running
+ bind10 module Stats should not be running
+ bind10 module Resolver should not be running
+
+ Scenario: Executing scripts from files
+ # This test tests the 'execute' command, which reads and executes
+ # bindctl commands from a file
+ Given I have bind10 running with configuration bindctl/bindctl.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ # first a few bad commands
+ When I send bind10 the command execute
+ last bindctl output should contain Error
+ When I send bind10 the command execute file
+ last bindctl output should contain Error
+ When I send bind10 the command execute file data/commands/nosuchfile
+ last bindctl output should contain Error
+
+ # empty list should be no-op
+ When I send bind10 the command execute file data/commands/empty
+ last bindctl output should not contain Error
+
+ # some tests of directives like !echo and !verbose
+ When I send bind10 the command execute file data/commands/directives
+ last bindctl output should not contain Error
+ last bindctl output should not contain commentexample1
+ last bindctl output should contain echoexample2
+ last bindctl output should contain verbosecommentexample3
+ last bindctl output should not contain commentexample4
+ last bindctl output should contain echoexample5
+ last bindctl output should contain echoexample6
+ last bindctl output should contain verbosecommentexample7
+ last bindctl output should not contain commentexample8
+
+ # bad_command contains a bad command, at which point execution should stop
+ When I send bind10 the command execute file data/commands/bad_command
+ last bindctl output should contain shouldshow
+ last bindctl output should contain Error
+ last bindctl output should not contain shouldnotshow
+ # This would fail if the entire list was passed, or the configuration
+ # was committed
+ send bind10 the command config show Boss/components
+ last bindctl output should not contain b10-auth
+
+ # nested_command contains another execute script
+ When I send bind10 the command execute file data/commands/nested
+ last bindctl output should contain shouldshow
+ last bindctl output should not contain Error
+
+ # show commands from a file
+ When I send bind10 the command execute file data/commands/bad_command show
+ last bindctl output should not contain Error
+ last bindctl output should contain shouldshow
+ last bindctl output should contain shouldnotshow
+
+ Scenario: Executing builting script init_authoritative_server
+ Given I have bind10 running with configuration bindctl/bindctl.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ When I send bind10 the command execute init_authoritative_server show
+ # just test some parts of the output
+ last bindctl output should contain /Boss/components/b10-auth/special
+ last bindctl output should contain /Boss/components/b10-zonemgr/kind
+ last bindctl output should contain Please
+
+ # nothing should have been changed
+ When I send bind10 the command config diff
+ last bindctl output should contain {}
+
+ # ok now make sure modules aren't running, execute it, and make
+ # sure modules are running
+ bind10 module Auth should not be running
+ bind10 module Xfrout should not be running
+ bind10 module Xfrin should not be running
+ bind10 module Zonemgr should not be running
+
+ When I send bind10 the following commands:
+ """
+ execute init_authoritative_server
+ config commit
+ """
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+
+ last bindctl output should not contain Error
+ bind10 module Auth should be running
+ bind10 module Xfrout should be running
+ bind10 module Xfrin should be running
+ bind10 module Zonemgr should be running
diff --git a/tests/lettuce/features/ddns_system.feature b/tests/lettuce/features/ddns_system.feature
new file mode 100644
index 0000000..327ef96
--- /dev/null
+++ b/tests/lettuce/features/ddns_system.feature
@@ -0,0 +1,144 @@
+Feature: DDNS System
+ A number of BIND10-specific DDNS tests, that do not fall under the
+ 'compliance' category; specific ACL checks, module checks, etc.
+
+ Scenario: Module tests
+ # The given config has b10-ddns disabled
+ Given I have bind10 running with configuration ddns/noddns.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ # Sanity check
+ bind10 module DDNS should not be running
+
+ # Test 1
+ When I use DDNS to set the SOA serial to 1235
+ # Note: test spec says refused here, system returns SERVFAIL
+ #The DDNS response should be REFUSED
+ The DDNS response should be SERVFAIL
+ And the SOA serial for example.org should be 1234
+
+ # Test 2
+ When I configure bind10 to run DDNS
+ And wait for new bind10 stderr message DDNS_STARTED
+ bind10 module DDNS should be running
+
+ # Test 3
+ When I use DDNS to set the SOA serial to 1236
+ The DDNS response should be REFUSED
+ And the SOA serial for example.org should be 1234
+
+ # Test 4
+ When I send bind10 the following commands
+ """
+ config add DDNS/zones
+ config set DDNS/zones[0]/origin example.org
+ config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "from": "127.0.0.1"}
+ config commit
+ """
+
+ # Test 5
+ When I use DDNS to set the SOA serial to 1237
+ # also check if Auth server reloaded
+ And wait for new bind10 stderr message AUTH_LOAD_ZONE
+ The DDNS response should be SUCCESS
+ And the SOA serial for example.org should be 1237
+
+ # Test 6
+ When I send bind10 the command DDNS shutdown
+ And wait for new bind10 stderr message DDNS_STOPPED
+
+ # Test 7
+ # BoB should restart it
+ And wait for new bind10 stderr message DDNS_STARTED
+
+ # Test 8
+ # Known issue: after shutdown, first new attempt results in SERVFAIL
+ When I use DDNS to set the SOA serial to 1238
+ The DDNS response should be SERVFAIL
+ And the SOA serial for example.org should be 1237
+
+ When I use DDNS to set the SOA serial to 1238
+ And wait for new bind10 stderr message AUTH_LOAD_ZONE
+ The DDNS response should be SUCCESS
+ And the SOA serial for example.org should be 1238
+
+ # Test 9
+ When I send bind10 the command Auth shutdown
+ And wait for new bind10 stderr message AUTH_SHUTDOWN
+ # BoB should restart it automatically
+ And wait for new bind10 stderr message AUTH_SERVER_STARTED
+
+ # Test 10
+ When I use DDNS to set the SOA serial to 1239
+ And wait for new bind10 stderr message AUTH_LOAD_ZONE
+ The DDNS response should be SUCCESS
+ And the SOA serial for example.org should be 1239
+
+ # Test 11
+ When I configure BIND10 to stop running DDNS
+ And wait for new bind10 stderr message DDNS_STOPPED
+
+ bind10 module DDNS should not be running
+
+ # Test 12
+ When I use DDNS to set the SOA serial to 1240
+ # should this be REFUSED again?
+ The DDNS response should be SERVFAIL
+ And the SOA serial for example.org should be 1239
+
+ Scenario: ACL
+ Given I have bind10 running with configuration ddns/ddns.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message DDNS_STARTED
+
+ # Sanity check
+ A query for new1.example.org should have rcode NXDOMAIN
+ A query for new2.example.org should have rcode NXDOMAIN
+ A query for new3.example.org should have rcode NXDOMAIN
+ The SOA serial for example.org should be 1234
+
+ # Test 1
+ When I use DDNS to add a record new1.example.org. 3600 IN A 192.0.2.1
+ The DDNS response should be SUCCESS
+ A query for new1.example.org should have rcode NOERROR
+ The SOA serial for example.org should be 1235
+
+ # Test 2
+ When I set DDNS ACL 0 for 127.0.0.1 to REJECT
+ Then use DDNS to add a record new2.example.org. 3600 IN A 192.0.2.2
+ The DDNS response should be REFUSED
+ A query for new2.example.org should have rcode NXDOMAIN
+ The SOA serial for example.org should be 1235
+
+ # Test 3
+ When I set DDNS ACL 0 for 127.0.0.1 to ACCEPT
+ Then use DDNS to add a record new3.example.org. 3600 IN A 192.0.2.3
+ The DDNS response should be SUCCESS
+ A query for new3.example.org should have rcode NOERROR
+ The SOA serial for example.org should be 1236
+
+ #Scenario: DDNS and Xfrout
+ ## Unfortunately, Xfrout can only notify to inzone slaves, and hence only
+ ## to port 53, which we do not want to use for Lettuce tests (for various
+ ## reasons). So for now this test is only an outline, the configs
+ ## themselves have not been set up yet
+ # When I start bind10 with configuration ddns/primary.config as primary
+ # And wait for primary stderr message AUTH_SERVER_STARTED
+ # And wait for primary stderr message XFROUT_STARTED
+ # And wait for primary stderr message DDNS_STARTED
+
+ # And I start bind10 with configuration example2.org.config with cmdctl port 47804 as secondary
+ # And wait for secondary stderr message AUTH_SERVER_STARTED
+ # And wait for secondary stderr message XFRIN_STARTED
+
+ # # Sanity check
+ # The SOA serial for example.org should be 1234
+ # The SOA serial for example.org at 127.0.0.1:47807 should be 1234
+
+ # When I use DDNS to set the SOA serial to 1235
+ # The DDNS response should be SUCCESS
+
+ # The SOA serial for example.org should be 1235
+ # The SOA serial for example.org at 127.0.0.1:47807 should be 1235
diff --git a/tests/lettuce/features/default.feature b/tests/lettuce/features/default.feature
new file mode 100644
index 0000000..ce7ee1e
--- /dev/null
+++ b/tests/lettuce/features/default.feature
@@ -0,0 +1,21 @@
+Feature: default bind10 config
+ Tests for the default configuration of bind10.
+
+ Scenario: Check that only the default components are running
+ Given I have bind10 running with configuration default.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message STATS_STARTING
+
+ # These should be running
+ bind10 module Boss should be running
+ And bind10 module Logging should be running
+ And bind10 module Stats should be running
+
+ # These should not be running
+ bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Auth should not be running
+ And bind10 module StatsHttpd should not be running
diff --git a/tests/lettuce/features/example.feature b/tests/lettuce/features/example.feature
new file mode 100644
index 0000000..685cf8b
--- /dev/null
+++ b/tests/lettuce/features/example.feature
@@ -0,0 +1,193 @@
+Feature: Example feature
+ This is an example Feature set. Is is mainly intended to show
+ our use of the lettuce tool and our own framework for it
+ The first scenario is to show what a simple test would look like, and
+ is intentionally uncommented.
+ The later scenarios have comments to show what the test steps do and
+ support
+
+ Scenario: A simple example
+ Given I have bind10 running with configuration example.org.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for www.example.org should have rcode NOERROR
+ A query for www.doesnotexist.org should have rcode REFUSED
+ The SOA serial for example.org should be 1234
+
+ Scenario: New database
+ # This test checks whether a database file is automatically created
+ # Underwater, we take advantage of our intialization routines so
+ # that we are sure this file does not exist, see
+ # features/terrain/terrain.py
+
+ # Standard check to test (non-)existence of a file.
+ # This file is actually automatically created.
+ The file data/test_nonexistent_db.sqlite3 should not exist
+
+ # In the first scenario, we used 'given I have bind10 running', which
+ # is actually a compound step consisting of the following two
+ # one to start the server
+ When I start bind10 with configuration no_db_file.config
+
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ # Now we use the first step again to see if the file has been created
+ The file data/test_nonexistent_db.sqlite3 should exist
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ # This is a general step to stop a named process. By convention,
+ # the default name for any process is the same as the one we
+ # use in the start step (for bind 10, that is 'I start bind10 with')
+ # See scenario 'Multiple instances' for more.
+ Then stop process bind10
+
+ Scenario: example.org queries
+ # This scenario performs a number of queries and inspects the results
+ # Simple queries have already been show, but after we have sent a query,
+ # we can also do more extensive checks on the result.
+ # See querying.py for more information on these steps.
+
+ # note: lettuce can group similar checks by using tables, but we
+ # intentionally do not make use of that here
+
+ # This is a compound statement that starts and waits for the
+ # started message
+ Given I have bind10 running with configuration example.org.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ # Some simple queries that is not examined further
+ A query for www.example.com should have rcode REFUSED
+ A query for www.example.org should have rcode NOERROR
+
+ # A query where we look at some of the result properties
+ A query for www.example.org should have rcode NOERROR
+ The last query response should have qdcount 1
+ The last query response should have ancount 1
+ The last query response should have nscount 3
+ The last query response should have adcount 0
+ # The answer section can be inspected in its entirety; in the future
+ # we may add more granular inspection steps
+ The answer section of the last query response should be
+ """
+ www.example.org. 3600 IN A 192.0.2.1
+ """
+
+ A query for example.org type NS should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ example.org. 3600 IN NS ns3.example.org.
+ """
+
+ # We have a specific step for checking SOA serial numbers
+ The SOA serial for example.org should be 1234
+
+ # Another query where we look at some of the result properties
+ A query for doesnotexist.example.org should have rcode NXDOMAIN
+ The last query response should have qdcount 1
+ The last query response should have ancount 0
+ The last query response should have nscount 1
+ The last query response should have adcount 0
+ # When checking flags, we must pass them exactly as they appear in
+ # the output of dig.
+ The last query response should have flags qr aa rd
+
+ A query for www.example.org type TXT should have rcode NOERROR
+ The last query response should have ancount 0
+
+ # Some queries where we specify more details about what to send and
+ # where
+ A query for www.example.org class CH should have rcode REFUSED
+ A query for www.example.org to 127.0.0.1 should have rcode NOERROR
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
+ A query for www.example.org type A class IN to 127.0.0.1:47806 should have rcode NOERROR
+
+ Scenario: changing database
+ # This scenario contains a lot of 'wait for' steps
+ # If those are not present, the asynchronous nature of the application
+ # can cause some of the things we send to be handled out of order;
+ # for instance auth could still be serving the old zone when we send
+ # the new query, or already respond from the new database.
+ # Therefore we wait for specific log messages after each operation
+ #
+ # This scenario outlines every single step, and does not use
+ # 'steps of steps' (e.g. Given I have bind10 running)
+ # We can do that but as an example this is probably better to learn
+ # the system
+
+ When I start bind10 with configuration example.org.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for www.example.org should have rcode NOERROR
+ Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+ Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
+ And wait for new bind10 stderr message DATASRC_SQLITE_OPEN
+ A query for www.example.org should have rcode REFUSED
+ Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+ Then set bind10 configuration Auth/database_file to data/example.org.sqlite3
+ And wait for new bind10 stderr message DATASRC_SQLITE_OPEN
+ A query for www.example.org should have rcode NOERROR
+
+ Scenario: two bind10 instances
+ # This is more a test of the test system, start 2 bind10's
+ When I start bind10 with configuration example.org.config as bind10_one
+ And wait for bind10_one stderr message BIND10_STARTED_CC
+ And wait for bind10_one stderr message CMDCTL_STARTED
+ And wait for bind10_one stderr message AUTH_SERVER_STARTED
+
+ And I start bind10 with configuration example2.org.config with cmdctl port 47804 as bind10_two
+ And wait for bind10_two stderr message BIND10_STARTED_CC
+ And wait for bind10_two stderr message CMDCTL_STARTED
+ And wait for bind10_two stderr message AUTH_SERVER_STARTED
+
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
+ A query for www.example.org to [::1]:47807 should have rcode NOERROR
+ The SOA serial for example.org should be 1234
+ The SOA serial for example.org at 127.0.0.1:47806 should be 1234
+ The SOA serial for example.org at ::1:47807 should be 1234
+
+ Then set bind10 configuration Auth/database_file to data/empty_db.sqlite3
+ And wait for bind10_one stderr message DATASRC_SQLITE_OPEN
+
+ A query for www.example.org to 127.0.0.1:47806 should have rcode REFUSED
+ A query for www.example.org to [::1]:47807 should have rcode NOERROR
diff --git a/tests/lettuce/features/inmemory_over_sqlite3.feature b/tests/lettuce/features/inmemory_over_sqlite3.feature
new file mode 100644
index 0000000..2e48689
--- /dev/null
+++ b/tests/lettuce/features/inmemory_over_sqlite3.feature
@@ -0,0 +1,43 @@
+Feature: In-memory zone using SQLite3 backend
+ This feature tests the authoritative server configured with an in-memory
+ data source that uses the SQLite3 data source as the backend, and tests
+ scenarios that update the zone via incoming zone transfers.
+
+ Scenario: 1. Load and response
+ Given I have bind10 running with configuration inmemory_over_sqlite3/secondary.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ A query for www.example.org should have rcode NOERROR
+ The SOA serial for example.org should be 1234
+
+ Scenario: 2. In-memory datasource backed by sqlite3
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+
+ And I have bind10 running with configuration xfrin/inmem_slave.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ A query for www.example.org should have rcode NOERROR
+ """
+ www.example.org. 3600 IN A 192.0.2.63
+ """
+ A query for mail.example.org should have rcode NXDOMAIN
+ When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
+ Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ Then wait for new bind10 stderr message AUTH_LOAD_ZONE
+
+ A query for www.example.org should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ www.example.org. 3600 IN A 192.0.2.1
+ """
+ A query for mail.example.org should have rcode NOERROR
diff --git a/tests/lettuce/features/ixfr_out_bind10.feature b/tests/lettuce/features/ixfr_out_bind10.feature
new file mode 100644
index 0000000..24a9299
--- /dev/null
+++ b/tests/lettuce/features/ixfr_out_bind10.feature
@@ -0,0 +1,209 @@
+Feature: IXFR out
+ Tests for IXFR-out, specific for BIND 10 behaviour.
+ These are (part of) the tests as described on
+ http://bind10.isc.org/wiki/IxfrSystemTests
+
+ # A lot of these tests test specific UDP behaviour.
+ #
+ # Where possible, we use the TCP equivalent. Some of the behaviour
+ # tested is UDP-specific though. In either case, a comment above
+ # the test shows how and why it differs from the test specification,
+ # or why it is commented out for now.
+ # When we do implement UDP IXFR, we should probably keep the TCP
+ # tests, and add them to the test specification, so we still have a
+ # 1-to-1 mapping between these tests and the specification document.
+ #
+ # These tests use a zone with just a few records, the first serial
+ # is 2, and it is incremented in steps of 2, up to serial 22.
+ # Each updates either deletes or adds the www.example.com A record.
+ # Version 2 has the record, then the update to version 4 deletes it,
+ # the update to 6 adds it again, and so on, until version 22 (where
+ # the last update has added it again)
+ #
+ # Some of the tests (scenario 1 tests 3 and 4, and scenario 2 tests 1 and
+ # 2 may still not work if we replicate BIND 9's behaviour; it always
+ # responds to UDP IXFR requests with just the SOA, and it does not do
+ # AXFR-style IXFR if the number of changes exceeds the size of the zone)
+ #
+ # So in effect, there is only one test that is currently active (scenario
+ # 1 test 7)
+
+
+ Scenario: Test Set 1
+ Given I have bind10 running with configuration ixfr-out/testset1-config.db
+
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ The SOA serial for example.com should be 22
+
+ #
+ # Test 1
+ #
+ # We don't support UDP yet, and for TCP we currently return full zone,
+ # so this test is currently skipped
+ #
+ #When I do an IXFR transfer of example.com 123 over udp
+ #The transfer result should have 1 RRs
+ #The full result of the last transfer should be
+ #"""
+ #example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ #"""
+
+ #
+ # Test 2
+ #
+ # Original test specification was for UDP, using TCP for now
+ #
+ #When I do an IXFR transfer of example.com 22 over udp
+ When I do an IXFR transfer of example.com 22 over tcp
+ The transfer result should have 1 RRs
+ The full result of the last transfer should be
+ """
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ """
+
+ #
+ # Test 3
+ #
+ # Original test specification was for UDP, using TCP for now
+ #
+ #When I do an IXFR transfer of example.com 20 over udp
+ When I do an IXFR transfer of example.com 20 over tcp
+ The transfer result should have 5 RRs
+ The full result of the last transfer should be
+ """
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 20 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ """
+
+ #
+ # Test 4
+ #
+ # Original test specification was for UDP, using TCP for now
+ #
+ #When I do an IXFR transfer of example.com 18 over udp
+ When I do an IXFR transfer of example.com 18 over tcp
+ The transfer result should have 8 RRs
+ The full result of the last transfer should be
+ """
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 18 28800 7200 604800 18000
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 20 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 20 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ """
+
+ #
+ # Test 5
+ #
+ # This test does not have a TCP equivalent, so it is skipped.
+ #
+ #When I do an IXFR transfer of example.com 2 over udp
+ #The transfer result should have 1 RRs
+ #The full result of the last transfer should be
+ #"""
+ #example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ #"""
+
+ #
+ # Test 6
+ #
+ # This test does not have a TCP equivalent, so it is skipped.
+ #
+ #When I do an IXFR transfer of example.com 5 over udp
+ #The transfer result should have 1 RRs
+ #The full result of the last transfer should be
+ #"""
+ #example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ #"""
+
+ #
+ # Test 7
+ #
+ When I do an IXFR transfer of example.com 14 over tcp
+ The transfer result should have 14 RRs
+ The full result of the last transfer should be
+ """
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 14 28800 7200 604800 18000
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 16 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 16 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 18 28800 7200 604800 18000
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 18 28800 7200 604800 18000
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 20 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 20 28800 7200 604800 18000
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ """
+
+ Scenario: Test Set 2
+ Given I have bind10 running with configuration ixfr-out/testset1-config.db
+
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ The SOA serial for example.com should be 22
+
+ #
+ # Test 1
+ #
+ # Original test specification was for UDP, using TCP for now
+ #
+ #When I do an IXFR transfer of example.com 19 over udp
+ When I do an IXFR transfer of example.com 19 over tcp
+ The transfer result should have 5 RRs
+ The full result of the last transfer should be
+ """
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ example.com. 3600 IN NS ns.example.com.
+ ns.example.com. 3600 IN A 192.0.2.1
+ www.example.com. 3600 IN A 192.0.2.1
+ example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ """
+
+ #
+ # Test 2
+ #
+ # This test has no TCP equivalent
+ #
+ #When I do an IXFR transfer of example.com 6 over udp
+ #The transfer result should have 5 RRs
+ #The full result of the last transfer should be
+ #"""
+ #example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ #example.com. 3600 IN NS ns.example.com.
+ #ns.example.com. 3600 IN A 192.0.2.1
+ #www.example.com. 3600 IN A 192.0.2.1
+ #example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ #"""
+
+ #
+ # Test 3
+ #
+ # This test has no TCP equivalent
+ #
+ #When I do an IXFR transfer of example.com 2 over udp
+ #The transfer result should have 1 RRs
+ #The full result of the last transfer should be
+ #"""
+ #example.com. 3600 IN SOA ns.example.com. admin.example.com. 22 28800 7200 604800 18000
+ #"""
diff --git a/tests/lettuce/features/multi_instance.feature b/tests/lettuce/features/multi_instance.feature
new file mode 100644
index 0000000..4ce135a
--- /dev/null
+++ b/tests/lettuce/features/multi_instance.feature
@@ -0,0 +1,59 @@
+Feature: Multiple instances
+ This feature tests whether multiple instances can be run, and whether
+ removing them does not affect the running of other instances
+
+ Scenario: Multiple instances of Auth
+ # Standard check to test (non-)existence of a file
+ # This file is actually automatically
+ The file data/test_nonexistent_db.sqlite3 should not exist
+
+ # This config should have two running instances
+ Given I have bind10 running with configuration multi_instance/multi_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ # This is a hack. We should actually check if b10-auth and
+ # b10-auth-2 are started by name. But there's currently no way
+ # for a component to find out its name and log it.
+ And wait 2 times for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ # Now we use the first step again to see if the file has been created
+ The file data/test_nonexistent_db.sqlite3 should exist
+
+ A query for example.com should have rcode REFUSED
+
+ # this also checks whether the process is running
+ If I remember the pid of process b10-auth
+ And remember the pid of process b10-auth-2
+
+ When I remove bind10 configuration Boss/components value b10-auth-2
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+
+ Then the pid of process b10-auth should not have changed
+ And a query for example.com should have rcode REFUSED
+
+ When I send bind10 the following commands
+ """
+ config add Boss/components b10-auth-2
+ config set Boss/components/b10-auth-2/special auth
+ config set Boss/components/b10-auth-2/kind needed
+ config commit
+ """
+ And wait for new bind10 stderr message AUTH_SERVER_STARTED
+ And remember the pid of process b10-auth-2
+
+ Then the pid of process b10-auth should not have changed
+ A query for example.com should have rcode REFUSED
+
+ When I remove bind10 configuration Boss/components value b10-auth
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+ Then the pid of process b10-auth-2 should not have changed
+ A query for example.com should have rcode REFUSED
diff --git a/tests/lettuce/features/nsec3_auth.feature b/tests/lettuce/features/nsec3_auth.feature
new file mode 100644
index 0000000..4e5ed5b
--- /dev/null
+++ b/tests/lettuce/features/nsec3_auth.feature
@@ -0,0 +1,466 @@
+Feature: NSEC3 Authoritative service
+ This feature tests NSEC3 as defined in RFC5155, using the example
+ zone from appendix A and testing the example responses from appendix B.
+ Additional tests can be added as well.
+
+ # Response section data is taken directly from RFC5155
+ # It has been modified slightly; it has been 'flattened' (i.e. converted
+ # to 1-line RRs with TTL and class data), and whitespace has been added
+ # in the places where dig adds them too.
+ # Any other changes from the specific example data are added as inline
+ # comments.
+
+ Scenario: B.1. Name Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.c.x.w.example. should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 8
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN NSEC3 1 1 12 aabbccdd gjeqe526plbf1g8mklp59enfd789njgi MX RRSIG
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 aabbccdd b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ """
+
+ Scenario: B.2. No Data Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for ns1.example. type MX should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN NSEC3 1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG
+ 2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OmBvJ1Vgg1hCKMXHFiNeIYHK9XVW0iLDLwJN4TFoNxZuP03gAXEI634Y wOc4YBNITrj413iqNI6mRk/r1dOSUw==
+ """
+
+ Scenario: B2.1. No Data Error, Empty Non-Terminal
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for y.w.example. should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN NSEC3 1 1 12 aabbccdd k8udemvp1j2f7eg6jebps17vp3n8i58h
+ ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. gPkFp1s2QDQ6wQzcg1uSebZ61W33rUBDcTj72F3kQ490fEdp7k1BUIfb cZtPbX3YCpE+sIt0MpzVSKfTwx4uYA==
+ """
+
+ Scenario: B.3. Referral to an Opt-Out Unsigned Zone
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for mc.c.example. type MX should have rcode NOERROR
+ The last query response should have flags qr rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 3
+ The authority section of the last query response should be
+ """
+ c.example. 3600 IN NS ns1.c.example.
+ c.example. 3600 IN NS ns2.c.example.
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 aabbccdd b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ """
+ The additional section of the last query response should be
+ """
+ ns1.c.example. 3600 IN A 192.0.2.7
+ ns2.c.example. 3600 IN A 192.0.2.8
+ """
+
+ Scenario: B.4. Wildcard Expansion
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.z.w.example. type MX should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 2
+ The last query response should have nscount 5
+ The last query response should have adcount 9
+ The answer section of the last query response should be
+ """
+ a.z.w.example. 3600 IN MX 1 ai.example.
+ a.z.w.example. 3600 IN RRSIG MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+ """
+ The authority section of the last query response should be
+ """
+ example. 3600 IN NS ns1.example.
+ example. 3600 IN NS ns2.example.
+ example. 3600 IN RRSIG NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+ """
+ # This is slightly different from the example in RFC5155; there are
+ # more RRs in the additional section.
+ The additional section of the last query response should be
+ """
+ ai.example. 3600 IN A 192.0.2.9
+ ai.example. 3600 IN AAAA 2001:db8::f00:baa9
+ ns1.example. 3600 IN A 192.0.2.1
+ ns2.example. 3600 IN A 192.0.2.2
+ ai.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ ai.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+ ns1.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ ns2.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+ """
+
+ Scenario: B.5. Wildcard No Data Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.z.w.example. type AAAA should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 8
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3 1 1 12 aabbccdd kohar7mbb8dc2ce8a9qvl8hon4k53uhi
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 aabbccdd t644ebqk9bibcna874givr6joj62mlhv MX RRSIG
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+ """
+
+ Scenario: B.6. DS Child Zone No Data Error
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for example. type DS should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ """
+
+ #
+ # Below are additional tests, not explicitely stated in RFC5155
+ #
+
+ Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (closest encloser)
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for b.x.w.example. should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN NSEC3 1 1 12 aabbccdd gjeqe526plbf1g8mklp59enfd789njgi MX RRSIG
+ b4um86eghhds6nea196smvmlo4ors995.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 aabbccdd b4um86eghhds6nea196smvmlo4ors995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ """
+
+ Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (wildcard)
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for a.w.example. should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN NSEC3 1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+ k8udemvp1j2f7eg6jebps17vp3n8i58h.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+ """
+
+ Scenario: Wildcard other: Wildcard name itself
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for *.w.example. type MX should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 2
+ The last query response should have nscount 3
+ The last query response should have adcount 9
+ The answer section of the last query response should be
+ """
+ *.w.example. 3600 IN MX 1 ai.example.
+ *.w.example. 3600 IN RRSIG MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+ """
+ The authority section of the last query response should be
+ """
+ example. 3600 IN NS ns1.example.
+ example. 3600 IN NS ns2.example.
+ example. 3600 IN RRSIG NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+ """
+ The additional section of the last query response should be
+ """
+ ai.example. 3600 IN A 192.0.2.9
+ ai.example. 3600 IN AAAA 2001:db8::f00:baa9
+ ns1.example. 3600 IN A 192.0.2.1
+ ns2.example. 3600 IN A 192.0.2.2
+ ai.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ ai.example. 3600 IN RRSIG AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+ ns1.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ ns2.example. 3600 IN RRSIG A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+ """
+
+ Scenario: Wildcard other: Wildcard name itself nodata
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for *.w.example. type A should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN NSEC3 1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+ r53bq7cc2uvmubfu5ocmm6pers9tk9en.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+ """
+
+ Scenario: Direct query for NSEC3 record
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. type NSEC3 should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 8
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN NSEC3 1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG
+ q04jkcevqvmu85r014c7dkba38o0ji5r.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN NSEC3 1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ """
+
+ Scenario: No data, type DS, in-zone
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for ai.example. type DS should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 4
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN NSEC3 1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+ gjeqe526plbf1g8mklp59enfd789njgi.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ """
+
+ Scenario: No data, type DS, optout delegation
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for c.example. type DS should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 6
+ The last query response should have adcount 1
+ The authority section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+ example. 3600 IN RRSIG SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN NSEC3 1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+ 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN NSEC3 1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG
+ 35mthgpgcu1qg68fab165klnsnk3dpvl.example. 3600 IN RRSIG NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+ """
diff --git a/tests/lettuce/features/queries.feature b/tests/lettuce/features/queries.feature
new file mode 100644
index 0000000..8ca9467
--- /dev/null
+++ b/tests/lettuce/features/queries.feature
@@ -0,0 +1,146 @@
+Feature: Querying feature
+ This feature is a collection of non-specific querying tests;
+ for instance whether multiple queries in a row return consistent
+ answers.
+
+ Scenario: Repeated queries
+ Given I have bind10 running with configuration example.org.inmem.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for www.example.org should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 1
+ The last query response should have nscount 2
+ The last query response should have adcount 2
+
+ The answer section of the last query response should be
+ """
+ www.example.org. 3600 IN A 192.0.2.1
+ """
+ The authority section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns1.example.org. 3600 IN A 192.0.2.3
+ ns2.example.org. 3600 IN A 192.0.2.4
+ """
+
+ # Repeat of the above
+ A query for www.example.org should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 1
+ The last query response should have nscount 2
+ The last query response should have adcount 2
+
+ The answer section of the last query response should be
+ """
+ www.example.org. 3600 IN A 192.0.2.1
+ """
+ The authority section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns1.example.org. 3600 IN A 192.0.2.3
+ ns2.example.org. 3600 IN A 192.0.2.4
+ """
+
+ # And now query something completely different
+ A query for nosuchname.example.org should have rcode NXDOMAIN
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 0
+ The last query response should have nscount 1
+ The last query response should have adcount 0
+ The authority section of the last query response should be
+ """
+ example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+ """
+
+ Scenario: ANY query
+ Given I have bind10 running with configuration example.org.inmem.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for example.org type ANY should have rcode NOERROR
+ The last query response should have flags qr aa rd
+ The last query response should have ancount 4
+ The last query response should have nscount 0
+ The last query response should have adcount 3
+ The answer section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
+ example.org. 3600 IN MX 10 mail.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns1.example.org. 3600 IN A 192.0.2.3
+ ns2.example.org. 3600 IN A 192.0.2.4
+ mail.example.org. 3600 IN A 192.0.2.10
+ """
+ Scenario: Delegation query for unsigned child zone
+ Given I have bind10 running with configuration example.org.inmem.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ A dnssec query for www.sub.example.org type AAAA should have rcode NOERROR
+ The last query response should have flags qr rd
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 1
+ The last query response should have adcount 2
+ The authority section of the last query response should be
+ """
+ sub.example.org. 3600 IN NS ns.sub.example.org.
+ """
+ The additional section of the last query response should be
+ """
+ ns.sub.example.org. 3600 IN A 192.0.2.101
+ """
+ Scenario: SSHFP query
+ Given I have bind10 running with configuration example.org.inmem.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for example.org type SSHFP should have rcode NOERROR
+ The last query response should have ancount 0
+ A query for shell.example.org type SSHFP should have rcode NOERROR
+ The last query response should have ancount 1
+ The answer section of the last query response should be
+ """
+ shell.example.org. 3600 IN SSHFP 2 1 123456789abcdef67890123456789abcdef67890
+ """
diff --git a/tests/lettuce/features/resolver_basic.feature b/tests/lettuce/features/resolver_basic.feature
new file mode 100644
index 0000000..4092101
--- /dev/null
+++ b/tests/lettuce/features/resolver_basic.feature
@@ -0,0 +1,36 @@
+Feature: Basic Resolver
+ This feature set is just testing the execution of the b10-resolver
+ module. It sees whether it starts up, takes configuration, and
+ answers queries.
+
+ Scenario: Listen for and answer query
+ # This scenario starts a server that runs a real resolver.
+ # In order not to send out queries into the wild, we only
+ # query for something known to be hardcoded at this moment.
+ # NOTE: once real priming has been implemented, this test needs
+ # to be revised (as it would then leak, which is probably true
+ # for any resolver system test)
+ When I start bind10 with configuration resolver/resolver_basic.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message RESOLVER_STARTED
+
+ bind10 module Resolver should be running
+ And bind10 module Auth should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ # The ACL is set to reject any queries
+ A query for l.root-servers.net. should have rcode REFUSED
+
+ # Test whether acl ACCEPT works
+ When I set bind10 configuration Resolver/query_acl[0]/action to ACCEPT
+ # This address is currently hardcoded, so shouldn't cause outside traffic
+ A query for l.root-servers.net. should have rcode NOERROR
+
+ # Check whether setting the ACL to reject again works
+ When I set bind10 configuration Resolver/query_acl[0]/action to REJECT
+ A query for l.root-servers.net. should have rcode REFUSED
diff --git a/tests/lettuce/features/terrain/.gitignore b/tests/lettuce/features/terrain/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/tests/lettuce/features/terrain/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
new file mode 100644
index 0000000..a08a887
--- /dev/null
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -0,0 +1,365 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from lettuce import *
+import time
+import subprocess
+import re
+import json
+
+ at step('sleep for (\d+) seconds')
+def wait_seconds(step, seconds):
+ """Sleep for some seconds.
+ Parameters:
+ seconds number of seconds to sleep for.
+ """
+ time.sleep(float(seconds))
+
+ at step('start bind10(?: with configuration (\S+))?' +\
+ '(?: with cmdctl port (\d+))?' +\
+ '(?: with msgq socket file (\S+))?' +\
+ '(?: as (\S+))?')
+def start_bind10(step, config_file, cmdctl_port, msgq_sockfile, process_name):
+ """
+ Start BIND 10 with the given optional config file, cmdctl port, and
+ store the running process in world with the given process name.
+ Parameters:
+ config_file ('with configuration <file>', optional): this configuration
+ will be used. The path is relative to the base lettuce
+ directory.
+ cmdctl_port ('with cmdctl port <portnr>', optional): The port on which
+ b10-cmdctl listens for bindctl commands. Defaults to 47805.
+ msgq_sockfile ('with msgq socket file', optional): The msgq socket file
+ that will be used for internal communication
+ process_name ('as <name>', optional). This is the name that can be used
+ in the following steps of the scenario to refer to this
+ BIND 10 instance. Defaults to 'bind10'.
+ This call will block until BIND10_STARTUP_COMPLETE or BIND10_STARTUP_ERROR
+ is logged. In the case of the latter, or if it times out, the step (and
+ scenario) will fail.
+ It will also fail if there is a running process with the given process_name
+ already.
+ """
+ args = [ 'bind10', '-n', '-v' ]
+ if config_file is not None:
+ args.append('-p')
+ args.append("configurations/")
+ args.append('-c')
+ args.append(config_file)
+ if cmdctl_port is None:
+ args.append('--cmdctl-port=47805')
+ else:
+ args.append('--cmdctl-port=' + cmdctl_port)
+ if process_name is None:
+ process_name = "bind10"
+ else:
+ args.append('-m')
+ args.append(process_name + '_msgq.socket')
+
+ world.processes.add_process(step, process_name, args)
+
+ # check output to know when startup has been completed
+ (message, line) = world.processes.wait_for_stderr_str(process_name,
+ ["BIND10_STARTUP_COMPLETE",
+ "BIND10_STARTUP_ERROR"])
+ assert message == "BIND10_STARTUP_COMPLETE", "Got: " + str(line)
+
+ at step('wait for bind10 auth (?:of (\w+) )?to start')
+def wait_for_auth(step, process_name):
+ """Wait for b10-auth to run. This is done by blocking until the message
+ AUTH_SERVER_STARTED is logged.
+ Parameters:
+ process_name ('of <name', optional): The name of the BIND 10 instance
+ to wait for. Defaults to 'bind10'.
+ """
+ if process_name is None:
+ process_name = "bind10"
+ world.processes.wait_for_stderr_str(process_name, ['AUTH_SERVER_STARTED'],
+ False)
+
+ at step('wait for bind10 xfrout (?:of (\w+) )?to start')
+def wait_for_xfrout(step, process_name):
+ """Wait for b10-xfrout to run. This is done by blocking until the message
+ XFROUT_NEW_CONFIG_DONE is logged.
+ Parameters:
+ process_name ('of <name', optional): The name of the BIND 10 instance
+ to wait for. Defaults to 'bind10'.
+ """
+ if process_name is None:
+ process_name = "bind10"
+ world.processes.wait_for_stderr_str(process_name,
+ ['XFROUT_NEW_CONFIG_DONE'],
+ False)
+
+ at step('have bind10 running(?: with configuration ([\S]+))?' +\
+ '(?: with cmdctl port (\d+))?' +\
+ '(?: as ([\S]+))?')
+def have_bind10_running(step, config_file, cmdctl_port, process_name):
+ """
+ Compound convenience step for running bind10, which consists of
+ start_bind10.
+ Currently only supports the 'with configuration' option.
+ """
+ start_step = 'start bind10 with configuration ' + config_file
+ if cmdctl_port is not None:
+ start_step += ' with cmdctl port ' + str(cmdctl_port)
+ if process_name is not None:
+ start_step += ' as ' + process_name
+ step.given(start_step)
+
+# function to send lines to bindctl, and store the result
+def run_bindctl(commands, cmdctl_port=None):
+ """Run bindctl.
+ Parameters:
+ commands: a sequence of strings which will be sent.
+ cmdctl_port: a port number on which cmdctl is listening, is converted
+ to string if necessary. If not provided, or None, defaults
+ to 47805
+
+ bindctl's stdout and stderr streams are stored (as one multiline string
+ in world.last_bindctl_stdout/stderr.
+ Fails if the return code is not 0
+ """
+ if cmdctl_port is None:
+ cmdctl_port = 47805
+ args = ['bindctl', '-p', str(cmdctl_port)]
+ bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
+ subprocess.PIPE, None)
+ for line in commands:
+ bindctl.stdin.write(line + "\n")
+ (stdout, stderr) = bindctl.communicate()
+ result = bindctl.returncode
+ world.last_bindctl_stdout = stdout
+ world.last_bindctl_stderr = stderr
+ assert result == 0, "bindctl exit code: " + str(result) +\
+ "\nstdout:\n" + str(stdout) +\
+ "stderr:\n" + str(stderr)
+
+
+ at step('last bindctl( stderr)? output should( not)? contain (\S+)( exactly)?')
+def check_bindctl_output(step, stderr, notv, string, exactly):
+ """Checks the stdout (or stderr) stream of the last run of bindctl,
+ fails if the given string is not found in it (or fails if 'not' was
+ set and it is found
+ Parameters:
+ stderr ('stderr'): Check stderr instead of stdout output
+ notv ('not'): reverse the check (fail if string is found)
+ string ('contain <string>') string to look for
+ exactly ('exactly'): Make an exact match delimited by whitespace
+ """
+ if stderr is None:
+ output = world.last_bindctl_stdout
+ else:
+ output = world.last_bindctl_stderr
+ found = False
+ if exactly is None:
+ if string in output:
+ found = True
+ else:
+ if re.search(r'^\s+' + string + r'\s+', output, re.IGNORECASE | re.MULTILINE) is not None:
+ found = True
+ if notv is None:
+ assert found == True, "'" + string +\
+ "' was not found in bindctl output:\n" +\
+ output
+ else:
+ assert not found, "'" + string +\
+ "' was found in bindctl output:\n" +\
+ output
+
+def parse_bindctl_output_as_data_structure():
+ """Helper function for data-related command tests: evaluates the
+ last output of bindctl as a data structure that can then be
+ inspected.
+ If the bindctl output is not valid (json) data, this call will
+ fail with an assertion failure.
+ If it is valid, it is parsed and returned as whatever data
+ structure it represented.
+ """
+ # strip any extra output after a charater that commonly terminates a valid
+ # JSON expression, i.e., ']', '}' and '"'. (The extra output would
+ # contain 'Exit from bindctl' message, and depending on environment some
+ # other control-like characters...but why is this message even there?)
+ # Note that this filter is not perfect. For example, it cannot recognize
+ # a simple expression of true/false/null.
+ output = re.sub("(.*)([^]}\"]*$)", r"\1", world.last_bindctl_stdout)
+ try:
+ return json.loads(output)
+ except ValueError as ve:
+ assert False, "Last bindctl output does not appear to be a " +\
+ "parseable data structure: '" + output + "': " + str(ve)
+
+def find_process_pid(step, process_name):
+ """Helper function to request the running processes from Boss, and
+ return the pid of the process with the given process_name.
+ Fails with an assert if the response from boss is not valid JSON,
+ or if the process with the given name is not found.
+ """
+ # show_processes output is a list of lists, where the inner lists
+ # are of the form [ pid, "name" ]
+ # Not checking data form; errors will show anyway (if these turn
+ # out to be too vague, we can change this)
+ step.given('send bind10 the command Boss show_processes')
+ running_processes = parse_bindctl_output_as_data_structure()
+
+ for process in running_processes:
+ if process[1] == process_name:
+ return process[0]
+ assert False, "Process named " + process_name +\
+ " not found in output of Boss show_processes";
+
+ at step("remember the pid of process ([\S]+)")
+def remember_pid(step, process_name):
+ """Stores the PID of the process with the given name as returned by
+ Boss show_processes command.
+ Fails if the process with the given name does not appear to exist.
+ Stores the component_name->pid value in the dict world.process_pids.
+ This should only be used by the related step
+ 'the pid of process <name> should (not) have changed'
+ Arguments:
+ process name ('process <name>') the name of the component to store
+ the pid of.
+ """
+ if world.process_pids is None:
+ world.process_pids = {}
+ world.process_pids[process_name] = find_process_pid(step, process_name)
+
+ at step('pid of process ([\S]+) should not have changed')
+def check_pid(step, process_name):
+ """Checks the PID of the process with the given name as returned by
+ Boss show_processes command.
+ Fails if the process with the given name does not appear to exist.
+ Fails if the process with the given name exists, but has a different
+ pid than it had when the step 'remember the pid of process' was
+ called.
+ Fails if that step has not been called (since world.process_pids
+ does not exist).
+ """
+ assert world.process_pids is not None, "No process pids stored"
+ assert process_name in world.process_pids, "Process named " +\
+ process_name +\
+ " was not stored"
+ pid = find_process_pid(step, process_name)
+ assert world.process_pids[process_name] == pid,\
+ "Expected pid: " + str(world.process_pids[process_name]) +\
+ " Got pid: " + str(pid)
+
+ at step('set bind10 configuration (\S+) to (.*)(?: with cmdctl port (\d+))?')
+def config_set_command(step, name, value, cmdctl_port):
+ """
+ Run bindctl, set the given configuration to the given value, and commit it.
+ Parameters:
+ name ('configuration <name>'): Identifier of the configuration to set
+ value ('to <value>'): value to set it to.
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to. Defaults to 47805.
+ Fails if cmdctl does not exit with status code 0.
+ """
+ commands = ["config set " + name + " " + value,
+ "config commit",
+ "quit"]
+ run_bindctl(commands, cmdctl_port)
+
+ at step('send bind10 the following commands(?: with cmdctl port (\d+))?')
+def send_multiple_commands(step, cmdctl_port):
+ """
+ Run bindctl, and send it the given multiline set of commands.
+ A quit command is always appended.
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to. Defaults to 47805.
+ Fails if cmdctl does not exit with status code 0.
+ """
+ commands = step.multiline.split("\n")
+ # Always add quit
+ commands.append("quit")
+ run_bindctl(commands, cmdctl_port)
+
+ at step('remove bind10 configuration (\S+)(?: value (\S+))?(?: with cmdctl port (\d+))?')
+def config_remove_command(step, name, value, cmdctl_port):
+ """
+ Run bindctl, remove the given configuration item, and commit it.
+ Parameters:
+ name ('configuration <name>'): Identifier of the configuration to remove
+ value ('value <value>'): if name is a named set, use value to identify
+ item to remove
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to. Defaults to 47805.
+ Fails if cmdctl does not exit with status code 0.
+ """
+ cmd = "config remove " + name
+ if value is not None:
+ cmd = cmd + " " + value
+ commands = [cmd,
+ "config commit",
+ "quit"]
+ run_bindctl(commands, cmdctl_port)
+
+ at step('send bind10 the command (.+)(?: with cmdctl port (\d+))?')
+def send_command(step, command, cmdctl_port):
+ """
+ Run bindctl, send the given command, and exit bindctl.
+ Parameters:
+ command ('the command <command>'): The command to send.
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to. Defaults to 47805.
+ Fails if cmdctl does not exit with status code 0.
+ """
+ commands = [command,
+ "quit"]
+ run_bindctl(commands, cmdctl_port)
+
+ at step('bind10 module (\S+) should( not)? be running')
+def module_is_running(step, name, not_str):
+ """
+ Convenience step to check if a module is running; can only work with
+ default cmdctl port; sends a 'help' command with bindctl, then
+ checks if the output contains the given name.
+ Parameters:
+ name ('module <name>'): The name of the module (case sensitive!)
+ not ('not'): Reverse the check (fail if it is running)
+ """
+ if not_str is None:
+ not_str = ""
+ step.given('send bind10 the command help')
+ step.given('last bindctl output should' + not_str + ' contain ' + name + ' exactly')
+
+ at step('Configure BIND10 to run DDNS')
+def configure_ddns_on(step):
+ """
+ Convenience compound step to enable the b10-ddns module.
+ """
+ step.behave_as("""
+ When I send bind10 the following commands
+ \"\"\"
+ config add Boss/components b10-ddns
+ config set Boss/components/b10-ddns/kind dispensable
+ config set Boss/components/b10-ddns/address DDNS
+ config commit
+ \"\"\"
+ """)
+
+ at step('Configure BIND10 to stop running DDNS')
+def configure_ddns_off(step):
+ """
+ Convenience compound step to disable the b10-ddns module.
+ """
+ step.behave_as("""
+ When I send bind10 the following commands
+ \"\"\"
+ config remove Boss/components b10-ddns
+ config commit
+ \"\"\"
+ """)
+
diff --git a/tests/lettuce/features/terrain/nsupdate.py b/tests/lettuce/features/terrain/nsupdate.py
new file mode 100644
index 0000000..946439d
--- /dev/null
+++ b/tests/lettuce/features/terrain/nsupdate.py
@@ -0,0 +1,168 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from lettuce import *
+import subprocess
+import re
+
+def run_nsupdate(commands):
+ """Run nsupdate.
+ Parameters:
+ commands: a sequence of strings which will be sent.
+ update_address: adress to send the update to
+ update_port: port to send the update to
+ zone: zone to update
+
+ Appends 'send' and 'quit' as final commands.
+
+ nsupdate's stdout and stderr streams are stored (as one multiline string
+ in world.last_nsupdate_stdout/stderr.
+
+ The return code is stored in world.last_nsupdate_returncode
+ (it is not checked here, since a number of tests intentionally
+ result in a non-zero return code).
+ """
+ commands.append('send')
+ commands.append('quit')
+ args = ['nsupdate' ]
+ nsupdate = subprocess.Popen(args, 1, None, subprocess.PIPE,
+ subprocess.PIPE, subprocess.PIPE)
+ for line in commands:
+ nsupdate.stdin.write(line + "\n")
+ (stdout, stderr) = nsupdate.communicate()
+ world.last_nsupdate_returncode = nsupdate.returncode
+ world.last_nsupdate_stdout = stdout
+ world.last_nsupdate_stderr = stderr
+
+ at step('send a DDNS update for (\S+) with the following commands:')
+def send_multiple_commands(step, zone):
+ """
+ Run nsupdate, and send it the given multiline set of commands.
+ A send and quit command is always appended.
+
+ This is the most 'raw' wrapper around the nsupdate call; every
+ command except the final send needs to be specified. Intended
+ for those tests that have unique properties.
+ """
+ commands = step.multiline.split("\n")
+ run_nsupdate(commands, zone)
+
+ at step('DDNS response should be ([A-Z]+)')
+def check_ddns_response(step, response):
+ """
+ Checks the result of the last call to nsupdate.
+
+ If the given response argument is SUCCESS, it simply checks whether
+ the return code from nsupdate is 0 (there is no output in that case).
+ If not, it checks whether it is not 0, and if the given response string
+ matches a line 'update failed: <response>' in the stderr output of
+ nsupdate.
+
+ Prints exit code, stdout and stderr streams of nsupdate if it fails.
+ """
+ # For success, nsupdate is silent, only check result code 0
+ if response == "SUCCESS":
+ assert 0 == world.last_nsupdate_returncode,\
+ "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
+ "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
+ "stderr:\n" + str(world.last_nsupdate_stderr)
+ else:
+ found = False
+ for line in world.last_nsupdate_stderr.split('\n'):
+ if line == "update failed: " + response:
+ found = True
+ assert found and (0 != world.last_nsupdate_returncode),\
+ "Response " + response + " not found in nsupdate output\n" +\
+ "nsupdate exit code: " + str(world.last_nsupdate_returncode) +\
+ "\nstdout:\n" + str(world.last_nsupdate_stdout) +\
+ "stderr:\n" + str(world.last_nsupdate_stderr)
+
+
+# Individual steps to create a DDNS update packet through nsupdate
+ at step('Prepare a DDNS update(?: for (\S+))?(?: to (\S+)(?: port ([0-9]+)))?')
+def prepare_update(step, zone, server, port):
+ """
+ Prepares an nsupdate command that sets up an update to a server
+ for a zone. The update is not sent yet, but the commands
+ are stored in world.nsupdate_commands.
+ """
+ commands = []
+ if server is not None:
+ commands.append("server " + server)
+ else:
+ commands.append("server 127.0.0.1")
+ if port is not None:
+ commands[0] = commands[0] + " " + port
+ else:
+ commands[0] = commands[0] + " 47806"
+ if zone is not None:
+ commands.append("zone " + zone)
+ world.nsupdate_commands = commands
+
+ at step('Add to the DDNS update: (.*)')
+def add_line_to_ddns_update(step, line):
+ """
+ Adds a single line to the prepared nsupdate. It is not sent yet.
+ The line must conform to nsupdate syntax.
+ """
+ world.nsupdate_commands.append(line)
+
+ at step('Add the following lines to the DDNS update:')
+def add_lines_to_ddns_update(step, line):
+ """
+ Adds multiple lines to the prepared nsupdate. It is not sent yet.
+ The lines must conform to nsupdate syntax.
+ """
+ world.nsupdate_commands.extend(step.multiline.split('\n'))
+
+ at step('Send the DDNS update')
+def run_ddns_update(step):
+ """
+ Runs the prepared nsupdate, see run_nsupdate() for more information.
+ """
+ run_nsupdate(world.nsupdate_commands)
+
+ at step('use DDNS to set the SOA SERIAL to ([0-9]+)')
+def set_serial_to(step, new_serial):
+ """
+ Convenience compound step; prepare an update for example.org,
+ which sets the SERIAL to the given value, and send it.
+ It makes no other changes, and has hardcoded values for the other
+ SOA rdata fields.
+ """
+ step.given('Prepare a DDNS update')
+ step.given('add to the DDNS update: update add example.org 3600 IN SOA ns1.example.org. admin.example.org. ' + new_serial + ' 3600 1800 2419200 7200')
+ step.given('Send the DDNS update')
+
+ at step('use DDNS to add a record (.*)')
+def add_record(step, new_record):
+ """
+ Convenience compound step; prepare an update for example.org,
+ which adds one record, then send it.
+ Apart from the update addition, the update will not contain anything else.
+ """
+ step.given('Prepare a DDNS update')
+ step.given('add to the DDNS update: update add ' + new_record)
+ step.given('Send the DDNS update')
+
+ at step('set DDNS ACL ([0-9]+) for ([0-9.]+) to ([A-Z]+)')
+def set_ddns_acl_to(step, nr, address, action):
+ """
+ Convenience step to update a single ACL for DDNS.
+ Replaces the ACL at the given index for the given
+ address, to the given action
+ """
+ step.given('set bind10 configuration DDNS/zones[' + nr + ']/update_acl to [{"action": "' + action + '", "from": "' + address + '"}]')
+ step.given('last bindctl output should not contain Error')
diff --git a/tests/lettuce/features/terrain/querying.py b/tests/lettuce/features/terrain/querying.py
new file mode 100644
index 0000000..abd7c18
--- /dev/null
+++ b/tests/lettuce/features/terrain/querying.py
@@ -0,0 +1,338 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This script provides querying functionality
+# The most important step is
+#
+# query for <name> [type X] [class X] [to <addr>[:port]] should have rcode <rc>
+#
+# By default, it will send queries to 127.0.0.1:47806 unless specified
+# otherwise. The rcode is always checked. If the result is not NO_ANSWER,
+# the result will be stored in last_query_result, which can then be inspected
+# more closely, for instance with the step
+#
+# "the last query response should have <property> <value>"
+#
+# Also see example.feature for some examples
+
+from lettuce import *
+import subprocess
+import re
+
+#
+# define a class to easily access different parts
+# We may consider using our full library for this, but for now
+# simply store several parts of the response as text values in
+# this structure.
+# (this actually has the advantage of not relying on our own libraries
+# to test our own, well, libraries)
+#
+# The following attributes are 'parsed' from the response, all as strings,
+# and end up as direct attributes of the QueryResult object:
+# opcode, rcode, id, flags, qdcount, ancount, nscount, adcount,
+# edns_version, edns_flags, and edns_udp_size
+# (flags and edns_flags are both one string with all flags, in the order
+# in which they appear in the response message.)
+#
+# this will set 'rcode' as the result code, we 'define' one additional
+# rcode, "NO_ANSWER", if the dig process returned an error code itself
+# In this case none of the other attributes will be set.
+#
+# The different sections will be lists of strings, one for each RR in the
+# section. The question section will start with ';', as per dig output
+#
+# See server_from_sqlite3.feature for various examples to perform queries
+class QueryResult(object):
+ status_re = re.compile("opcode: ([A-Z])+, status: ([A-Z]+), id: ([0-9]+)")
+ edns_re = re.compile("; EDNS: version: ([0-9]+), flags: ([a-z ]*); udp: ([0-9]+)")
+ flags_re = re.compile("flags: ([a-z ]+); QUERY: ([0-9]+), ANSWER: " +
+ "([0-9]+), AUTHORITY: ([0-9]+), ADDITIONAL: ([0-9]+)")
+
+ def __init__(self, name, qtype, qclass, address, port,
+ additional_args=None):
+ """
+ Constructor. This fires of a query using dig.
+ Parameters:
+ name: The domain name to query
+ qtype: The RR type to query. Defaults to A if it is None.
+ qclass: The RR class to query. Defaults to IN if it is None.
+ address: The IP adress to send the query to.
+ port: The port number to send the query to.
+ additional_args: List of additional arguments (e.g. '+dnssec').
+ All parameters must be either strings or have the correct string
+ representation.
+ Only one query attempt will be made.
+ """
+ args = [ 'dig', '+tries=1', '@' + str(address), '-p', str(port) ]
+ if qtype is not None:
+ args.append('-t')
+ args.append(str(qtype))
+ if qclass is not None:
+ args.append('-c')
+ args.append(str(qclass))
+ if additional_args is not None:
+ args.extend(additional_args)
+ args.append(name)
+ dig_process = subprocess.Popen(args, 1, None, None, subprocess.PIPE,
+ None)
+ result = dig_process.wait()
+ if result != 0:
+ self.rcode = "NO_ANSWER"
+ else:
+ self.rcode = None
+ parsing = "HEADER"
+ self.question_section = []
+ self.answer_section = []
+ self.authority_section = []
+ self.additional_section = []
+ self.line_handler = self.parse_header
+ for out in dig_process.stdout:
+ self.line_handler(out)
+
+ def _check_next_header(self, line):
+ """
+ Returns true if we found a next header, and sets the internal
+ line handler to the appropriate value.
+ """
+ if line == ";; ANSWER SECTION:\n":
+ self.line_handler = self.parse_answer
+ elif line == ";; OPT PSEUDOSECTION:\n":
+ self.line_handler = self.parse_opt
+ elif line == ";; AUTHORITY SECTION:\n":
+ self.line_handler = self.parse_authority
+ elif line == ";; ADDITIONAL SECTION:\n":
+ self.line_handler = self.parse_additional
+ elif line.startswith(";; Query time"):
+ self.line_handler = self.parse_footer
+ else:
+ return False
+ return True
+
+ def parse_header(self, line):
+ """
+ Parse the header lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ if not self._check_next_header(line):
+ status_match = self.status_re.search(line)
+ flags_match = self.flags_re.search(line)
+ if status_match is not None:
+ self.opcode = status_match.group(1)
+ self.rcode = status_match.group(2)
+ elif flags_match is not None:
+ self.flags = flags_match.group(1)
+ self.qdcount = flags_match.group(2)
+ self.ancount = flags_match.group(3)
+ self.nscount = flags_match.group(4)
+ self.adcount = flags_match.group(5)
+
+ def parse_opt(self, line):
+ """
+ Parse the header lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ if not self._check_next_header(line):
+ edns_match = self.edns_re.search(line)
+ if edns_match is not None:
+ self.edns_version = edns_match.group(1)
+ self.edns_flags = edns_match.group(2)
+ self.edns_udp_size = edns_match.group(3)
+
+ def parse_question(self, line):
+ """
+ Parse the question section lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ if not self._check_next_header(line):
+ if line != "\n":
+ self.question_section.append(line.strip())
+
+ def parse_answer(self, line):
+ """
+ Parse the answer section lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ if not self._check_next_header(line):
+ if line != "\n":
+ self.answer_section.append(line.strip())
+
+ def parse_authority(self, line):
+ """
+ Parse the authority section lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ if not self._check_next_header(line):
+ if line != "\n":
+ self.authority_section.append(line.strip())
+
+ def parse_additional(self, line):
+ """
+ Parse the additional section lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ if not self._check_next_header(line):
+ if line != "\n":
+ self.additional_section.append(line.strip())
+
+ def parse_footer(self, line):
+ """
+ Parse the footer lines of the query response.
+ Parameters:
+ line: The current line of the response.
+ """
+ pass
+
+ at step('A (dnssec )?query for ([\S]+) (?:type ([A-Z0-9]+) )?' +
+ '(?:class ([A-Z]+) )?(?:to ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))? )?' +
+ 'should have rcode ([\w.]+)')
+def query(step, dnssec, query_name, qtype, qclass, addr, port, rcode):
+ """
+ Run a query, check the rcode of the response, and store the query
+ result in world.last_query_result.
+ Parameters:
+ query_name ('query for <name>'): The domain name to query.
+ qtype ('type <type>', optional): The RR type to query. Defaults to A.
+ qclass ('class <class>', optional): The RR class to query. Defaults to IN.
+ addr ('to <address>', optional): The IP address of the nameserver to query.
+ Defaults to 127.0.0.1.
+ port (':<port>', optional): The port number of the nameserver to query.
+ Defaults to 47806.
+ rcode ('should have rcode <rcode>'): The expected rcode of the answer.
+ """
+ if qtype is None:
+ qtype = "A"
+ if qclass is None:
+ qclass = "IN"
+ if addr is None:
+ addr = "127.0.0.1"
+ addr = re.sub(r"\[(.+)\]", r"\1", addr) # convert [IPv6_addr] to IPv6_addr
+ if port is None:
+ port = 47806
+ additional_arguments = []
+ if dnssec is not None:
+ additional_arguments.append("+dnssec")
+ else:
+ # some builds of dig add edns0 by default. This could muck up
+ # additional counts, so unless we need dnssec, explicitly
+ # disable edns0
+ additional_arguments.append("+noedns")
+ query_result = QueryResult(query_name, qtype, qclass, addr, port,
+ additional_arguments)
+ assert query_result.rcode == rcode,\
+ "Expected: " + rcode + ", got " + query_result.rcode
+ world.last_query_result = query_result
+
+ at step('The SOA serial for ([\S.]+) (?:at (\S+)(?::([0-9]+)) )?should be ([0-9]+)')
+def query_soa(step, query_name, address, port, serial=None):
+ """
+ Convenience function to check the SOA SERIAL value of the given zone at
+ the nameserver at the default address (127.0.0.1:47806).
+ Parameters:
+ query_name ('for <name>'): The zone to find the SOA record for.
+ serial ('should be <number>'): The expected value of the SOA SERIAL.
+ If the rcode is not NOERROR, or the answer section does not contain the
+ SOA record, this step fails.
+ """
+ if address is None:
+ address = "127.0.0.1"
+ if port is None:
+ port = "47806"
+ query_result = QueryResult(query_name, "SOA", "IN", address, port)
+ assert "NOERROR" == query_result.rcode,\
+ "Got " + query_result.rcode + ", expected NOERROR"
+ assert len(query_result.answer_section) == 1,\
+ "Too few or too many answers in SOA response"
+ soa_parts = query_result.answer_section[0].split()
+ assert serial == soa_parts[6],\
+ "Got SOA serial " + soa_parts[6] + ", expected " + serial
+
+ at step('last query response should have (\S+) (.+)')
+def check_last_query(step, item, value):
+ """
+ Check a specific value in the reponse from the last successful query sent.
+ Parameters:
+ item: The item to check the value of
+ value: The expected value.
+ This performs a very simple direct string comparison of the QueryResult
+ member with the given item name and the given value.
+ Fails if the item is unknown, or if its value does not match the expected
+ value.
+ """
+ assert world.last_query_result is not None
+ assert item in world.last_query_result.__dict__
+ lq_val = world.last_query_result.__dict__[item]
+ assert str(value) == str(lq_val),\
+ "Got: " + str(lq_val) + ", expected: " + str(value)
+
+ at step('([a-zA-Z]+) section of the last query response should be')
+def check_last_query_section(step, section):
+ """
+ Check the entire contents of the given section of the response of the last
+ query.
+ Parameters:
+ section ('<section> section'): The name of the section (QUESTION, ANSWER,
+ AUTHORITY or ADDITIONAL).
+ The expected response is taken from the multiline part of the step in the
+ scenario. Differing whitespace is ignored, the order of the lines is
+ ignored, and the comparison is case insensitive.
+ Fails if they do not match.
+ WARNING: Case insensitivity is not strictly correct; for instance the
+ data of TXT RRs would be case sensitive. But most other output is, so
+ currently the checks are always case insensitive. Should we decide
+ these checks do need to be case sensitive, we can either remove it
+ or make it optional (for the former, we'll need to update a number of
+ tests).
+ """
+ response_string = None
+ if section.lower() == 'question':
+ response_string = "\n".join(world.last_query_result.question_section)
+ elif section.lower() == 'answer':
+ response_string = "\n".join(world.last_query_result.answer_section)
+ elif section.lower() == 'authority':
+ response_string = "\n".join(world.last_query_result.authority_section)
+ elif section.lower() == 'additional':
+ response_string = "\n".join(world.last_query_result.additional_section)
+ else:
+ assert False, "Unknown section " + section
+
+ # Now mangle the data for 'conformance'
+ # This could be done more efficiently, but is done one
+ # by one on a copy of the original data, so it is clear
+ # what is done. Final error output is currently still the
+ # original unchanged multiline strings
+
+ # replace whitespace of any length by one space
+ response_string = re.sub("[ \t]+", " ", response_string)
+ expect = re.sub("[ \t]+", " ", step.multiline)
+ # lowercase them
+ response_string = response_string.lower()
+ expect = expect.lower()
+ # sort them
+ response_string_parts = response_string.split("\n")
+ response_string_parts.sort()
+ response_string = "\n".join(response_string_parts)
+ expect_parts = expect.split("\n")
+ expect_parts.sort()
+ expect = "\n".join(expect_parts)
+
+ assert response_string.strip() == expect.strip(),\
+ "Got:\n'" + response_string + "'\nExpected:\n'" + step.multiline +"'"
+
diff --git a/tests/lettuce/features/terrain/steps.py b/tests/lettuce/features/terrain/steps.py
new file mode 100644
index 0000000..8df0bae
--- /dev/null
+++ b/tests/lettuce/features/terrain/steps.py
@@ -0,0 +1,91 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#
+# This file contains a number of common steps that are general and may be used
+# By a lot of feature files.
+#
+
+from lettuce import *
+import os
+
+ at step('stop process (\w+)')
+def stop_a_named_process(step, process_name):
+ """
+ Stop the process with the given name.
+ Parameters:
+ process_name ('process <name>'): Name of the process to stop.
+ """
+ world.processes.stop_process(process_name)
+
+ at step('wait (?:(\d+) times )?for (new )?(\w+) stderr message (\w+)(?: not (\w+))?')
+def wait_for_stderr_message(step, times, new, process_name, message, not_message):
+ """
+ Block until the given message is printed to the given process's stderr
+ output.
+ Parameter:
+ times: Check for the string this many times.
+ new: (' new', optional): Only check the output printed since last time
+ this step was used for this process.
+ process_name ('<name> stderr'): Name of the process to check the output of.
+ message ('message <message>'): Output (part) to wait for.
+ not_message ('not <message>'): Output (part) to wait for, and fail
+ Fails if the message is not found after 10 seconds.
+ """
+ strings = [message]
+ if not_message is not None:
+ strings.append(not_message)
+ if times is None:
+ times = 1
+ (found, line) = world.processes.wait_for_stderr_str(process_name, strings, new, int(times))
+ if not_message is not None:
+ assert found != not_message, line
+
+ at step('wait (?:(\d+) times )?for (new )?(\w+) stdout message (\w+)(?: not (\w+))?')
+def wait_for_stdout_message(step, times, new, process_name, message, not_message):
+ """
+ Block until the given message is printed to the given process's stdout
+ output.
+ Parameter:
+ times: Check for the string this many times.
+ new: (' new', optional): Only check the output printed since last time
+ this step was used for this process.
+ process_name ('<name> stderr'): Name of the process to check the output of.
+ message ('message <message>'): Output (part) to wait for, and succeed.
+ not_message ('not <message>'): Output (part) to wait for, and fail
+ Fails if the message is not found after 10 seconds.
+ """
+ strings = [message]
+ if not_message is not None:
+ strings.append(not_message)
+ if times is None:
+ times = 1
+ (found, line) = world.processes.wait_for_stdout_str(process_name, strings, new, int(times))
+ if not_message is not None:
+ assert found != not_message, line
+
+ at step('the file (\S+) should (not )?exist')
+def check_existence(step, file_name, should_not_exist):
+ """
+ Check the existence of the given file.
+ Parameters:
+ file_name ('file <name>'): File to check existence of.
+ should_not_exist ('not', optional): Whether it should or should not exist.
+ Fails if the file should exist and does not, or vice versa.
+ """
+ if should_not_exist is None:
+ assert os.path.exists(file_name), file_name + " does not exist"
+ else:
+ assert not os.path.exists(file_name), file_name + " exists"
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
new file mode 100644
index 0000000..a35d0de
--- /dev/null
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -0,0 +1,406 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#
+# This is the 'terrain' in which the lettuce lives. By convention, this is
+# where global setup and teardown is defined.
+#
+# We declare some attributes of the global 'world' variables here, so the
+# tests can safely assume they are present.
+#
+# We also use it to provide scenario invariants, such as resetting data.
+#
+
+from lettuce import *
+import subprocess
+import os.path
+import shutil
+import re
+import sys
+import time
+
+# In order to make sure we start all tests with a 'clean' environment,
+# We perform a number of initialization steps, like restoring configuration
+# files, and removing generated data files.
+
+# This approach may not scale; if so we should probably provide specific
+# initialization steps for scenarios. But until that is shown to be a problem,
+# It will keep the scenarios cleaner.
+
+# This is a list of files that are freshly copied before each scenario
+# The first element is the original, the second is the target that will be
+# used by the tests that need them
+copylist = [
+ ["configurations/bindctl_commands.config.orig",
+ "configurations/bindctl_commands.config"],
+ ["configurations/example.org.config.orig",
+ "configurations/example.org.config"],
+ ["configurations/bindctl/bindctl.config.orig",
+ "configurations/bindctl/bindctl.config"],
+ ["configurations/resolver/resolver_basic.config.orig",
+ "configurations/resolver/resolver_basic.config"],
+ ["configurations/multi_instance/multi_auth.config.orig",
+ "configurations/multi_instance/multi_auth.config"],
+ ["configurations/ddns/ddns.config.orig",
+ "configurations/ddns/ddns.config"],
+ ["configurations/ddns/noddns.config.orig",
+ "configurations/ddns/noddns.config"],
+ ["data/inmem-xfrin.sqlite3.orig",
+ "data/inmem-xfrin.sqlite3"],
+ ["data/ddns/example.org.sqlite3.orig",
+ "data/ddns/example.org.sqlite3"]
+]
+
+# This is a list of files that, if present, will be removed before a scenario
+removelist = [
+"data/test_nonexistent_db.sqlite3"
+]
+
+# When waiting for output data of a running process, use OUTPUT_WAIT_INTERVAL
+# as the interval in which to check again if it has not been found yet.
+# If we have waited OUTPUT_WAIT_MAX_INTERVALS times, we will abort with an
+# error (so as not to hang indefinitely)
+OUTPUT_WAIT_INTERVAL = 0.5
+OUTPUT_WAIT_MAX_INTERVALS = 20
+
+# class that keeps track of one running process and the files
+# we created for it.
+class RunningProcess:
+ def __init__(self, step, process_name, args):
+ # set it to none first so destructor won't error if initializer did
+ """
+ Initialize the long-running process structure, and start the process.
+ Parameters:
+ step: The scenario step it was called from. This is used for
+ determining the output files for redirection of stdout
+ and stderr.
+ process_name: The name to refer to this running process later.
+ args: Array of arguments to pass to Popen().
+ """
+ self.process = None
+ self.step = step
+ self.process_name = process_name
+ self.remove_files_on_exit = True
+ self._check_output_dir()
+ self._create_filenames()
+ self._start_process(args)
+
+ def _start_process(self, args):
+ """
+ Start the process.
+ Parameters:
+ args:
+ Array of arguments to pass to Popen().
+ """
+ stderr_write = open(self.stderr_filename, "w")
+ stdout_write = open(self.stdout_filename, "w")
+ self.process = subprocess.Popen(args, 1, None, subprocess.PIPE,
+ stdout_write, stderr_write)
+ # open them again, this time for reading
+ self.stderr = open(self.stderr_filename, "r")
+ self.stdout = open(self.stdout_filename, "r")
+
+ def mangle_filename(self, filebase, extension):
+ """
+ Remove whitespace and non-default characters from a base string,
+ and return the substituted value. Whitespace is replaced by an
+ underscore. Any other character that is not an ASCII letter, a
+ number, a dot, or a hyphen or underscore is removed.
+ Parameter:
+ filebase: The string to perform the substitution and removal on
+ extension: An extension to append to the result value
+ Returns the modified filebase with the given extension
+ """
+ filebase = re.sub("\s+", "_", filebase)
+ filebase = re.sub("[^a-zA-Z0-9.\-_]", "", filebase)
+ return filebase + "." + extension
+
+ def _check_output_dir(self):
+ # We may want to make this overridable by the user, perhaps
+ # through an environment variable. Since we currently expect
+ # lettuce to be run from our lettuce dir, we shall just use
+ # the relative path 'output/'
+ """
+ Make sure the output directory for stdout/stderr redirection
+ exists.
+ Fails if it exists but is not a directory, or if it does not
+ and we are unable to create it.
+ """
+ self._output_dir = os.getcwd() + os.sep + "output"
+ if not os.path.exists(self._output_dir):
+ os.mkdir(self._output_dir)
+ assert os.path.isdir(self._output_dir),\
+ self._output_dir + " is not a directory."
+
+ def _create_filenames(self):
+ """
+ Derive the filenames for stdout/stderr redirection from the
+ feature, scenario, and process name. The base will be
+ "<Feature>-<Scenario>-<process name>.[stdout|stderr]"
+ """
+ filebase = self.step.scenario.feature.name + "-" +\
+ self.step.scenario.name + "-" + self.process_name
+ self.stderr_filename = self._output_dir + os.sep +\
+ self.mangle_filename(filebase, "stderr")
+ self.stdout_filename = self._output_dir + os.sep +\
+ self.mangle_filename(filebase, "stdout")
+
+ def stop_process(self):
+ """
+ Stop this process by calling terminate(). Blocks until process has
+ exited. If remove_files_on_exit is True, redirected output files
+ are removed.
+ """
+ if self.process is not None:
+ self.process.terminate()
+ self.process.wait()
+ self.process = None
+ if self.remove_files_on_exit:
+ self._remove_files()
+
+ def _remove_files(self):
+ """
+ Remove the files created for redirection of stdout/stderr output.
+ """
+ os.remove(self.stderr_filename)
+ os.remove(self.stdout_filename)
+
+ def _wait_for_output_str(self, filename, running_file, strings, only_new, matches = 1):
+ """
+ Wait for a line of output in this process. This will (if only_new is
+ False) first check all previous output from the process, and if not
+ found, check all output since the last time this method was called.
+ For each line in the output, the given strings array is checked. If
+ any output lines checked contains one of the strings in the strings
+ array, that string (not the line!) is returned.
+ Parameters:
+ filename: The filename to read previous output from, if applicable.
+ running_file: The open file to read new output from.
+ strings: Array of strings to look for.
+ only_new: If true, only check output since last time this method was
+ called. If false, first check earlier output.
+ matches: Check for the string this many times.
+ Returns a tuple containing the matched string, and the complete line
+ it was found in.
+ Fails if none of the strings was read after 10 seconds
+ (OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
+ """
+ match_count = 0
+ if not only_new:
+ full_file = open(filename, "r")
+ for line in full_file:
+ for string in strings:
+ if line.find(string) != -1:
+ match_count += 1
+ if match_count >= matches:
+ full_file.close()
+ return (string, line)
+ wait_count = 0
+ while wait_count < OUTPUT_WAIT_MAX_INTERVALS:
+ where = running_file.tell()
+ line = running_file.readline()
+ if line:
+ for string in strings:
+ if line.find(string) != -1:
+ match_count += 1
+ if match_count >= matches:
+ return (string, line)
+ else:
+ wait_count += 1
+ time.sleep(OUTPUT_WAIT_INTERVAL)
+ running_file.seek(where)
+ assert False, "Timeout waiting for process output: " + str(strings)
+
+ def wait_for_stderr_str(self, strings, only_new = True, matches = 1):
+ """
+ Wait for one of the given strings in this process's stderr output.
+ Parameters:
+ strings: Array of strings to look for.
+ only_new: If true, only check output since last time this method was
+ called. If false, first check earlier output.
+ matches: Check for the string this many times.
+ Returns a tuple containing the matched string, and the complete line
+ it was found in.
+ Fails if none of the strings was read after 10 seconds
+ (OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
+ """
+ return self._wait_for_output_str(self.stderr_filename, self.stderr,
+ strings, only_new, matches)
+
+ def wait_for_stdout_str(self, strings, only_new = True, matches = 1):
+ """
+ Wait for one of the given strings in this process's stdout output.
+ Parameters:
+ strings: Array of strings to look for.
+ only_new: If true, only check output since last time this method was
+ called. If false, first check earlier output.
+ matches: Check for the string this many times.
+ Returns a tuple containing the matched string, and the complete line
+ it was found in.
+ Fails if none of the strings was read after 10 seconds
+ (OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
+ """
+ return self._wait_for_output_str(self.stdout_filename, self.stdout,
+ strings, only_new, matches)
+
+# Container class for a number of running processes
+# i.e. servers like bind10, etc
+# one-shot programs like dig or bindctl are started and closed separately
+class RunningProcesses:
+ def __init__(self):
+ """
+ Initialize with no running processes.
+ """
+ self.processes = {}
+
+ def add_process(self, step, process_name, args):
+ """
+ Start a process with the given arguments, and store it under the given
+ name.
+ Parameters:
+ step: The scenario step it was called from. This is used for
+ determining the output files for redirection of stdout
+ and stderr.
+ process_name: The name to refer to this running process later.
+ args: Array of arguments to pass to Popen().
+ Fails if a process with the given name is already running.
+ """
+ assert process_name not in self.processes,\
+ "Process " + process_name + " already running"
+ self.processes[process_name] = RunningProcess(step, process_name, args)
+
+ def get_process(self, process_name):
+ """
+ Return the Process with the given process name.
+ Parameters:
+ process_name: The name of the process to return.
+ Fails if the process is not running.
+ """
+ assert process_name in self.processes,\
+ "Process " + name + " unknown"
+ return self.processes[process_name]
+
+ def stop_process(self, process_name):
+ """
+ Stop the Process with the given process name.
+ Parameters:
+ process_name: The name of the process to return.
+ Fails if the process is not running.
+ """
+ assert process_name in self.processes,\
+ "Process " + name + " unknown"
+ self.processes[process_name].stop_process()
+ del self.processes[process_name]
+
+ def stop_all_processes(self):
+ """
+ Stop all running processes.
+ """
+ for process in self.processes.values():
+ process.stop_process()
+
+ def keep_files(self):
+ """
+ Keep the redirection files for stdout/stderr output of all processes
+ instead of removing them when they are stopped later.
+ """
+ for process in self.processes.values():
+ process.remove_files_on_exit = False
+
+ def wait_for_stderr_str(self, process_name, strings, only_new = True, matches = 1):
+ """
+ Wait for one of the given strings in the given process's stderr output.
+ Parameters:
+ process_name: The name of the process to check the stderr output of.
+ strings: Array of strings to look for.
+ only_new: If true, only check output since last time this method was
+ called. If false, first check earlier output.
+ matches: Check for the string this many times.
+ Returns the matched string.
+ Fails if none of the strings was read after 10 seconds
+ (OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
+ Fails if the process is unknown.
+ """
+ assert process_name in self.processes,\
+ "Process " + process_name + " unknown"
+ return self.processes[process_name].wait_for_stderr_str(strings,
+ only_new,
+ matches)
+
+ def wait_for_stdout_str(self, process_name, strings, only_new = True, matches = 1):
+ """
+ Wait for one of the given strings in the given process's stdout output.
+ Parameters:
+ process_name: The name of the process to check the stdout output of.
+ strings: Array of strings to look for.
+ only_new: If true, only check output since last time this method was
+ called. If false, first check earlier output.
+ matches: Check for the string this many times.
+ Returns the matched string.
+ Fails if none of the strings was read after 10 seconds
+ (OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
+ Fails if the process is unknown.
+ """
+ assert process_name in self.processes,\
+ "Process " + process_name + " unknown"
+ return self.processes[process_name].wait_for_stdout_str(strings,
+ only_new,
+ matches)
+
+ at before.each_scenario
+def initialize(scenario):
+ """
+ Global initialization for each scenario.
+ """
+ # Keep track of running processes
+ world.processes = RunningProcesses()
+
+ # Convenience variable to access the last query result from querying.py
+ world.last_query_result = None
+
+ # For slightly better errors, initialize a process_pids for the relevant
+ # steps
+ world.process_pids = None
+
+ # Some tests can modify the settings. If the tests fail half-way, or
+ # don't clean up, this can leave configurations or data in a bad state,
+ # so we copy them from originals before each scenario
+ for item in copylist:
+ shutil.copy(item[0], item[1])
+
+ for item in removelist:
+ if os.path.exists(item):
+ os.remove(item)
+
+ at after.each_scenario
+def cleanup(scenario):
+ """
+ Global cleanup for each scenario.
+ """
+ # Keep output files if the scenario failed
+ if not scenario.passed:
+ world.processes.keep_files()
+ # Stop any running processes we may have had around
+ world.processes.stop_all_processes()
+
+# Environment check
+# Checks if LETTUCE_SETUP_COMPLETED is set in the environment
+# If not, abort with an error to use the run-script
+if 'LETTUCE_SETUP_COMPLETED' not in os.environ:
+ print("Environment check failure; LETTUCE_SETUP_COMPLETED not set")
+ print("Please use the run_lettuce.sh script. If you want to test an")
+ print("installed version of bind10 with these tests, use")
+ print("run_lettuce.sh -I [lettuce arguments]")
+ sys.exit(1)
diff --git a/tests/lettuce/features/terrain/transfer.py b/tests/lettuce/features/terrain/transfer.py
new file mode 100644
index 0000000..4ba45b8
--- /dev/null
+++ b/tests/lettuce/features/terrain/transfer.py
@@ -0,0 +1,140 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This script provides transfer (ixfr/axfr) test functionality
+# It provides steps to perform the client side of a transfer,
+# and inspect the results.
+#
+# Like querying.py, it uses dig to do the transfers, and
+# places its output in a result structure
+#
+# This is done in a different file with different steps than
+# querying, because the format of dig's output is
+# very different than that of normal queries
+
+from lettuce import *
+import subprocess
+import re
+
+class TransferResult(object):
+ """This object stores transfer results, which is essentially simply
+ a list of RR strings. These are stored, as read from dig's output,
+ in the list 'records'. So for an IXFR transfer it contains
+ the exact result as returned by the server.
+ If this list is empty, the transfer failed for some reason (dig
+ does not really show error results well, unfortunately).
+ We may add some smarter inspection functionality to this class
+ later.
+ """
+ def __init__(self, args):
+ """Perform the transfer by calling dig, and store the results.
+ args is the array of arguments to pass to Popen(), this
+ is passed as is since for IXFR and AXFR there can be very
+ different options"""
+ self.records = []
+
+ # Technically, using a pipe here can fail; since we don't expect
+ # large output right now, this works, but should we get a test
+ # where we do have a lot of output, this could block, and we will
+ # need to read the output in a different way.
+ dig_process = subprocess.Popen(args, 1, None, None, subprocess.PIPE,
+ None)
+ result = dig_process.wait()
+ assert result == 0
+ for l in dig_process.stdout:
+ line = l.strip()
+ if len(line) > 0 and line[0] != ';':
+ self.records.append(line)
+
+ at step('An AXFR transfer of ([\w.]+)(?: from ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
+def perform_axfr(step, zone_name, address, port):
+ """
+ Perform an AXFR transfer, and store the result as an instance of
+ TransferResult in world.transfer_result.
+
+ Step definition:
+ An AXFR transfer of <zone_name> [from <address>:<port>]
+
+ Address defaults to 127.0.0.1
+ Port defaults to 47806
+ """
+ if address is None:
+ address = "127.0.0.1"
+ # convert [IPv6_addr] to IPv6_addr:
+ address = re.sub(r"\[(.+)\]", r"\1", address)
+ if port is None:
+ port = 47806
+ args = [ 'dig', 'AXFR', '@' + str(address), '-p', str(port), zone_name ]
+ world.transfer_result = TransferResult(args)
+
+ at step('An IXFR transfer of ([\w.]+) (\d+)(?: from ([^:]+)(?::([0-9]+))?)?(?: over (tcp|udp))?')
+def perform_ixfr(step, zone_name, serial, address, port, protocol):
+ """
+ Perform an IXFR transfer, and store the result as an instance of
+ TransferResult in world.transfer_result.
+
+ Step definition:
+ An IXFR transfer of <zone_name> <serial> [from <address>:port] [over <tcp|udp>]
+
+ Address defaults to 127.0.0.1
+ Port defaults to 47806
+ If either tcp or udp is specified, only this protocol will be used.
+ """
+ if address is None:
+ address = "127.0.0.1"
+ if port is None:
+ port = 47806
+ args = [ 'dig', 'IXFR=' + str(serial), '@' + str(address), '-p', str(port), zone_name ]
+ if protocol is not None:
+ assert protocol == 'tcp' or protocol == 'udp', "Unknown protocol: " + protocol
+ if protocol == 'tcp':
+ args.append('+tcp')
+ elif protocol == 'udp':
+ args.append('+notcp')
+ world.transfer_result = TransferResult(args)
+
+ at step('transfer result should have (\d+) rrs?')
+def check_transfer_result_count(step, number_of_rrs):
+ """
+ Check the number of rrs in the transfer result object created by
+ the AXFR transfer or IXFR transfer step.
+
+ Step definition:
+ transfer result should have <number> rr[s]
+
+ Fails if the number of RRs is not equal to number
+ """
+ assert int(number_of_rrs) == len(world.transfer_result.records),\
+ "Got " + str(len(world.transfer_result.records)) +\
+ " records, expected " + str(number_of_rrs)
+
+ at step('full result of the last transfer should be')
+def check_full_transfer_result(step):
+ """
+ Check the complete output from the last transfer call.
+
+ Step definition:
+ full result of the last transfer should be <multiline value>
+
+ Whitespace is normalized in both the multiline value and the
+ output, but the order of the output is not.
+ Fails if there is any difference between the two. Prints
+ full output and expected value upon failure.
+ """
+ records_string = "\n".join(world.transfer_result.records)
+ records_string = re.sub("[ \t]+", " ", records_string)
+ expect = re.sub("[ \t]+", " ", step.multiline)
+ assert records_string.strip() == expect.strip(),\
+ "Got:\n'" + records_string + "'\nExpected:\n'" + expect + "'"
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
new file mode 100644
index 0000000..27dfb83
--- /dev/null
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -0,0 +1,39 @@
+Feature: Xfrin
+ Tests for Xfrin, specific for BIND 10 behaviour.
+
+ Scenario: Retransfer command
+ # Standard check to test (non-)existence of a file.
+ # This file is actually automatically created.
+ The file data/test_nonexistent_db.sqlite3 should not exist
+
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+
+ And I have bind10 running with configuration xfrin/retransfer_slave.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ # Now we use the first step again to see if the file has been created
+ The file data/test_nonexistent_db.sqlite3 should exist
+
+ A query for www.example.org should have rcode REFUSED
+ When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
+ Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ A query for www.example.org should have rcode NOERROR
+
+ # The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
+ # The following check will get these by AXFR, so the total # of RRs
+ # should be 13, counting the duplicated SOA.
+ # At this point we can confirm both in and out of AXFR for a zone
+ # containing an NSEC3 RR.
+ # We don't have to specify the address/port here; the defaults will work.
+ When I do an AXFR transfer of example.org
+ Then transfer result should have 13 rrs
diff --git a/tests/lettuce/run_lettuce.sh b/tests/lettuce/run_lettuce.sh
new file mode 100755
index 0000000..9580dce
--- /dev/null
+++ b/tests/lettuce/run_lettuce.sh
@@ -0,0 +1,25 @@
+#! /bin/sh
+
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+if [ "$1" = "-I" ]; then
+ shift
+ echo "$@"
+ LETTUCE_SETUP_COMPLETED=1 exec lettuce $@
+else
+ . ./setup_intree_bind10.sh
+ exec lettuce $@
+fi
diff --git a/tests/lettuce/setup_intree_bind10.sh.in b/tests/lettuce/setup_intree_bind10.sh.in
new file mode 100644
index 0000000..4ccf6ca
--- /dev/null
+++ b/tests/lettuce/setup_intree_bind10.sh.in
@@ -0,0 +1,48 @@
+#! /bin/sh
+
+# Copyright (C) 2010 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
+export PYTHON_EXEC
+
+BIND10_PATH=@abs_top_builddir@/src/bin/bind10
+
+PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
+export PATH
+
+PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs:$PYTHONPATH
+export PYTHONPATH
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ export @ENV_LIBRARY_PATH@
+fi
+
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+# TODO: We need to do this feature based (ie. no general from_source)
+# But right now we need a second one because some spec files are
+# generated and hence end up under builddir
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
+export LETTUCE_SETUP_COMPLETED=1
diff --git a/tests/lettuce/setup_intree_bind10.sh.win32 b/tests/lettuce/setup_intree_bind10.sh.win32
new file mode 100644
index 0000000..4ccf6ca
--- /dev/null
+++ b/tests/lettuce/setup_intree_bind10.sh.win32
@@ -0,0 +1,48 @@
+#! /bin/sh
+
+# Copyright (C) 2010 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
+export PYTHON_EXEC
+
+BIND10_PATH=@abs_top_builddir@/src/bin/bind10
+
+PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
+export PATH
+
+PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs:$PYTHONPATH
+export PYTHONPATH
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ export @ENV_LIBRARY_PATH@
+fi
+
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+# TODO: We need to do this feature based (ie. no general from_source)
+# But right now we need a second one because some spec files are
+# generated and hence end up under builddir
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
+BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
+export BIND10_MSGQ_SOCKET_FILE
+
+export LETTUCE_SETUP_COMPLETED=1
diff --git a/tests/system/.gitignore b/tests/system/.gitignore
new file mode 100644
index 0000000..76f87fe
--- /dev/null
+++ b/tests/system/.gitignore
@@ -0,0 +1,2 @@
+/conf.sh
+/run.sh
diff --git a/tests/system/bindctl/nsx1/.gitignore b/tests/system/bindctl/nsx1/.gitignore
new file mode 100644
index 0000000..4a8ce05
--- /dev/null
+++ b/tests/system/bindctl/nsx1/.gitignore
@@ -0,0 +1,3 @@
+/b10-config.db.template
+/bind10.run
+/bindctl.out
diff --git a/tests/system/bindctl/nsx1/b10-config.db.template.in b/tests/system/bindctl/nsx1/b10-config.db.template.in
index 162329a..7a3647c 100644
--- a/tests/system/bindctl/nsx1/b10-config.db.template.in
+++ b/tests/system/bindctl/nsx1/b10-config.db.template.in
@@ -3,8 +3,5 @@
"listen_on": [{"address": "10.53.0.1", "port": 53210}],
"database_file": "@abs_builddir@/zone.sqlite3",
"statistics-interval": 1
- },
- "Xfrout": {
- "log_file": "@abs_builddir@/Xfrout.log"
}
}
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
index 49ef0f1..352642e 100755
--- a/tests/system/bindctl/tests.sh
+++ b/tests/system/bindctl/tests.sh
@@ -25,10 +25,25 @@ status=0
n=0
# TODO: consider consistency with statistics definition in auth.spec
-auth_queries_tcp="\<queries\.tcp\>"
-auth_queries_udp="\<queries\.udp\>"
+cnt_name1="\<queries\.tcp\>"
+cnt_name2="\<queries\.udp\>"
+cnt_name3="\<opcode\.query\>"
+cnt_value1=0
+cnt_value2=0
+cnt_value3=0
-echo "I:Checking b10-auth is working by default ($n)"
+echo "I:Checking b10-auth is disabled by default ($n)"
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Starting b10-auth and checking that it works ($n)"
+echo 'config add Boss/components b10-auth
+config set Boss/components/b10-auth { "special": "auth", "kind": "needed" }
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
# perform a simple check on the output (digcomp would be too much for this)
grep 192.0.2.1 dig.out.$n > /dev/null || status=1
@@ -42,31 +57,37 @@ sleep 2
echo 'Stats show
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# the server should have received 1 UDP and 1 TCP queries (TCP query was
-# sent from the server startup script)
-grep $auth_queries_tcp".*\<1\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_queries_udp".*\<1\>" bindctl.out.$n > /dev/null || status=1
+# the server should have received 1 UDP and 0 TCP queries (the server
+# startup script no longer sends any TCP queries)
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
echo "I:Stopping b10-auth and checking that ($n)"
-echo 'config set Boss/start_auth false
+echo 'config remove Boss/components b10-auth
config commit
quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
# dig should exit with a failure code.
-$DIG +tcp +norec @10.53.0.1 -p 53210 ns.example.com. A && status=1
+$DIG +tcp +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
echo "I:Restarting b10-auth and checking that ($n)"
-echo 'config set Boss/start_auth true
+echo 'config add Boss/components b10-auth
+config set Boss/components/b10-auth { "special": "auth", "kind": "needed" }
config commit
quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+# perform a simple check on the output (digcomp would be too much for this)
grep 192.0.2.1 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
@@ -77,8 +98,12 @@ echo 'Stats show
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
# The statistics counters should have been reset while stop/start.
-grep $auth_queries_tcp".*\<0\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_queries_udp".*\<1\>" bindctl.out.$n > /dev/null || status=1
+cnt_value1=0
+cnt_value2=1
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
@@ -101,10 +126,68 @@ echo 'Stats show
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
# The statistics counters shouldn't be reset due to hot-swapping datasource.
-grep $auth_queries_tcp".*\<0\>" bindctl.out.$n > /dev/null || status=1
-grep $auth_queries_udp".*\<2\>" bindctl.out.$n > /dev/null || status=1
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Starting more b10-auths and checking that ($n)"
+for i in 2 3
+do
+ echo 'config add Boss/components b10-auth-'$i'
+config set Boss/components/b10-auth-'$i' { "special": "auth", "kind": "needed" }
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+done
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+grep 192.0.2.2 dig.out.$n > /dev/null || status=1
+if [ $status != 0 ]; then echo "I:failed"; fi
+n=`expr $n + 1`
+
+echo "I:Rechecking BIND 10 statistics consistency after a pause ($n)"
+sleep 2
+cnt_value1=`expr $cnt_value1 + 0`
+cnt_value2=`expr $cnt_value2 + 1`
+cnt_value3=`expr $cnt_value1 + $cnt_value2`
+# Rechecking some times
+for i in 1 2 3 4
+do
+ echo 'Stats show
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+ # The statistics counters should keep being consistent even while
+ # multiple b10-auths are running.
+ grep $cnt_name1".*\<"$cnt_value1"\>" bindctl.out.$n > /dev/null || status=1
+ grep $cnt_name2".*\<"$cnt_value2"\>" bindctl.out.$n > /dev/null || status=1
+ grep $cnt_name3".*\<"$cnt_value3"\>" bindctl.out.$n > /dev/null || status=1
+ if [ $status != 0 ]; then echo "I:failed "; break ; fi
+done
+n=`expr $n + 1`
+
+echo "I:Stopping extra b10-auths and checking that ($n)"
+for i in 3 2
+do
+ echo 'config remove Boss/components b10-auth-'$i'
+config commit
+quit
+' | $RUN_BINDCTL \
+ --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+done
+$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
+grep 192.0.2.2 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
+# The statistics counters can not be rechecked here because the auth
+# instance seems to hang up after one of the multiple auth instances
+# was removed via bindctl. This reason seems to be the same reason as
+# #1703.
+
echo "I:exit status: $status"
exit $status
diff --git a/tests/system/glue/.gitignore b/tests/system/glue/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/glue/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/glue/nsx1/.gitignore b/tests/system/glue/nsx1/.gitignore
new file mode 100644
index 0000000..c0750b3
--- /dev/null
+++ b/tests/system/glue/nsx1/.gitignore
@@ -0,0 +1,3 @@
+/b10-config.db
+/bind10.run
+/bindctl.out
diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in
index acd040c..660183b 100644
--- a/tests/system/glue/nsx1/b10-config.db.in
+++ b/tests/system/glue/nsx1/b10-config.db.in
@@ -3,7 +3,14 @@
"listen_on": [{"address": "10.53.0.1", "port": 53210}],
"database_file": "@abs_builddir@/zone.sqlite3"
},
- "Xfrout": {
- "log_file": "@abs_builddir@/Xfrout.log"
+ "Boss": {
+ "components": {
+ "b10-auth": {"kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/system/ixfr/.gitignore b/tests/system/ixfr/.gitignore
new file mode 100644
index 0000000..027d45e
--- /dev/null
+++ b/tests/system/ixfr/.gitignore
@@ -0,0 +1,8 @@
+/b10-config.db
+/common_tests.sh
+/db.example.n0
+/db.example.n2
+/db.example.n2.refresh
+/db.example.n4
+/db.example.n6
+/ixfr_init.sh
diff --git a/tests/system/ixfr/b10-config.db.in b/tests/system/ixfr/b10-config.db.in
index 946d80f..156c959 100644
--- a/tests/system/ixfr/b10-config.db.in
+++ b/tests/system/ixfr/b10-config.db.in
@@ -19,5 +19,15 @@
"name": "example.",
"class": "IN"
}]
+ },
+ "Boss": {
+ "components": {
+ "b10-auth": {"kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
}
}
diff --git a/tests/system/ixfr/in-1/.gitignore b/tests/system/ixfr/in-1/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-1/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/ixfr/in-2/.gitignore b/tests/system/ixfr/in-2/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-2/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/ixfr/in-2/ns1/.gitignore b/tests/system/ixfr/in-2/ns1/.gitignore
new file mode 100644
index 0000000..35ae1cb
--- /dev/null
+++ b/tests/system/ixfr/in-2/ns1/.gitignore
@@ -0,0 +1 @@
+/named.run
diff --git a/tests/system/ixfr/in-2/nsx2/.gitignore b/tests/system/ixfr/in-2/nsx2/.gitignore
new file mode 100644
index 0000000..d31eb18
--- /dev/null
+++ b/tests/system/ixfr/in-2/nsx2/.gitignore
@@ -0,0 +1 @@
+/bindctl.out
diff --git a/tests/system/ixfr/in-2/tests.sh b/tests/system/ixfr/in-2/tests.sh
index 7b1e2a8..3050713 100644
--- a/tests/system/ixfr/in-2/tests.sh
+++ b/tests/system/ixfr/in-2/tests.sh
@@ -54,7 +54,7 @@ then
exit 1
fi
-grep XFRIN_XFR_TRANSFER_SUCCESS nsx2/bind10.run | grep IXFR > /dev/null
+grep XFRIN_IXFR_TRANSFER_SUCCESS nsx2/bind10.run | grep IXFR > /dev/null
if [ $? -ne 0 ];
then
echo "R:$CLIENT_NAME FAIL no 'IXFR successful' message in the BIND 10 log"
diff --git a/tests/system/ixfr/in-3/.gitignore b/tests/system/ixfr/in-3/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-3/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/ixfr/in-3/tests.sh b/tests/system/ixfr/in-3/tests.sh
index 858b815..d47a221 100644
--- a/tests/system/ixfr/in-3/tests.sh
+++ b/tests/system/ixfr/in-3/tests.sh
@@ -22,6 +22,8 @@
# server; the server should not respond to the request, so the client should
# then send an AXFR request and receive the latest copy of the zone.
+# TODO It seems bind9 still allows IXFR even when provide-ixfr on;
+
. ../ixfr_init.sh
status=$?
@@ -29,9 +31,6 @@ status=$?
old_client_serial=`$DIG_SOA @$CLIENT_IP | $AWK '{print $3}'`
echo "I:SOA serial of IXFR client $CLIENT_NAME is $old_client_serial"
-# TODO: Need to alter configuration of BIND 10 server such that it accepts
-# NOTIFYs from and sends IXFR requests to the BIND 9 master.
-
# If required, get the IXFR server to notify the IXFR client of the new zone.
# Do this by allowing notifies and then triggering a re-notification of the
# server.
@@ -48,8 +47,20 @@ status=`expr $status + $?`
compare_soa $SERVER_NAME $SERVER_IP $CLIENT_NAME $CLIENT_IP
status=`expr $status + $?`
-# TODO: Check the BIND 10 log, looking for the IXFR messages that indicate that
-# it has initiated an IXFR and then an AXFR.
+# Check the log there's the IXFR and fallback
+grep XFRIN_XFR_TRANSFER_STARTED nsx2/bind10.run | grep IXFR
+if [ $? -ne 0 ];
+then
+ echo "R:$CLIENT_NAME FAIL no 'IXFR started' message in the BIND 10 log"
+ exit 1
+fi
+
+grep XFRIN_XFR_TRANSFER_FALLBACK nsx2/bind10.run
+if [ $? -ne 0 ];
+then
+ echo "R:$CLIENT_NAME FAIL no fallback message in BIND10 log"
+ exit 1
+fi
echo "I:exit status: $status"
exit $status
diff --git a/tests/system/ixfr/in-4/.gitignore b/tests/system/ixfr/in-4/.gitignore
new file mode 100644
index 0000000..87e08bf
--- /dev/null
+++ b/tests/system/ixfr/in-4/.gitignore
@@ -0,0 +1 @@
+/setup.sh
diff --git a/tests/system/ixfr/named_noixfr.conf b/tests/system/ixfr/named_noixfr.conf
index b0d972a..d171876 100644
--- a/tests/system/ixfr/named_noixfr.conf
+++ b/tests/system/ixfr/named_noixfr.conf
@@ -33,6 +33,7 @@ options {
ixfr-from-differences no;
notify explicit;
also-notify { 10.53.0.2; };
+ provide-ixfr no;
};
zone "example" {
diff --git a/tests/system/start.pl b/tests/system/start.pl
index daa4577..32284de 100755
--- a/tests/system/start.pl
+++ b/tests/system/start.pl
@@ -53,6 +53,8 @@ if ($server && !-d "$test/$server") {
my $topdir = abs_path("$test/..");
my $testdir = abs_path("$test");
my $RUN_BIND10 = $ENV{'RUN_BIND10'};
+my $RUN_BINDCTL = $ENV{'RUN_BINDCTL'};
+my $BINDCTL_CSV_DIR = $ENV{'BINDCTL_CSV_DIR'};
my $NAMED = $ENV{'BIND9_NAMED'};
my $LWRESD = $ENV{'LWRESD'};
my $DIG = $ENV{'DIG'};
@@ -211,14 +213,15 @@ sub verify_server {
my $tries = 0;
while (1) {
- my $return = system("$DIG +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p 53210 version.bind. chaos txt \@10.53.0.$n > dig.out");
+ my $return = system("echo \"Stats show\" | $RUN_BINDCTL --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out");
last if ($return == 0);
- print `grep ";" dig.out`;
if (++$tries >= 30) {
print "I:no response from $server\n";
print "R:FAIL\n";
system("$PERL $topdir/stop.pl $testdir");
exit 1;
+ } else {
+ print "I:no response from $server. retrying.\n";
}
sleep 2;
}
diff --git a/tests/tools/Makefile.am b/tests/tools/Makefile.am
index 2f07494..b4c1394 100644
--- a/tests/tools/Makefile.am
+++ b/tests/tools/Makefile.am
@@ -1 +1,5 @@
-SUBDIRS = badpacket
+# perfdhcp uses getifaddrs, which is not a standard library and is not
+# portable (at least not exist on our Solaris buildbot). For a short term
+# workaround we stop building it until it's resolved.
+#SUBDIRS = badpacket perfdhcp
+SUBDIRS = badpacket perfdhcp
diff --git a/tests/tools/badpacket/.gitignore b/tests/tools/badpacket/.gitignore
new file mode 100644
index 0000000..ad6c1e6
--- /dev/null
+++ b/tests/tools/badpacket/.gitignore
@@ -0,0 +1 @@
+/badpacket
diff --git a/tests/tools/badpacket/tests/.gitignore b/tests/tools/badpacket/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/tests/tools/badpacket/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/tests/tools/badpacket/tests/Makefile.am b/tests/tools/badpacket/tests/Makefile.am
index 2daa664..a036dc8 100644
--- a/tests/tools/badpacket/tests/Makefile.am
+++ b/tests/tools/badpacket/tests/Makefile.am
@@ -10,6 +10,9 @@ endif
CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
TESTS =
if HAVE_GTEST
TESTS += run_unittests
diff --git a/tests/tools/perfdhcp/.gitignore b/tests/tools/perfdhcp/.gitignore
new file mode 100644
index 0000000..1a8375a
--- /dev/null
+++ b/tests/tools/perfdhcp/.gitignore
@@ -0,0 +1 @@
+/perfdhcp
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
new file mode 100644
index 0000000..6ebc00f
--- /dev/null
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -0,0 +1,40 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+AM_LDFLAGS = $(CLOCK_GETTIME_LDFLAGS)
+AM_LDFLAGS += -lm
+if USE_STATIC_LINK
+AM_LDFLAGS += -static
+endif
+
+lib_LTLIBRARIES = libperfdhcp++.la
+libperfdhcp___la_SOURCES = command_options.cc command_options.h
+libperfdhcp___la_SOURCES += localized_option.h
+libperfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h
+libperfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h
+libperfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
+
+libperfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libperfdhcp___la_CXXFLAGS += -Wno-unused-parameter
+endif
+
+libperfdhcp___la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+libperfdhcp___la_LIBADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
+libperfdhcp___la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+
+pkglibexec_PROGRAMS = perfdhcp
+perfdhcp_SOURCES = perfdhcp.c
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
new file mode 100644
index 0000000..09393bb
--- /dev/null
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -0,0 +1,699 @@
+// 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "exceptions/exceptions.h"
+
+#include "command_options.h"
+
+using namespace std;
+using namespace isc;
+
+namespace isc {
+namespace perfdhcp {
+
+CommandOptions&
+CommandOptions::instance() {
+ static CommandOptions options;
+ return (options);
+}
+
+void
+CommandOptions::reset() {
+ // Default mac address used in DHCP messages
+ // if -b mac=<mac-address> was not specified
+ uint8_t mac[6] = { 0x0, 0xC, 0x1, 0x2, 0x3, 0x4 };
+
+ // Default packet drop time if -D<drop-time> parameter
+ // was not specified
+ double dt[2] = { 1., 1. };
+
+ // We don't use constructor initialization list because we
+ // will need to reset all members many times to perform unit tests
+ ipversion_ = 0;
+ exchange_mode_ = DORA_SARR;
+ rate_ = 0;
+ report_delay_ = 0;
+ clients_num_ = 0;
+ mac_prefix_.assign(mac, mac + 6);
+ base_.resize(0);
+ num_request_.resize(0);
+ period_ = 0;
+ drop_time_set_ = 0;
+ drop_time_.assign(dt, dt + 2);
+ max_drop_.clear();
+ max_pdrop_.clear();
+ localname_.clear();
+ is_interface_ = false;
+ preload_ = 0;
+ aggressivity_ = 1;
+ local_port_ = 0;
+ seeded_ = false;
+ seed_ = 0;
+ broadcast_ = false;
+ rapid_commit_ = false;
+ use_first_ = false;
+ template_file_.clear();
+ rnd_offset_.clear();
+ xid_offset_.clear();
+ elp_offset_ = -1;
+ sid_offset_ = -1;
+ rip_offset_ = -1;
+ diags_.clear();
+ wrapped_.clear();
+ server_name_.clear();
+}
+
+void
+CommandOptions::parse(int argc, char** const argv) {
+ // Reset internal variables used by getopt
+ // to eliminate undefined behavior when
+ // parsing different command lines multiple times
+
+#ifdef __GLIBC__
+ // Warning: non-portable code. This is due to a bug in glibc's
+ // getopt() which keeps internal state about an old argument vector
+ // (argc, argv) from last call and tries to scan them when a new
+ // argument vector (argc, argv) is passed. As the old vector may not
+ // be main()'s arguments, but heap allocated and may have been freed
+ // since, this becomes a use after free and results in random
+ // behavior. According to the NOTES section in glibc getopt()'s
+ // manpage, setting optind=0 resets getopt()'s state. Though this is
+ // not required in our usage of getopt(), the bug still happens
+ // unless we set optind=0.
+ //
+ // Setting optind=0 is non-portable code.
+ optind = 0;
+#else
+ optind = 1;
+#endif
+
+ opterr = 0;
+
+ // Reset values of class members
+ reset();
+
+ initialize(argc, argv);
+ validate();
+}
+
+void
+CommandOptions::initialize(int argc, char** argv) {
+ char opt = 0; // Subsequent options returned by getopt()
+ std::string drop_arg; // Value of -D<value>argument
+ size_t percent_loc = 0; // Location of % sign in -D<value>
+ double drop_percent = 0; // % value (1..100) in -D<value%>
+ int num_drops = 0; // Max number of drops specified in -D<value>
+ int num_req = 0; // Max number of dropped requests in -n<max-drops>
+ int offset_arg = 0; // Temporary variable holding offset arguments
+ std::string sarg; // Temporary variable for string args
+
+ // In this section we collect argument values from command line
+ // they will be tuned and validated elsewhere
+ while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:")) != -1) {
+ switch (opt) {
+ case 'v':
+ version();
+ return;
+
+ case '1':
+ use_first_ = true;
+ break;
+
+ case '4':
+ check(ipversion_ == 6, "IP version already set to 6");
+ ipversion_ = 4;
+ break;
+
+ case '6':
+ check(ipversion_ == 4, "IP version already set to 4");
+ ipversion_ = 6;
+ break;
+
+ case 'a':
+ aggressivity_ = positiveInteger("value of aggressivity: -a<value> must be a positive integer");
+ break;
+
+ case 'b':
+ check(base_.size() > 3, "-b<value> already specified, unexpected occurence of 5th -b<value>");
+ base_.push_back(optarg);
+ decodeBase(base_.back());
+ break;
+
+ case 'B':
+ broadcast_ = true;
+ break;
+
+ case 'c':
+ rapid_commit_ = true;
+ break;
+
+ case 'd':
+ check(drop_time_set_ > 1, "maximum number of drops already specified, "
+ "unexpected 3rd occurence of -d<value>");
+ try {
+ drop_time_[drop_time_set_] = boost::lexical_cast<double>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter,
+ "value of drop time: -d<value> must be positive number");
+ }
+ check(drop_time_[drop_time_set_] <= 0., "drop-time must be a positive number");
+ drop_time_set_ = true;
+ break;
+
+ case 'D':
+ drop_arg = std::string(optarg);
+ percent_loc = drop_arg.find('%');
+ check(max_pdrop_.size() > 1 || max_drop_.size() > 1, "values of maximum drops: -D<value> already "
+ "specified, unexpected 3rd occurence of -D,value>");
+ if ((percent_loc) != std::string::npos) {
+ try {
+ drop_percent = boost::lexical_cast<double>(drop_arg.substr(0, percent_loc));
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter,
+ "value of drop percentage: -D<value%> must be 0..100");
+ }
+ check((drop_percent <= 0) || (drop_percent >= 100),
+ "value of drop percentage: -D<value%> must be 0..100");
+ max_pdrop_.push_back(drop_percent);
+ } else {
+ num_drops = positiveInteger("value of max drops number: -d<value> must be a positive integer");
+ max_drop_.push_back(num_drops);
+ }
+ break;
+
+ case 'E':
+ elp_offset_ = nonNegativeInteger("value of time-offset: -E<value> must not be a negative integer");
+ break;
+
+ case 'h':
+ usage();
+ return;
+
+ case 'i':
+ exchange_mode_ = DO_SA;
+ break;
+
+ case 'I':
+ rip_offset_ = positiveInteger("value of ip address offset: -I<value> must be a positive integer");
+ break;
+
+ case 'l':
+ localname_ = std::string(optarg);
+ break;
+
+ case 'L':
+ local_port_ = nonNegativeInteger("value of local port: -L<value> must not be a negative integer");
+ check(local_port_ > static_cast<int>(std::numeric_limits<uint16_t>::max()),
+ "local-port must be lower than " +
+ boost::lexical_cast<std::string>(std::numeric_limits<uint16_t>::max()));
+ break;
+
+ case 'n':
+ num_req = positiveInteger("value of num-request: -n<value> must be a positive integer");
+ if (num_request_.size() >= 2) {
+ isc_throw(isc::InvalidParameter,"value of maximum number of requests: -n<value> "
+ "already specified, unexpected 3rd occurence of -n<value>");
+ }
+ num_request_.push_back(num_req);
+ break;
+
+ case 'O':
+ if (rnd_offset_.size() < 2) {
+ offset_arg = positiveInteger("value of random offset: -O<value> must be greater than 3");
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "random offsets already specified, unexpected 3rd occurence of -O<value>");
+ }
+ check(offset_arg < 3, "value of random random-offset: -O<value> must be greater than 3 ");
+ rnd_offset_.push_back(offset_arg);
+ break;
+
+ case 'p':
+ period_ = positiveInteger("value of test period: -p<value> must be a positive integer");
+ break;
+
+ case 'P':
+ preload_ = nonNegativeInteger("number of preload packets: -P<value> must not be "
+ "a negative integer");
+ break;
+
+ case 'r':
+ rate_ = positiveInteger("value of rate: -r<value> must be a positive integer");
+ break;
+
+ case 'R':
+ initClientsNum();
+ break;
+
+ case 's':
+ seed_ = static_cast<unsigned int>
+ (nonNegativeInteger("value of seed: -s <seed> must be non-negative integer"));
+ seeded_ = seed_ > 0 ? true : false;
+ break;
+
+ case 'S':
+ sid_offset_ = positiveInteger("value of server id offset: -S<value> must be a positive integer");
+ break;
+
+ case 't':
+ report_delay_ = positiveInteger("value of report delay: -t<value> must be a positive integer");
+ break;
+
+ case 'T':
+ if (template_file_.size() < 2) {
+ sarg = nonEmptyString("template file name not specified, expected -T<filename>");
+ template_file_.push_back(sarg);
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "template files are already specified, unexpected 3rd -T<filename> occurence");
+ }
+ break;
+
+ case 'w':
+ wrapped_ = nonEmptyString("command for wrapped mode: -w<command> must be specified");
+ break;
+
+ case 'x':
+ diags_ = nonEmptyString("value of diagnostics selectors: -x<value> must be specified");
+ break;
+
+ case 'X':
+ if (xid_offset_.size() < 2) {
+ offset_arg = positiveInteger("value of transaction id: -X<value> must be a positive integer");
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "transaction ids already specified, unexpected 3rd -X<value> occurence");
+ }
+ xid_offset_.push_back(offset_arg);
+ break;
+
+ default:
+ isc_throw(isc::InvalidParameter, "unknown command line option");
+ }
+ }
+
+ // If the IP version was not specified in the
+ // command line, assume IPv4.
+ if (ipversion_ == 0) {
+ ipversion_ = 4;
+ }
+
+ // If template packet files specified for both DISCOVER/SOLICIT
+ // and REQUEST/REPLY exchanges make sure we have transaction id
+ // and random duid offsets for both exchanges. We will duplicate
+ // value specified as -X<value> and -R<value> for second
+ // exchange if user did not specified otherwise.
+ if (template_file_.size() > 1) {
+ if (xid_offset_.size() == 1) {
+ xid_offset_.push_back(xid_offset_[0]);
+ }
+ if (rnd_offset_.size() == 1) {
+ rnd_offset_.push_back(rnd_offset_[0]);
+ }
+ }
+
+ // Get server argument
+ // NoteFF02::1:2 and FF02::1:3 are defined in RFC3315 as
+ // All_DHCP_Relay_Agents_and_Servers and All_DHCP_Servers
+ // addresses
+ check(optind < argc -1, "extra arguments?");
+ if (optind == argc - 1) {
+ server_name_ = argv[optind];
+ // Decode special cases
+ if ((ipversion_ == 4) && (server_name_.compare("all") == 0)) {
+ broadcast_ = 1;
+ // 255.255.255.255 is IPv4 broadcast address
+ server_name_ = "255.255.255.255";
+ } else if ((ipversion_ == 6) && (server_name_.compare("all") == 0)) {
+ server_name_ = "FF02::1:2";
+ } else if ((ipversion_ == 6) && (server_name_.compare("servers") == 0)) {
+ server_name_ = "FF05::1:3";
+ }
+ }
+
+ // TODO handle -l option with IfaceManager when it is created
+}
+
+void
+CommandOptions::initClientsNum() {
+ const std::string errmsg = "value of -R <value> must be non-negative integer";
+
+ // Declare clients_num as as 64-bit signed value to
+ // be able to detect negative values provided
+ // by user. We would not detect negative values
+ // if we casted directly to unsigned value.
+ long long clients_num = 0;
+ try {
+ clients_num = boost::lexical_cast<long long>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter, errmsg.c_str());
+ }
+ check(clients_num < 0, errmsg);
+ try {
+ clients_num_ = boost::lexical_cast<uint32_t>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+}
+
+void
+CommandOptions::decodeBase(const std::string& base) {
+ std::string b(base);
+ boost::algorithm::to_lower(b);
+
+ // Currently we only support mac and duid
+ if ((b.substr(0, 4) == "mac=") || (b.substr(0, 6) == "ether=")) {
+ decodeMac(b);
+ } else if (b.substr(0, 5) == "duid=") {
+ decodeDuid(b);
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "base value not provided as -b<value>, expected -b mac=<mac> or -b duid=<duid>");
+ }
+}
+
+void
+CommandOptions::decodeMac(const std::string& base) {
+ // Strip string from mac=
+ size_t found = base.find('=');
+ static const char* errmsg = "expected -b<base> format for mac address is -b mac=00::0C::01::02::03::04";
+ check(found == std::string::npos, errmsg);
+
+ // Decode mac address to vector of uint8_t
+ std::istringstream s1(base.substr(found + 1));
+ std::string token;
+ mac_prefix_.clear();
+ // Get pieces of MAC address separated with : (or even ::)
+ while (std::getline(s1, token, ':')) {
+ unsigned int ui = 0;
+ // Convert token to byte value using std::istringstream
+ if (token.length() > 0) {
+ try {
+ // Do actual conversion
+ ui = convertHexString(token);
+ } catch (isc::InvalidParameter&) {
+ isc_throw(isc::InvalidParameter,
+ "invalid characters in MAC provided");
+
+ }
+ // If conversion succeeded store byte value
+ mac_prefix_.push_back(ui);
+ }
+ }
+ // MAC address must consist of 6 octets, otherwise it is invalid
+ check(mac_prefix_.size() != 6, errmsg);
+}
+
+void
+CommandOptions::decodeDuid(const std::string& base) {
+ // Strip argument from duid=
+ size_t found = base.find('=');
+ check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>");
+ std::string b = base.substr(found + 1);
+
+ // DUID must have even number of digits and must not be longer than 64 bytes
+ check(b.length() & 1, "odd number of hexadecimal digits in duid");
+ check(b.length() > 128, "duid too large");
+ check(b.length() == 0, "no duid specified");
+
+ // Turn pairs of hexadecimal digits into vector of octets
+ for (int i = 0; i < b.length(); i += 2) {
+ unsigned int ui = 0;
+ try {
+ // Do actual conversion
+ ui = convertHexString(b.substr(i, 2));
+ } catch (isc::InvalidParameter&) {
+ isc_throw(isc::InvalidParameter,
+ "invalid characters in DUID provided, exepected hex digits");
+ }
+ duid_prefix_.push_back(static_cast<uint8_t>(ui));
+ }
+}
+
+uint8_t
+CommandOptions::convertHexString(const std::string& text) const {
+ unsigned int ui = 0;
+ // First, check if we are dealing with hexadecimal digits only
+ for (int i = 0; i < text.length(); ++i) {
+ if (!std::isxdigit(text[i])) {
+ isc_throw(isc::InvalidParameter,
+ "The following digit: " << text[i] << " in "
+ << text << "is not hexadecimal");
+ }
+ }
+ // If we are here, we have valid string to convert to octet
+ std::istringstream text_stream(text);
+ text_stream >> std::hex >> ui >> std::dec;
+ // Check if for some reason we have overflow - this should never happen!
+ if (ui > 0xFF) {
+ isc_throw(isc::InvalidParameter, "Can't convert more than two hex digits to byte");
+ }
+ return ui;
+}
+
+void
+CommandOptions::validate() const {
+ check((getIpVersion() != 4) && (isBroadcast() != 0),
+ "-B is not compatible with IPv6 (-6)");
+ check((getIpVersion() != 6) && (isRapidCommit() != 0),
+ "-6 (IPv6) must be set to use -c");
+ check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
+ "second -n<num-request> is not compatible with -i");
+ check((getExchangeMode() == DO_SA) && (getDropTime()[1] != 1.),
+ "second -d<drop-time> is not compatible with -i");
+ check((getExchangeMode() == DO_SA) &&
+ ((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)),
+ "second -D<max-drop> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (isUseFirst()),
+ "-1 is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1),
+ "second -T<template-file> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getTransactionIdOffset().size() > 1),
+ "second -X<xid-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getRandomOffset().size() > 1),
+ "second -O<random-offset is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getElapsedTimeOffset() >= 0),
+ "-E<time-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getServerIdOffset() >= 0),
+ "-S<srvid-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getRequestedIpOffset() >= 0),
+ "-I<ip-offset> is not compatible with -i\n");
+ check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
+ "-i must be set to use -c\n");
+ check((getRate() == 0) && (getReportDelay() != 0),
+ "-r<rate> must be set to use -t<report>\n");
+ check((getRate() == 0) && (getNumRequests().size() > 0),
+ "-r<rate> must be set to use -n<num-request>\n");
+ check((getRate() == 0) && (getPeriod() != 0),
+ "-r<rate> must be set to use -p<test-period>\n");
+ check((getRate() == 0) &&
+ ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
+ "-r<rate> must be set to use -D<max-drop>\n");
+ check((getTemplateFiles().size() < getTransactionIdOffset().size()),
+ "-T<template-file> must be set to use -X<xid-offset>\n");
+ check((getTemplateFiles().size() < getRandomOffset().size()),
+ "-T<template-file> must be set to use -O<random-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getElapsedTimeOffset() >= 0),
+ "second/request -T<template-file> must be set to use -E<time-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getServerIdOffset() >= 0),
+ "second/request -T<template-file> must be set to "
+ "use -S<srvid-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getRequestedIpOffset() >= 0),
+ "second/request -T<template-file> must be set to "
+ "use -I<ip-offset>\n");
+
+}
+
+void
+CommandOptions::check(bool condition, const std::string& errmsg) const {
+ // The same could have been done with macro or just if statement but
+ // we prefer functions to macros here
+ if (condition) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+}
+
+int
+CommandOptions::positiveInteger(const std::string& errmsg) const {
+ try {
+ int value = boost::lexical_cast<int>(optarg);
+ check(value <= 0, errmsg);
+ return (value);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(InvalidParameter, errmsg);
+ }
+}
+
+int
+CommandOptions::nonNegativeInteger(const std::string& errmsg) const {
+ try {
+ int value = boost::lexical_cast<int>(optarg);
+ check(value < 0, errmsg);
+ return (value);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(InvalidParameter, errmsg);
+ }
+}
+
+std::string
+CommandOptions::nonEmptyString(const std::string& errmsg) const {
+ std::string sarg = optarg;
+ if (sarg.length() == 0) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+ return sarg;
+}
+
+void
+CommandOptions::usage() const {
+ fprintf(stdout, "%s",
+"perfdhcp [-hv] [-4|-6] [-r<rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+" [-n<num-request>] [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
+" [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
+" [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
+" [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
+" [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
+" [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+"\n"
+"The [server] argument is the name/address of the DHCP server to\n"
+"contact. For DHCPv4 operation, exchanges are initiated by\n"
+"transmitting a DHCP DISCOVER to this address.\n"
+"\n"
+"For DHCPv6 operation, exchanges are initiated by transmitting a DHCP\n"
+"SOLICIT to this address. In the DHCPv6 case, the special name 'all'\n"
+"can be used to refer to All_DHCP_Relay_Agents_and_Servers (the\n"
+"multicast address FF02::1:2), or the special name 'servers' to refer\n"
+"to All_DHCP_Servers (the multicast address FF05::1:3). The [server]\n"
+"argument is optional only in the case that -l is used to specify an\n"
+"interface, in which case [server] defaults to 'all'.\n"
+"\n"
+"The default is to perform a single 4-way exchange, effectively pinging\n"
+"the server.\n"
+"The -r option is used to set up a performance test, without\n"
+"it exchanges are initiated as fast as possible.\n"
+"\n"
+"Options:\n"
+"-1: Take the server-ID option from the first received message.\n"
+"-4: DHCPv4 operation (default). This is incompatible with the -6 option.\n"
+"-6: DHCPv6 operation. This is incompatible with the -4 option.\n"
+"-a<aggressivity>: When the target sending rate is not yet reached,\n"
+" control how many exchanges are initiated before the next pause.\n"
+"-b<base>: The base mac, duid, IP, etc, used to simulate different\n"
+" clients. This can be specified multiple times, each instance is\n"
+" in the <type>=<value> form, for instance:\n"
+" (and default) mac=00:0c:01:02:03:04.\n"
+"-d<drop-time>: Specify the time after which a request is treated as\n"
+" having been lost. The value is given in seconds and may contain a\n"
+" fractional component. The default is 1 second.\n"
+"-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
+" elapsed-time option in the (second/request) template.\n"
+" The value 0 disables it.\n"
+"-h: Print this help.\n"
+"-i: Do only the initial part of an exchange: DO or SA, depending on\n"
+" whether -6 is given.\n"
+"-I<ip-offset>: Offset of the (DHCPv4) IP address in the requested-IP\n"
+" option / (DHCPv6) IA_NA option in the (second/request) template.\n"
+"-l<local-addr|interface>: For DHCPv4 operation, specify the local\n"
+" hostname/address to use when communicating with the server. By\n"
+" default, the interface address through which traffic would\n"
+" normally be routed to the server is used.\n"
+" For DHCPv6 operation, specify the name of the network interface\n"
+" via which exchanges are initiated.\n"
+"-L<local-port>: Specify the local port to use\n"
+" (the value 0 means to use the default).\n"
+"-O<random-offset>: Offset of the last octet to randomize in the template.\n"
+"-P<preload>: Initiate first <preload> exchanges back to back at startup.\n"
+"-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA)\n"
+" exchanges per second. A periodic report is generated showing the\n"
+" number of exchanges which were not completed, as well as the\n"
+" average response latency. The program continues until\n"
+" interrupted, at which point a final report is generated.\n"
+"-R<range>: Specify how many different clients are used. With 1\n"
+" (the default), all requests seem to come from the same client.\n"
+"-s<seed>: Specify the seed for randomization, making it repeatable.\n"
+"-S<srvid-offset>: Offset of the server-ID option in the\n"
+" (second/request) template.\n"
+"-T<template-file>: The name of a file containing the template to use\n"
+" as a stream of hexadecimal digits.\n"
+"-v: Report the version number of this program.\n"
+"-w<wrapped>: Command to call with start/stop at the beginning/end of\n"
+" the program.\n"
+"-x<diagnostic-selector>: Include extended diagnostics in the output.\n"
+" <diagnostic-selector> is a string of single-keywords specifying\n"
+" the operations for which verbose output is desired. The selector\n"
+" keyletters are:\n"
+" * 'a': print the decoded command line arguments\n"
+" * 'e': print the exit reason\n"
+" * 'i': print rate processing details\n"
+" * 'r': print randomization details\n"
+" * 's': print first server-id\n"
+" * 't': when finished, print timers of all successful exchanges\n"
+" * 'T': when finished, print templates\n"
+"-X<xid-offset>: Transaction ID (aka. xid) offset in the template.\n"
+"\n"
+"DHCPv4 only options:\n"
+"-B: Force broadcast handling.\n"
+"\n"
+"DHCPv6 only options:\n"
+"-c: Add a rapid commit option (exchanges will be SA).\n"
+"\n"
+"The remaining options are used only in conjunction with -r:\n"
+"\n"
+"-D<max-drop>: Abort the test if more than <max-drop> requests have\n"
+" been dropped. Use -D0 to abort if even a single request has been\n"
+" dropped. If <max-drop> includes the suffix '%', it specifies a\n"
+" maximum percentage of requests that may be dropped before abort.\n"
+" In this case, testing of the threshold begins after 10 requests\n"
+" have been expected to be received.\n"
+"-n<num-request>: Initiate <num-request> transactions. No report is\n"
+" generated until all transactions have been initiated/waited-for,\n"
+" after which a report is generated and the program terminates.\n"
+"-p<test-period>: Send requests for the given test period, which is\n"
+" specified in the same manner as -d. This can be used as an\n"
+" alternative to -n, or both options can be given, in which case the\n"
+" testing is completed when either limit is reached.\n"
+"-t<report>: Delay in seconds between two periodic reports.\n"
+"\n"
+"Errors:\n"
+"- tooshort: received a too short message\n"
+"- orphans: received a message which doesn't match an exchange\n"
+" (duplicate, late or not related)\n"
+"- locallimit: reached to local system limits when sending a message.\n"
+"\n"
+"Exit status:\n"
+"The exit status is:\n"
+"0 on complete success.\n"
+"1 for a general error.\n"
+"2 if an error is found in the command line arguments.\n"
+"3 if there are no general failures in operation, but one or more\n"
+" exchanges are not successfully completed.\n");
+}
+
+void
+CommandOptions::version() const {
+ fprintf(stdout, "version 0.01\n");
+}
+
+
+} // namespace perfdhcp
+} // namespace isc
diff --git a/tests/tools/perfdhcp/command_options.h b/tests/tools/perfdhcp/command_options.h
new file mode 100644
index 0000000..9196857
--- /dev/null
+++ b/tests/tools/perfdhcp/command_options.h
@@ -0,0 +1,412 @@
+// 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 __COMMAND_OPTIONS_H
+#define __COMMAND_OPTIONS_H
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Command Options
+///
+/// This class is responsible for parsing the command-line and storing the
+/// specified options.
+///
+class CommandOptions : public boost::noncopyable {
+public:
+ /// 2-way (cmd line param -i) or 4-way exchanges
+ enum ExchangeMode {
+ DO_SA,
+ DORA_SARR
+ };
+
+ /// CommandOptions is a singleton class. This method returns reference
+ /// to its sole instance.
+ ///
+ /// \return the only existing instance of command options
+ static CommandOptions& instance();
+
+ /// \brief Reset to defaults
+ ///
+ /// Reset data members to default values. This is specifically
+ /// useful when unit tests are performed using different
+ /// command line options.
+ void reset();
+
+ /// \brief Parse command line
+ ///
+ /// Parses the command line and stores the selected options
+ /// in class data members.
+ ///
+ /// \param argc Argument count passed to main().
+ /// \param argv Argument value array passed to main().
+ /// \throws isc::InvalidParameter if parse fails
+ void parse(int argc, char** const argv);
+
+ /// \brief Returns IP version
+ ///
+ /// \return IP version to be used
+ uint8_t getIpVersion() const { return ipversion_; }
+
+ /// \brief Returns packet exchange mode
+ ///
+ /// \return packet exchange mode
+ ExchangeMode getExchangeMode() const { return exchange_mode_; }
+
+ /// \brief Returns echange rate
+ ///
+ /// \return exchange rate per second
+ int getRate() const { return rate_; }
+
+ /// \brief Returns delay between two performance reports
+ ///
+ /// \return delay between two consecutive performance reports
+ int getReportDelay() const { return report_delay_; }
+
+ /// \brief Returns number of simulated clients
+ ///
+ /// \return number of simulated clients
+ uint32_t getClientsNum() const { return clients_num_; }
+
+ /// \brief Returns MAC address prefix
+ ///
+ /// \ return MAC address prefix to simulate different clients
+ std::vector<uint8_t> getMacPrefix() const { return mac_prefix_; }
+
+ /// \brief Returns DUID prefix
+ ///
+ /// \return DUID prefix to simulate different clients
+ std::vector<uint8_t> getDuidPrefix() const { return duid_prefix_; }
+
+ /// \brief Returns base values
+ ///
+ /// \return all base values specified
+ std::vector<std::string> getBase() const { return base_; }
+
+ /// \brief Returns maximum number of exchanges
+ ///
+ /// \return number of exchange requests before test is aborted
+ std::vector<int> getNumRequests() const { return num_request_; }
+
+ /// \brief Returns test period
+ ///
+ /// \return test period before it is aborted
+ int getPeriod() const { return period_; }
+
+ /// \brief Returns drop time
+ ///
+ /// The method returns maximum time elapsed from
+ /// sending the packet before it is assumed dropped.
+ ///
+ /// \return return time before request is assumed dropped
+ std::vector<double> getDropTime() const { return drop_time_; }
+
+ /// \brief Returns maximum drops number
+ ///
+ /// Returns maximum number of packet drops before
+ /// aborting a test.
+ ///
+ /// \return maximum number of dropped requests
+ std::vector<int> getMaxDrop() const { return max_drop_; }
+
+ /// \brief Returns maximal percentage of drops
+ ///
+ /// Returns maximal percentage of packet drops
+ /// before aborting a test.
+ ///
+ /// \return maximum percentage of lost requests
+ std::vector<double> getMaxDropPercentage() const { return max_pdrop_; }
+
+ /// \brief Returns local address or interface name
+ ///
+ /// \return local address or interface name
+ std::string getLocalName() const { return localname_; }
+
+ /// \brief Checks if interface name was used
+ ///
+ /// The method checks if interface name was used
+ /// rather than address.
+ ///
+ /// \return true if interface name was used
+ bool isInterface() const { return is_interface_; }
+
+ /// \brief Returns number of preload exchanges
+ ///
+ /// \return number of preload exchanges
+ int getPreload() const { return preload_; }
+
+ /// \brief Returns aggressivity value
+ ///
+ /// \return aggressivity value
+ int getAggressivity() const { return aggressivity_; }
+
+ /// \brief Returns local port number
+ ///
+ /// \return local port number
+ int getLocalPort() const { return local_port_; }
+
+ /// \brief Checks if seed provided
+ ///
+ /// \return true if seed was provided
+ bool isSeeded() const { return seeded_; }
+
+ /// \brief Returns radom seed
+ ///
+ /// \return random seed
+ uint32_t getSeed() const { return seed_; }
+
+ /// \brief Checks if broadcast address is to be used
+ ///
+ /// \return true if broadcast address is to be used
+ bool isBroadcast() const { return broadcast_; }
+
+ /// \brief Check if rapid commit option used
+ ///
+ /// \return true if rapid commit option is used
+ bool isRapidCommit() const { return rapid_commit_; }
+
+ /// \brief Check if server-ID to be taken from first package
+ ///
+ /// \return true if server-iD to be taken from first package
+ bool isUseFirst() const { return use_first_; }
+
+ /// \brief Returns template file names
+ ///
+ /// \return template file names
+ std::vector<std::string> getTemplateFiles() const { return template_file_; }
+
+ /// brief Returns template offsets for xid
+ ///
+ /// \return template offsets for xid
+ std::vector<int> getTransactionIdOffset() const { return xid_offset_; }
+
+ /// \brief Returns template offsets for rnd
+ ///
+ /// \return template offsets for rnd
+ std::vector<int> getRandomOffset() const { return rnd_offset_; }
+
+ /// \brief Returns template offset for elapsed time
+ ///
+ /// \return template offset for elapsed time
+ int getElapsedTimeOffset() const { return elp_offset_; }
+
+ /// \brief Returns template offset for server-ID
+ ///
+ /// \return template offset for server-ID
+ int getServerIdOffset() const { return sid_offset_; }
+
+ /// \brief Returns template offset for requested IP
+ ///
+ /// \return template offset for requested IP
+ int getRequestedIpOffset() const { return rip_offset_; }
+
+ /// \brief Returns diagnostic selectors
+ ///
+ /// \return diagnostics selector
+ std::string getDiags() const { return diags_; }
+
+ /// \brief Returns wrapped command
+ ///
+ /// \return wrapped command (start/stop)
+ std::string getWrapped() const { return wrapped_; }
+
+ /// \brief Returns server name
+ ///
+ /// \return server name
+ std::string getServerName() const { return server_name_; }
+
+ /// \brief Print usage
+ ///
+ /// Prints perfdhcp usage
+ void usage() const;
+
+ /// \brief Print program version
+ ///
+ /// Prints perfdhcp version
+ void version() const;
+
+private:
+
+ /// \brief Default Constructor
+ ///
+ /// Private constructor as this is a singleton class.
+ /// Use CommandOptions::instance() to get instance of it.
+ CommandOptions() {
+ reset();
+ }
+
+ /// \brief Initializes class members based command line
+ ///
+ /// Reads each command line parameter and sets class member values
+ ///
+ /// \param argc Argument count passed to main().
+ /// \param argv Argument value array passed to main().
+ /// \throws isc::InvalidParameter if command line options initialization fails
+ void initialize(int argc, char** argv);
+
+ /// \brief Validates initialized options
+ ///
+ /// \throws isc::InvalidParameter if command line validation fails
+ void validate() const;
+
+ /// \brief Throws !InvalidParameter exception if condition is true
+ ///
+ /// Convenience function that throws an InvalidParameter exception if
+ /// the condition argument is true
+ ///
+ /// \param condition Condition to be checked
+ /// \param errmsg Error message in exception
+ /// \throws isc::InvalidParameter if condition argument true
+ inline void check(bool condition, const std::string& errmsg) const;
+
+ /// \brief Casts command line argument to positive integer
+ ///
+ /// \param errmsg Error message if lexical cast fails
+ /// \throw InvalidParameter if lexical cast fails
+ int positiveInteger(const std::string& errmsg) const;
+
+ /// \brief Casts command line argument to non-negative integer
+ ///
+ /// \param errmsg Error message if lexical cast fails
+ /// \throw InvalidParameter if lexical cast fails
+ int nonNegativeInteger(const std::string& errmsg) const;
+
+ /// \brief Returns command line string if it is not empty
+ ///
+ /// \param errmsg Error message if string is empty
+ /// \throw InvalidParameter if string is empty
+ std::string nonEmptyString(const std::string& errmsg) const;
+
+ /// \brief Set number of clients
+ ///
+ /// Interprets the getopt() "opt" global variable as the number of clients
+ /// (a non-negative number). This value is specified by the "-R" switch.
+ ///
+ /// \throw InvalidParameter if -R<value> is wrong
+ void initClientsNum();
+
+ /// \brief Decodes base provided with -b<base>
+ ///
+ /// Function decodes argument of -b switch, which
+ /// specifies a base value used to generate unique
+ /// mac or duid values in packets sent to system
+ /// under test.
+ /// The following forms of switch arguments are supported:
+ /// - -b mac=00:01:02:03:04:05
+ /// - -b duid=0F1234 (duid can be up to 128 hex digits)
+ // Function will decode 00:01:02:03:04:05 and/or
+ /// 0F1234 respectively and initialize mac_prefix_
+ /// and/or duid_prefix_ members
+ ///
+ /// \param base Base in string format
+ /// \throws isc::InvalidParameter if base is invalid
+ void decodeBase(const std::string& base);
+
+ /// \brief Decodes base MAC address provided with -b<base>
+ ///
+ /// Function decodes parameter given as -b mac=00:01:02:03:04:05
+ /// The function will decode 00:01:02:03:04:05 initialize mac_prefix_
+ /// class member.
+ /// Provided MAC address is for example only
+ ///
+ /// \param base Base string given as -b mac=00:01:02:03:04:05
+ /// \throws isc::InvalidParameter if mac address is invalid
+ void decodeMac(const std::string& base);
+
+ /// \brief Decodes base DUID provided with -b<base>
+ ///
+ /// Function decodes parameter given as -b duid=0F1234
+ /// The function will decode 0F1234 and initialize duid_prefix_
+ /// class member.
+ /// Provided DUID is for example only.
+ ///
+ /// \param base Base string given as -b duid=0F1234
+ /// \throws isc::InvalidParameter if DUID is invalid
+ void decodeDuid(const std::string& base);
+
+ /// \brief Converts two-digit hexadecimal string to a byte
+ ///
+ /// \param hex_text Hexadecimal string e.g. AF
+ /// \throw isc::InvalidParameter if string does not represent hex byte
+ uint8_t convertHexString(const std::string& hex_text) const;
+
+ uint8_t ipversion_; ///< IP protocol version to be used, expected values are:
+ ///< 4 for IPv4 and 6 for IPv6, default value 0 means "not set"
+ ExchangeMode exchange_mode_; ///< Packet exchange mode (e.g. DORA/SARR)
+ int rate_; ///< Rate in exchange per second
+ int report_delay_; ///< Delay between generation of two consecutive
+ ///< performance reports
+ uint32_t clients_num_; ///< Number of simulated clients (aka randomization range).
+ std::vector<uint8_t> mac_prefix_; ///< MAC address prefix used to generate unique DUIDs
+ ///< for simulated clients.
+ std::vector<uint8_t> duid_prefix_; ///< DUID prefix used to generate unique DUIDs for
+ ///< simulated clients
+ std::vector<std::string> base_; ///< Collection of base values specified with -b<value>
+ ///< options. Supported "bases" are mac=<mac> and duid=<duid>
+ std::vector<int> num_request_; ///< Number of 2 or 4-way exchanges to perform
+ int period_; ///< Test period in seconds
+ uint8_t drop_time_set_; ///< Indicates number of -d<value> parameters specified by user.
+ ///< If this value goes above 2, command line parsing fails.
+ std::vector<double> drop_time_; ///< Time to elapse before request is lost. The fisrt value of
+ ///< two-element vector refers to DO/SA exchanges,
+ ///< second value refers to RA/RR. Default values are { 1, 1 }
+ std::vector<int> max_drop_; ///< Maximum number of drops request before aborting test.
+ ///< First value of two-element vector specifies maximum
+ ///< number of drops for DO/SA exchange, second value
+ ///< specifies maximum number of drops for RA/RR.
+ std::vector<double> max_pdrop_; ///< Maximal percentage of lost requests before aborting test.
+ ///< First value of two-element vector specifies percentage for
+ ///< DO/SA exchanges, second value for RA/RR.
+ std::string localname_; ///< Local address or interface specified with -l<value> option.
+ bool is_interface_; ///< Indicates that specified value with -l<value> is
+ ///< rather interface (not address)
+ int preload_; ///< Number of preload packets. Preload packets are used to
+ ///< initiate communication with server before doing performance
+ ///< measurements.
+ int aggressivity_; ///< Number of exchanges sent before next pause.
+ int local_port_; ///< Local port number (host endian)
+ bool seeded_; ///< Indicates that randomization seed was provided.
+ uint32_t seed_; ///< Randomization seed.
+ bool broadcast_; ///< Indicates that we use broadcast address.
+ bool rapid_commit_; ///< Indicates that we do rapid commit option.
+ bool use_first_; ///< Indicates that we take server id from first received packet.
+ std::vector<std::string> template_file_; ///< Packet template file names. These files store template packets
+ ///< that are used for initiating echanges. Template packets
+ ///< read from files are later tuned with variable data.
+ std::vector<int> xid_offset_; ///< Offset of transaction id in template files. First vector
+ ///< element points to offset for DISCOVER/SOLICIT messages,
+ ///< second element points to trasaction id offset for
+ ///< REQUEST messages
+ std::vector<int> rnd_offset_; ///< Random value offset in templates. Random value offset
+ ///< points to last octet of DUID. Up to 4 last octets of
+ ///< DUID are randomized to simulate differnt clients.
+ int elp_offset_; ///< Offset of elapsed time option in template packet.
+ int sid_offset_; ///< Offset of server id option in template packet.
+ int rip_offset_; ///< Offset of requested ip data in template packet/
+ std::string diags_; ///< String representing diagnostic selectors specified
+ ///< by user with -x<value>.
+ std::string wrapped_; ///< Wrapped command specified as -w<value>. Expected
+ ///< values are start and stop.
+ std::string server_name_; ///< Server name specified as last argument of command line.
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_H
diff --git a/tests/tools/perfdhcp/localized_option.h b/tests/tools/perfdhcp/localized_option.h
new file mode 100644
index 0000000..5374684
--- /dev/null
+++ b/tests/tools/perfdhcp/localized_option.h
@@ -0,0 +1,123 @@
+// 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 __LOCALIZED_OPTION_H
+#define __LOCALIZED_OPTION_H
+
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief DHCP option at specific offset
+///
+/// This class represents DHCP option with data placed at specified
+/// offset in DHCP message.
+/// Objects of this type are intended to be used when DHCP packets
+/// are created from templates (e.g. read from template file).
+/// Such packets have number of options with contents that have to be
+/// replaced before sending: e.g. DUID can be randomized.
+/// If option of this type is added to \ref PerfPkt6 options collection,
+/// \ref perfdhcp::PerfPkt6 will call \ref getOffset on this object
+/// to retrieve user-defined option position and replace contents of
+/// the output buffer at this offset before packet is sent to the server.
+/// (\see perfdhcp::PerfPkt6::rawPack).
+/// In order to read on-wire data from incoming packet client class
+/// has to specify options of \ref perfdhcp::LocalizedOption type
+/// with expected offsets of these options in a packet. The
+/// \ref perfdhcp::PerfPkt6 will use offsets to read fragments
+/// of packet and store them in options' buffers.
+/// (\see perfdhcp::PerfPkt6::rawUnpack).
+///
+class LocalizedOption : public dhcp::Option {
+public:
+ /// \brief Constructor, sets default (0) option offset
+ ///
+ /// \param u specifies universe (V4 or V6)
+ /// \param type option type (0-255 for V4 and 0-65535 for V6)
+ /// \param data content of the option
+ LocalizedOption(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& data) :
+ dhcp::Option(u, type, data),
+ offset_(0) {
+ }
+
+
+ /// \brief Constructor, used to create localized option from buffer
+ ///
+ /// \param u specifies universe (V4 or V6)
+ /// \param type option type (0-255 for V4 and 0-65535 for V6)
+ /// \param data content of the option
+ /// \param offset location of option in a packet (zero is default)
+ LocalizedOption(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& data,
+ const size_t offset) :
+ dhcp::Option(u, type, data),
+ offset_(offset) {
+ }
+
+ /// \brief Constructor, sets default (0) option offset
+ ///
+ /// This contructor is similar to the previous one, but it does not take
+ /// the whole vector<uint8_t>, but rather subset of it.
+ ///
+ /// \param u specifies universe (V4 or V6)
+ /// \param type option type (0-255 for V4 and 0-65535 for V6)
+ /// \param first iterator to the first element that should be copied
+ /// \param last iterator to the next element after the last one
+ /// to be copied.
+ LocalizedOption(dhcp::Option::Universe u,
+ uint16_t type,
+ dhcp::OptionBufferConstIter first,
+ dhcp::OptionBufferConstIter last) :
+ dhcp::Option(u, type, first, last),
+ offset_(0) {
+ }
+
+
+ /// \brief Constructor, used to create option from buffer iterators
+ ///
+ /// This contructor is similar to the previous one, but it does not take
+ /// the whole vector<uint8_t>, but rather subset of it.
+ ///
+ /// \param u specifies universe (V4 or V6)
+ /// \param type option type (0-255 for V4 and 0-65535 for V6)
+ /// \param first iterator to the first element that should be copied
+ /// \param last iterator to the next element after the last one
+ /// to be copied.
+ /// \param offset offset of option in a packet (zero is default)
+ LocalizedOption(dhcp::Option::Universe u,
+ uint16_t type,
+ dhcp::OptionBufferConstIter first,
+ dhcp::OptionBufferConstIter last, const size_t offset) :
+ dhcp::Option(u, type, first, last),
+ offset_(offset) {
+ }
+
+ /// \brief Returns offset of an option in a DHCP packet.
+ ///
+ /// \return option offset in a packet
+ size_t getOffset() const { return offset_; };
+
+private:
+ size_t offset_; ///< Offset of DHCP option in a packet
+};
+
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __LOCALIZED_OPTION_H
diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc
new file mode 100644
index 0000000..3f733af
--- /dev/null
+++ b/tests/tools/perfdhcp/perf_pkt4.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/dhcp6.h>
+
+#include "perf_pkt4.h"
+#include "pkt_transform.h"
+
+using namespace std;
+using namespace isc;
+using namespace dhcp;
+
+namespace isc {
+namespace perfdhcp {
+
+PerfPkt4::PerfPkt4(const uint8_t* buf,
+ size_t len,
+ size_t transid_offset,
+ uint32_t transid) :
+ Pkt4(buf, len),
+ transid_offset_(transid_offset) {
+ setTransid(transid);
+}
+
+bool
+PerfPkt4::rawPack() {
+ return (PktTransform::pack(dhcp::Option::V4,
+ data_,
+ options_,
+ getTransidOffset(),
+ getTransid(),
+ bufferOut_));
+}
+
+bool
+PerfPkt4::rawUnpack() {
+ uint32_t transid = getTransid();
+ bool res = PktTransform::unpack(dhcp::Option::V4,
+ data_,
+ options_,
+ getTransidOffset(),
+ transid);
+ if (res) {
+ setTransid(transid);
+ }
+ return (res);
+}
+
+} // namespace perfdhcp
+} // namespace isc
diff --git a/tests/tools/perfdhcp/perf_pkt4.h b/tests/tools/perfdhcp/perf_pkt4.h
new file mode 100644
index 0000000..f4cc440
--- /dev/null
+++ b/tests/tools/perfdhcp/perf_pkt4.h
@@ -0,0 +1,113 @@
+// 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 __PERF_PKT4_H
+#define __PERF_PKT4_H
+
+#include <time.h>
+#include <boost/shared_ptr.hpp>
+#include <dhcp/pkt4.h>
+
+#include "localized_option.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief PerfPkt4 (DHCPv4 packet)
+///
+/// This class extends the functionality of \ref isc::dhcp::Pkt4 by adding the
+/// ability to specify an options offset in the DHCP message and to override
+/// options' contents. This is particularly useful when we create a packet
+/// object using a template file (i.e. do not build it dynamically). The client
+/// class should read data from the template file and pass it to this class in
+/// a buffer.
+///
+/// The contents of such a packet can be later partially replaced, notably the
+/// selected options and the transaction ID. (The transaction ID and its
+/// offset in the template file are passed via the constructor.)
+///
+/// In order to replace contents of the options, the client class has to
+/// create a collection of \ref LocalizedOption, adding them using
+/// \ref dhcp::Pkt4::addOption.
+///
+/// \note If you don't use template files simply use constructors
+/// inherited from parent class and isc::dhcp::Option type instead
+
+class PerfPkt4 : public dhcp::Pkt4 {
+public:
+
+ /// Localized option pointer type.
+ typedef boost::shared_ptr<LocalizedOption> LocalizedOptionPtr;
+
+ /// \brief Constructor, used to create messages from packet
+ /// template files.
+ ///
+ /// Creates a new DHCPv4 message using the provided buffer.
+ /// The transaction ID and its offset are specified via this
+ /// constructor. The transaction ID is stored in outgoing message
+ /// when client class calls \ref PerfPkt4::rawPack. Transaction id
+ /// offset value is used for incoming and outgoing messages to
+ /// identify transaction ID field's position in incoming and outgoing
+ /// messages.
+ ///
+ /// \param buf buffer holding contents of the message (this can
+ /// be directly read from template file).
+ /// \param len length of the data in the buffer.
+ /// \param transid_offset transaction id offset in a message.
+ /// \param transid transaction id to be stored in outgoing message.
+ PerfPkt4(const uint8_t* buf,
+ size_t len,
+ size_t transid_offset = 1,
+ uint32_t transid = 0);
+
+ /// \brief Returns transaction id offset in packet buffer
+ ///
+ /// \return Transaction ID offset in packet buffer
+ size_t getTransidOffset() const { return transid_offset_; };
+
+ /// \brief Prepares on-wire format from raw buffer.
+ ///
+ /// The method copies the buffer provided in the constructor to the
+ /// output buffer and replaces the transaction ID and selected
+ /// options with new data.
+ ///
+ /// \note Use this method to prepare an on-wire DHCPv4 message
+ /// when you use template packets that require replacement
+ /// of selected options' contents before sending.
+ ///
+ /// \return false ID pack operation failed.
+ bool rawPack();
+
+ /// \brief Handles limited binary packet parsing for packets with
+ /// custom offsets of options and transaction ID
+ ///
+ /// This method handles the parsing of packets that have custom offsets
+ /// of options or transaction ID. Use
+ /// \ref isc::dhcp::Pkt4::addOption to specify which options to parse.
+ /// Options should be of the \ref isc::perfdhcp::LocalizedOption
+ /// type with offset values provided. Each added option will
+ /// be updated with actual data read from the binary packet buffer.
+ ///
+ /// \return false If unpack operation failed.
+ bool rawUnpack();
+
+private:
+ size_t transid_offset_; ///< transaction id offset
+
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __PERF_PKT4_H
diff --git a/tests/tools/perfdhcp/perf_pkt6.cc b/tests/tools/perfdhcp/perf_pkt6.cc
new file mode 100644
index 0000000..24cfb93
--- /dev/null
+++ b/tests/tools/perfdhcp/perf_pkt6.cc
@@ -0,0 +1,64 @@
+// 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.
+
+#include <iostream>
+#include <exceptions/exceptions.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/dhcp6.h>
+
+#include "perf_pkt6.h"
+#include "pkt_transform.h"
+
+using namespace std;
+using namespace isc;
+using namespace dhcp;
+
+namespace isc {
+namespace perfdhcp {
+
+PerfPkt6::PerfPkt6(const uint8_t* buf,
+ size_t len,
+ size_t transid_offset,
+ uint32_t transid) :
+ Pkt6(buf, len, Pkt6::UDP),
+ transid_offset_(transid_offset) {
+ setTransid(transid);
+}
+
+bool
+PerfPkt6::rawPack() {
+ return (PktTransform::pack(dhcp::Option::V6,
+ data_,
+ options_,
+ getTransidOffset(),
+ getTransid(),
+ bufferOut_));
+}
+
+bool
+PerfPkt6::rawUnpack() {
+ uint32_t transid = getTransid();
+ bool res = PktTransform::unpack(dhcp::Option::V6,
+ data_,
+ options_,
+ getTransidOffset(),
+ transid);
+ if (res) {
+ setTransid(transid);
+ }
+ return (res);
+}
+
+} // namespace perfdhcp
+} // namespace isc
diff --git a/tests/tools/perfdhcp/perf_pkt6.h b/tests/tools/perfdhcp/perf_pkt6.h
new file mode 100644
index 0000000..94fe47b
--- /dev/null
+++ b/tests/tools/perfdhcp/perf_pkt6.h
@@ -0,0 +1,113 @@
+// 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 __PERF_PKT6_H
+#define __PERF_PKT6_H
+
+#include <time.h>
+#include <boost/shared_ptr.hpp>
+#include <dhcp/pkt6.h>
+
+#include "localized_option.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief PerfPkt6 (DHCPv6 packet)
+///
+/// This class extends the functionality of \ref isc::dhcp::Pkt6 by
+/// adding the ability to specify an options offset in the DHCP message
+/// and so override the options' contents. This is particularly useful when we
+/// create a packet object using a template file (i.e. do not build it
+/// dynamically). The client class should read the data from the template file
+/// and pass it to this class as a buffer.
+///
+/// The contents of such packet can be later partially replaced: in particular,
+/// selected options and the transaction ID can be altered. (The transaction
+/// ID and its offset in the template file is passed via the constructor.)
+///
+/// In order to replace the contents of options, the client class has to
+/// create a collection of \ref LocalizedOption by adding them using
+/// \ref dhcp::Pkt6::addOption.
+///
+/// \note If you don't use template files, simply use constructors
+/// inherited from parent class and the \ref isc::dhcp::Option type instead.
+
+class PerfPkt6 : public dhcp::Pkt6 {
+public:
+
+ /// Localized option pointer type.
+ typedef boost::shared_ptr<LocalizedOption> LocalizedOptionPtr;
+
+ /// \brief Constructor, used to create messages from packet
+ /// template files.
+ ///
+ /// Creates a new DHCPv6 message using the provided buffer.
+ /// The transaction ID and its offset are specified via this
+ /// constructor. The transaction ID is stored in outgoing message
+ /// when client class calls \ref PerfPkt6::rawPack. Transaction id
+ /// offset value is used for incoming and outgoing messages to
+ /// identify transaction ID field's position in incoming and outgoing
+ /// messages.
+ ///
+ /// \param buf buffer holding contents of the message (this can
+ /// be directly read from template file).
+ /// \param len length of the data in the buffer.
+ /// \param transid_offset transaction id offset in a message.
+ /// \param transid transaction id to be stored in outgoing message.
+ PerfPkt6(const uint8_t* buf,
+ size_t len,
+ size_t transid_offset = 1,
+ uint32_t transid = 0);
+
+ /// \brief Returns transaction id offset in packet buffer
+ ///
+ /// \return Transaction ID offset in the packet buffer.
+ size_t getTransidOffset() const { return transid_offset_; };
+
+ /// \brief Prepares on-wire format from raw buffer
+ ///
+ /// The method copies the buffer provided in constructor to the
+ /// output buffer and replaces the transaction ID and selected
+ /// options with new data.
+ ///
+ /// \note Use this method to prepare an on-wire DHCPv6 message
+ /// when you use template packets that require replacement
+ /// of selected options' contents before sending.
+ ///
+ /// \return false ID pack operation failed.
+ bool rawPack();
+
+ /// \brief Handles limited binary packet parsing for packets with
+ /// custom offsets of options and transaction id
+ ///
+ /// This methoid handles the parsing of packets that have custom offsets
+ /// of options or transaction ID. Use
+ /// \ref isc::dhcp::Pkt4::addOption to specify which options to parse.
+ /// Options should be of the \ref isc::perfdhcp::LocalizedOption
+ /// type with offset values provided. Each added option will
+ /// be updated with actual data read from the binary packet buffer.
+ ///
+ /// \return false if unpack operation failed.
+ bool rawUnpack();
+
+private:
+ size_t transid_offset_; ///< transaction id offset
+
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __PERF_PKT6_H
diff --git a/tests/tools/perfdhcp/perfdhcp.c b/tests/tools/perfdhcp/perfdhcp.c
new file mode 100644
index 0000000..3ab9a2e
--- /dev/null
+++ b/tests/tools/perfdhcp/perfdhcp.c
@@ -0,0 +1,3559 @@
+/*
+ * 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.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETIFADDRS
+
+/*
+ * Solaris 10 does not have the getifaddrs() function available (although it
+ * is present on Solaris 11 and later). For the present we will not implement
+ * a replacement (as we do with clock_gettime) as the implementation is
+ * relatively complex. Just output a message saying that the utility is not
+ * supported on this operating system.
+ */
+
+#include <stdio.h>
+
+int
+main(const int argc, char* const argv[])
+{
+ fprintf(stderr, "perfdhcp is not supported on this version of the operating system\n");
+ return (1);
+}
+
+#else
+
+/* getifaddrs() present, so the code should compile */
+
+#ifdef __linux__
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <math.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef HAVE_PSELECT
+
+#include <assert.h>
+
+/* Platforms such as OpenBSD don't provide a pselect(), so we use our
+ own implementation for this testcase, which wraps around select() and
+ hence doesn't implement the high precision timer. This implementation
+ is fine for our purpose. */
+
+static int
+pselect (int nfds, fd_set *readfds, fd_set *writefds,
+ fd_set *exceptfds, const struct timespec *timeout,
+ const sigset_t *sigmask)
+{
+ struct timeval my_timeout;
+
+ /* Our particular usage of pselect() doesn't use these fields. */
+ assert(writefds == NULL);
+ assert(exceptfds == NULL);
+ assert(sigmask == NULL);
+
+ my_timeout.tv_sec = timeout->tv_sec;
+ my_timeout.tv_usec = timeout->tv_nsec / 1000;
+
+ return (select(nfds, readfds, writefds, exceptfds, &my_timeout));
+}
+
+#endif /* !HAVE_PSELECT */
+
+/* DHCPv4 defines (to be moved/shared) */
+
+#define DHCP_OFF_OPCODE 0
+#define DHCP_OFF_HTYPE 1
+#define DHCP_OFF_HLEN 2
+#define DHCP_OFF_HOPS 3
+#define DHCP_OFF_XID 4
+#define DHCP_OFF_SECS 8
+#define DHCP_OFF_FLAGS 10
+#define DHCP_OFF_CIADDR 12
+#define DHCP_OFF_YIADDR 16
+#define DHCP_OFF_SADDR 20
+#define DHCP_OFF_GIADDR 24
+#define DHCP_OFF_CHADDR 28
+#define DHCP_OFF_SNAME 44
+#define DHCP_OFF_FILE 108
+#define DHCP_OFF_COOKIE 236
+#define DHCP_OFF_OPTIONS 240
+
+#define BOOTP_OP_REQUEST 1
+#define BOOTP_OP_REPLY 2
+#define BOOTP_MIN_LEN 300
+
+#define DHCP_OP_DISCOVER 1
+#define DHCP_OP_OFFER 2
+#define DHCP_OP_REQUEST 3
+#define DHCP_OP_DECLINE 4
+#define DHCP_OP_ACK 5
+#define DHCP_OP_NAK 6
+#define DHCP_OP_RELEASE 7
+#define DHCP_OP_INFORM 8
+
+#define DHCP_HTYPE_ETHER 1
+
+#define DHCP_OPT_PAD 0
+#define DHCP_OPT_SUBNET_MASK 1
+#define DHCP_OPT_TIME_OFFSET 2
+#define DHCP_OPT_ROUTERS 3
+#define DHCP_OPT_DNS_SERVERS 6
+#define DHCP_OPT_HOST_NAME 12
+#define DHCP_OPT_DOMAIN_NAME 15
+#define DHCP_OPT_BROADCAST 28
+#define DHCP_OPT_DHCP_ADDRESS 50
+#define DHCP_OPT_DHCP_LEASE 51
+#define DHCP_OPT_DHCP_MSGTYPE 53
+#define DHCP_OPT_DHCP_SRVID 54
+#define DHCP_OPT_DHCP_PRL 55
+#define DHCP_OPT_END 255
+
+#define DHCP_OPTLEN_SRVID 6
+
+/* DHCPv6 defines (to be moved/shared) */
+
+#define DHCP6_OFF_MSGTYP 0
+#define DHCP6_OFF_XID 1
+#define DHCP6_OFF_OPTIONS 4
+
+#define DHCP6_OP_SOLICIT 1
+#define DHCP6_OP_ADVERTISE 2
+#define DHCP6_OP_REQUEST 3
+#define DHCP6_OP_REPLY 7
+
+#define DHCP6_OPT_CLIENTID 1
+#define DHCP6_OPT_SERVERID 2
+#define DHCP6_OPT_IA_NA 3
+#define DHCP6_OPT_ORO 6
+#define DHCP6_OPT_ELAPSED_TIME 8
+#define DHCP6_OPT_STATUS_CODE 13
+#define DHCP6_OPT_RAPID_COMMIT 14
+#define DHCP6_OPT_NAME_SERVERS 23
+#define DHCP6_OPT_DOMAIN_SEARCH 24
+
+#define DHCP6_ST_SUCCESS 0
+#define DHCP6_ST_NOADDRSAVAIL 2
+
+#define DHCP6_DUID_LLT 1
+#define DHCP6_DUID_EPOCH 946684800
+
+/* tail queue macros (from FreeBSD 8.2 /sys/sys/queue.h, to be moved/shared) */
+
+#define ISC_TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; \
+ struct type **tqh_last; \
+}
+
+#define ISC_TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; \
+ struct type **tqe_prev; \
+}
+
+#define ISC_TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define ISC_TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define ISC_TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define ISC_TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define ISC_TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define ISC_TAILQ_INIT(head) do { \
+ ISC_TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &ISC_TAILQ_FIRST((head)); \
+} while (0)
+
+#define ISC_TAILQ_INSERT_HEAD(head, elm, field) do { \
+ ISC_TAILQ_NEXT((elm), field) = ISC_TAILQ_FIRST((head)); \
+ if (!ISC_TAILQ_EMPTY((head))) \
+ ISC_TAILQ_FIRST((head))->field.tqe_prev = \
+ &ISC_TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &ISC_TAILQ_NEXT((elm), field); \
+ ISC_TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &ISC_TAILQ_FIRST((head)); \
+} while (0)
+
+#define ISC_TAILQ_INSERT_TAIL(head, elm, field) do { \
+ ISC_TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &ISC_TAILQ_NEXT((elm), field); \
+} while (0)
+
+#define ISC_TAILQ_REMOVE(head, elm, field) do { \
+ if ((ISC_TAILQ_NEXT((elm), field)) != NULL) \
+ ISC_TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = ISC_TAILQ_NEXT((elm), field); \
+} while (0)
+
+#define ISC_TAILQ_FOREACH(var, head, field) \
+ for ((var) = ISC_TAILQ_FIRST((head)); \
+ (var); \
+ (var) = ISC_TAILQ_NEXT((var), field))
+
+#define ISC_TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = ISC_TAILQ_FIRST((head)); \
+ (var) && ((tvar) = ISC_TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+/*
+ * Data structures
+ */
+
+/*
+ * exchange:
+ * - per exchange values:
+ * * order (for debugging)
+ * * xid (even/odd for 4 packet exchanges)
+ * * random (for debugging)
+ * * time-stamps
+ * * server ID (for 3rd packet)
+ * * IA_NA (for IPv6 3rd packet)
+ *
+ * sent/rcvd global chains, "next to be received" on entry cache,
+ * and hash table for xid -> data structure fast matching
+ * (using the assumption collisions are unlikely, cf birthday problem)
+ */
+
+struct exchange { /* per exchange structure */
+ ISC_TAILQ_ENTRY(exchange) gchain; /* global chaining */
+ ISC_TAILQ_ENTRY(exchange) hchain; /* hash table chaining */
+ uint64_t order0, order2; /* number of this exchange */
+ uint32_t xid; /* transaction ID */
+ uint32_t rnd; /* random part */
+ struct timespec ts0, ts1, ts2, ts3; /* timespecs */
+ uint8_t *sid; /* server ID */
+ size_t sidlen; /* server ID length */
+ uint8_t *iana; /* (IPv6) IA_NA */
+ size_t ianalen; /* (IPv6) IA_NA length */
+};
+struct exchange *xnext0, *xnext2; /* next to be received */
+ISC_TAILQ_HEAD(xlist, exchange); /* exchange list */
+struct xlist xsent0, xsent2, xrcvd0, xrcvd2; /* sent and received lists */
+uint64_t xscount0, xscount2; /* sent counters */
+uint64_t xrcount0, xrcount2; /* received counters */
+caddr_t exchanges0, exchanges2; /* hash tables */
+uint32_t hashsize0, hashsize2; /* hash table sizes */
+
+/*
+ * statictics counters and accumulators
+ */
+
+uint64_t tooshort, orphans, locallimit; /* error counters */
+uint64_t latesent, compsend, latercvd; /* rate stats */
+uint64_t multrcvd, shortwait, collected[2]; /* rate stats (cont) */
+double dmin0 = 999999999., dmin2 = 999999999.; /* minimum delays */
+double dmax0 = 0., dmax2 = 0.; /* maximum delays */
+double dsum0 = 0., dsum2 = 0.; /* delay sums */
+double dsumsq0 = 0., dsumsq2 = 0.; /* square delay sums */
+
+/*
+ * command line parameters
+ */
+
+int ipversion = 0; /* IP version */
+int simple; /* DO/SA in place of DORR/SARR */
+int rate; /* rate in exchange per second */
+int report; /* delay between two reports */
+uint32_t range; /* randomization range */
+uint32_t maxrandom; /* maximum random value */
+int basecnt; /* base count */
+char *base[4]; /* bases */
+int gotnumreq; /* numreq[0] was set */
+int numreq[2]; /* number of exchange */
+int period; /* test period */
+int gotlosttime; /* losttime[0] was set */
+double losttime[2] = {1., 1.}; /* time after a request is lost */
+int gotmaxdrop; /* max{p}drop[0] was set */
+int maxdrop[2]; /* maximum number of lost requests */
+double maxpdrop[2] = { 0., 0.}; /* maximum percentage */
+char *localname; /* local address or interface */
+int isinterface; /* interface vs local address */
+int preload; /* preload exchanges */
+int aggressivity = 1; /* back to back exchanges */
+int localport; /* local port number (host endian) */
+int seeded; /* is a seed provided */
+unsigned int seed; /* randomization seed */
+int isbroadcast; /* use broadcast */
+int rapidcommit; /* add rapid commit option */
+int usefirst; /* where to take the server-ID */
+char *templatefile[2]; /* template file name */
+int xidoffset[2] = {-1, -1}; /* template offsets (xid)*/
+int rndoffset[2] = {-1, -1}; /* template offsets (random) */
+int elpoffset = -1; /* template offset (elapsed time) */
+int sidoffset = -1; /* template offset (server ID) */
+int ripoffset = -1; /* template offset (requested IP) */
+char *diags; /* diagnostic selectors */
+char *wrapped; /* wrapped command */
+char *servername; /* server */
+
+/*
+ * global variables
+ */
+
+struct sockaddr_storage localaddr; /* local socket address */
+struct sockaddr_storage serveraddr; /* server socket address */
+
+int sock; /* socket descriptor */
+int interrupted, fatal; /* to finish flags */
+
+uint8_t obuf[4096], ibuf[4096]; /* I/O buffers */
+char tbuf[8200]; /* template buffer */
+
+struct timespec boot; /* the date of boot */
+struct timespec last; /* the date of last send */
+struct timespec due; /* the date of next send */
+struct timespec dreport; /* the date of next reporting */
+struct timespec finished; /* the date of finish */
+
+uint8_t *gsrvid; /* global server id */
+size_t gsrvidlen; /* and its length */
+uint8_t gsrvidbuf[64]; /* and its storage */
+
+/* MAC address */
+uint8_t mac_prefix[6] = { 0x00, 0x0c, 0x01, 0x02, 0x03, 0x04 };
+
+/* DUID prefix */
+uint8_t *duid_prefix;
+int duid_length;
+
+/* magic cookie for BOOTP/DHCPv4 */
+uint8_t dhcp_cookie[4] = { 0x63, 0x82, 0x53, 0x63 };
+
+/*
+ * templates
+ *
+ * note: the only hard point is what are the offsets:
+ * - xid_discover4 and xid_request4: first of the 4 octet long
+ * transaction ID (default DHCP_OFF_XID = 4)
+ * - random_discover4 and random_request4: last of the 6 octet long
+ * MAC address (default DHCP_OFF_CHADDR + 6 = 28 + 6)
+ * - elapsed_request4: first of the 2 octet long secs field
+ * (default DHCP_OFF_SECS = 8, 0 means disabled)
+ * - serverid_request4: first of the 6 octet long server ID option
+ * (no default, required)
+ * - reqaddr_request4: first of the 4 octet long requested IP address
+ * option content (i.e., the address itself, btw OFFER yiaddr)
+ * (no default, required)
+ * - xid_solicit6 and xid_request6: first of the 3 octet long
+ * transaction ID (default DHCP6_OFF_XID = 1)
+ * - random_solicit6 and random_request6: last of the DUID in the
+ * client ID option (no default, required when rate is set)
+ * - elapsed_request6: first of the 2 octet long content of
+ * the option elapsed time option (no default, 0 means disabled)
+ * - serverid_request6: position where the variable length server ID
+ * option is inserted (no default, required, set to length means append)
+ * - reqaddr_request6: position where of the variable length requested
+ * IP address option is inserted (no default, required, set to
+ * length means append)
+ */
+
+size_t length_discover4;
+uint8_t template_discover4[4096];
+size_t xid_discover4;
+size_t random_discover4;
+size_t length_request4;
+uint8_t template_request4[4096];
+size_t xid_request4;
+size_t elapsed_request4;
+size_t random_request4;
+size_t serverid_request4;
+size_t reqaddr_request4;
+size_t length_solicit6;
+uint8_t template_solicit6[4096];
+size_t xid_solicit6;
+size_t random_solicit6;
+size_t length_request6;
+uint8_t template_request6[4096];
+size_t xid_request6;
+size_t elapsed_request6;
+size_t random_request6;
+size_t serverid_request6;
+size_t reqaddr_request6;
+
+
+// use definition of CLOCK_REALTIME (or lack of thereof) as an indicator
+// if the code is being compiled or Linux (or somewhere else)
+// Perhaps this should be based on OS_LINUX define?
+
+#if !defined (CLOCK_REALTIME)
+#define CLOCK_REALTIME 0
+
+/// @brief clock_gettime implementation for non-Linux systems
+///
+/// This implementation lacks nanosecond resolution. It is intended
+/// to be used on non-Linux systems that does not provide clock_gettime
+/// implementation.
+///
+/// @param clockid ignored (kept for Linux prototype compatibility)
+/// @param tp timespec structure
+///
+/// @return always zero (kept for compatibility reasons)
+int clock_gettime(int clockid, struct timespec *tp) {
+
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ tp->tv_sec = tv.tv_sec;
+ tp->tv_nsec = tv.tv_usec*1000;
+
+ return (0);
+}
+
+#endif
+
+/*
+ * initialize data structures handling exchanges
+ */
+
+void
+inits(void)
+{
+ struct xlist *bucket;
+ caddr_t p;
+ size_t len, i;
+
+ ISC_TAILQ_INIT(&xsent0);
+ ISC_TAILQ_INIT(&xsent2);
+ ISC_TAILQ_INIT(&xrcvd0);
+ ISC_TAILQ_INIT(&xrcvd2);
+
+ /// compute hashsizes
+ hashsize0 = 1024;
+ len = sizeof(*bucket) * hashsize0;
+ exchanges0 = malloc(len);
+ if (exchanges0 == NULL) {
+ perror("malloc(exchanges0)");
+ exit(1);
+ }
+ for (i = 0, p = exchanges0; i < hashsize0; i++, p += sizeof(*bucket)) {
+ bucket = (struct xlist *) p;
+ ISC_TAILQ_INIT(bucket);
+ }
+ if (simple != 0)
+ return;
+ hashsize2 = 1024;
+ len = sizeof(*bucket) * hashsize2;
+ exchanges2 = malloc(len);
+ if (exchanges2 == NULL) {
+ perror("malloc(exchanges2)");
+ exit(1);
+ }
+ for (i = 0, p = exchanges2; i < hashsize2; i++, p += sizeof(*bucket)) {
+ bucket = (struct xlist *) p;
+ ISC_TAILQ_INIT(bucket);
+ }
+}
+
+/*
+ * randomize the value of the given field:
+ * - offset of the field
+ * - random seed (used as it when suitable)
+ * - returns the random value which was used
+ */
+
+uint32_t
+randomize(size_t offset, uint32_t r)
+{
+ uint32_t v;
+
+ if (range == 0)
+ return 0;
+ if (range == UINT32_MAX)
+ return r;
+ if (maxrandom != 0)
+ while (r >= maxrandom)
+ r = (uint32_t) random();
+ r %= range + 1;
+ v = r;
+ v += obuf[offset];
+ obuf[offset] = v;
+ if (v < 256)
+ return r;
+ v >>= 8;
+ v += obuf[offset - 1];
+ obuf[offset - 1] = v;
+ if (v < 256)
+ return r;
+ v >>= 8;
+ v += obuf[offset - 2];
+ obuf[offset - 2] = v;
+ if (v < 256)
+ return r;
+ v >>= 8;
+ v += obuf[offset - 3];
+ obuf[offset - 3] = v;
+ return r;
+}
+
+/*
+ * receive a reply (4th packet), shared between IPv4 and IPv6:
+ * - transaction ID xid
+ * - receiving time-stamp now
+ * called from receive[46]() when the xid is odd
+ */
+
+void
+receive_reply(uint32_t xid, struct timespec *now)
+{
+ struct exchange *x, *t;
+ struct xlist *bucket;
+ uint32_t hash;
+ int checklost;
+ double delta;
+
+ /* bucket is needed even when the next cache matches */
+ hash = (xid >> 1) & (hashsize2 - 1);
+ bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
+ /* try the 'next to be received' cache */
+ if ((xnext2 != NULL) && (xnext2->xid == xid)) {
+ x = xnext2;
+ goto found;
+ }
+ /* usually the lost probability is low for request/reply */
+ checklost = 1;
+ /* look for the exchange */
+ ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
+ double waited;
+
+ if (x->xid == xid)
+ goto found;
+ if (checklost <= 0)
+ continue;
+ checklost = 0;
+ /* check for a timed-out exchange */
+ waited = now->tv_sec - x->ts2.tv_sec;
+ waited += (now->tv_nsec - x->ts2.tv_nsec) / 1e9;
+ if (waited < losttime[1])
+ continue;
+ /* garbage collect timed-out exchange */
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent2, x, gchain);
+ free(x);
+ collected[1] += 1;
+ }
+ /* no match? very late or not for us */
+ orphans++;
+ return;
+
+ /* got it: update stats and move to the received queue */
+ found:
+ xrcount2++;
+ x->ts3 = *now;
+ delta = x->ts3.tv_sec - x->ts2.tv_sec;
+ delta += (x->ts3.tv_nsec - x->ts2.tv_nsec) / 1e9;
+ if (delta < dmin2)
+ dmin2 = delta;
+ if (delta > dmax2)
+ dmax2 = delta;
+ dsum2 += delta;
+ dsumsq2 += delta * delta;
+ xnext2 = ISC_TAILQ_NEXT(x, gchain);
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent2, x, gchain);
+ ISC_TAILQ_INSERT_TAIL(&xrcvd2, x, gchain);
+}
+
+/*
+ * get the DHCPv4 socket descriptor
+ * (the only complexity is broadcast enabling: there is no easy way to
+ * recognize broadcast addresses, so the command line -B flag)
+ */
+
+void
+getsock4(void)
+{
+ int ret;
+
+ /* update local port */
+ if (localport != 0) {
+ uint16_t lp = htons((uint16_t) localport);
+
+ ((struct sockaddr_in *) &localaddr)->sin_port = lp;
+ }
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+ ret = bind(sock,
+ (struct sockaddr *) &localaddr,
+ sizeof(struct sockaddr_in));
+ if (ret < 0) {
+ perror("bind");
+ exit(1);
+ }
+ /* enable broadcast if needed or required */
+ if (isbroadcast != 0) {
+ int on = 1;
+
+ ret = setsockopt(sock,
+ SOL_SOCKET, SO_BROADCAST,
+ &on, sizeof(on));
+ if (ret < 0) {
+ perror("setsockopt(SO_BROADCAST)");
+ exit(1);
+ }
+ }
+}
+
+/*
+ * build a DHCPv4 DISCOVER from a relay template
+ * (implicit parameters are the local (giaddr) and MAC addresses (chaddr))
+ * (assume the link is Ethernet)
+ */
+
+void
+build_template_discover4(void)
+{
+ uint8_t *p = template_discover4;
+
+ length_discover4 = BOOTP_MIN_LEN;
+ xid_discover4 = DHCP_OFF_XID;
+ random_discover4 = DHCP_OFF_CHADDR + 6;
+ /* opcode */
+ p[DHCP_OFF_OPCODE] = BOOTP_OP_REQUEST;
+ /* hardware address type */
+ p[DHCP_OFF_HTYPE] = DHCP_HTYPE_ETHER;
+ /* hardware address length */
+ p[DHCP_OFF_HLEN] = 6;
+ /* hops */
+ p[DHCP_OFF_HOPS] = 1;
+ /* gateway address */
+ memcpy(p + DHCP_OFF_GIADDR,
+ &((struct sockaddr_in *) &localaddr)->sin_addr,
+ 4);
+ /* hardware address */
+ memcpy(p + DHCP_OFF_CHADDR, mac_prefix, 6);
+ /* cookie */
+ memcpy(p + DHCP_OFF_COOKIE, dhcp_cookie, 4);
+ /* options */
+ p += DHCP_OFF_OPTIONS;
+ /* inline DHCP message type */
+ *p++ = DHCP_OPT_DHCP_MSGTYPE;
+ *p++ = 1;
+ *p++ = DHCP_OP_DISCOVER;
+ /* inline DHCP parameter request list (default) */
+ *p++ = DHCP_OPT_DHCP_PRL;
+ *p++ = 7;
+ *p++ = DHCP_OPT_SUBNET_MASK;
+ *p++ = DHCP_OPT_BROADCAST;
+ *p++ = DHCP_OPT_TIME_OFFSET;
+ *p++ = DHCP_OPT_ROUTERS;
+ *p++ = DHCP_OPT_DOMAIN_NAME;
+ *p++ = DHCP_OPT_DNS_SERVERS;
+ *p++ = DHCP_OPT_HOST_NAME;
+ /* end */
+ *p = DHCP_OPT_END;
+}
+
+/*
+ * get a DHCPv4 client/relay first packet (usually a DISCOVER) template
+ * from the file given in the command line (-T<template-file>)
+ * and xid/rnd offsets (-X<xid-offset> and -O<random-offset>)
+ */
+
+void
+get_template_discover4(void)
+{
+ uint8_t *p = template_discover4;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[0], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(1);
+ }
+ if (cc < 100) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[0]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[0]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[0]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[0]);
+ exit(2);
+ }
+ length_discover4 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[0] >= 0)
+ xid_discover4 = (size_t) xidoffset[0];
+ else
+ xid_discover4 = DHCP_OFF_XID;
+ if (xid_discover4 + 4 > length_discover4) {
+ fprintf(stderr,
+ "xid (at %zu) outside the template (length %zu)?\n",
+ xid_discover4, length_discover4);
+ exit(2);
+ }
+ if (rndoffset[0] >= 0)
+ random_discover4 = (size_t) rndoffset[0];
+ else
+ random_discover4 = DHCP_OFF_CHADDR + 6;
+ if (random_discover4 > length_discover4) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_discover4, length_discover4);
+ exit(2);
+ }
+}
+
+/*
+ * build a DHCPv4 REQUEST from a relay template
+ * (implicit parameters are the local (giaddr) and MAC addresses (chaddr))
+ * (assume the link is Ethernet)
+ */
+
+void
+build_template_request4(void)
+{
+ uint8_t *p = template_request4;
+
+ length_request4 = BOOTP_MIN_LEN;
+ xid_request4 = DHCP_OFF_XID;
+ elapsed_request4 = DHCP_OFF_SECS;
+ random_request4 = DHCP_OFF_CHADDR + 6;
+ /* opcode */
+ p[DHCP_OFF_OPCODE] = BOOTP_OP_REQUEST;
+ /* hardware address type */
+ p[DHCP_OFF_HTYPE] = DHCP_HTYPE_ETHER;
+ /* hardware address length */
+ p[DHCP_OFF_HLEN] = 6;
+ /* hops */
+ p[DHCP_OFF_HOPS] = 1;
+ /* gateway address */
+ memcpy(p + DHCP_OFF_GIADDR,
+ &((struct sockaddr_in *) &localaddr)->sin_addr,
+ 4);
+ /* hardware address */
+ memcpy(p + DHCP_OFF_CHADDR, mac_prefix, 6);
+ /* cookie */
+ memcpy(p + DHCP_OFF_COOKIE, dhcp_cookie, 4);
+ /* options */
+ p += DHCP_OFF_OPTIONS;
+ /* inline DHCP message type */
+ *p++ = DHCP_OPT_DHCP_MSGTYPE;
+ *p++ = 1;
+ *p++ = DHCP_OP_REQUEST;
+ /* place for DHCP server id (option) */
+ serverid_request4 = p - template_request4;
+ p += DHCP_OPTLEN_SRVID;
+ /* place for DHCP requested IP address (address) */
+ *p++ = DHCP_OPT_DHCP_ADDRESS;
+ *p++ = 4;
+ reqaddr_request4 = p - template_request4;
+ p += 4;
+ /* inline DHCP parameter request list (default) */
+ *p++ = DHCP_OPT_DHCP_PRL;
+ *p++ = 7;
+ *p++ = DHCP_OPT_SUBNET_MASK;
+ *p++ = DHCP_OPT_BROADCAST;
+ *p++ = DHCP_OPT_TIME_OFFSET;
+ *p++ = DHCP_OPT_ROUTERS;
+ *p++ = DHCP_OPT_DOMAIN_NAME;
+ *p++ = DHCP_OPT_DNS_SERVERS;
+ *p++ = DHCP_OPT_HOST_NAME;
+ /* end */
+ *p = DHCP_OPT_END;
+}
+
+/*
+ * get a DHCPv4 client/relay third packet (usually a REQUEST) template
+ * from the file given in the command line (-T<template-file>)
+ * and offsets (-X,-O,-E,-S,-I).
+ */
+
+void
+get_template_request4(void)
+{
+ uint8_t *p = template_request4;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[1], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(1);
+ }
+ if (cc < 100) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[1]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[1]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[1]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[1]);
+ exit(2);
+ }
+ length_request4 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[1] >= 0)
+ xid_request4 = (size_t) xidoffset[1];
+ else
+ xid_request4 = DHCP_OFF_XID;
+ if (xid_request4 + 4 > length_request4) {
+ fprintf(stderr,
+ "xid (at %zu) outside the template (length %zu)?\n",
+ xid_request4, length_request4);
+ exit(2);
+ }
+ if (rndoffset[1] >= 0)
+ random_request4 = (size_t) rndoffset[1];
+ else
+ random_request4 = DHCP_OFF_CHADDR + 6;
+ if (random_request4 > length_request4) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_request4, length_request4);
+ exit(2);
+ }
+ if (elpoffset >= 0)
+ elapsed_request4 = (size_t) elpoffset;
+ else
+ elapsed_request4 = DHCP_OFF_SECS;
+ if (elapsed_request4 + 2 > length_request4) {
+ fprintf(stderr,
+ "secs (at %zu) outside the template (length %zu)?\n",
+ elapsed_request4, length_request4);
+ exit(2);
+ }
+ serverid_request4 = (size_t) sidoffset;
+ if (serverid_request4 + 6 > length_request4) {
+ fprintf(stderr,
+ "server-id option (at %zu) outside the template "
+ "(length %zu)?\n",
+ serverid_request4, length_request4);
+ exit(2);
+ }
+ reqaddr_request4 = (size_t) ripoffset;
+ if (reqaddr_request4 + 4 > length_request4) {
+ fprintf(stderr,
+ "requested-ip-address option (at %zu) outside "
+ "the template (length %zu)?\n",
+ reqaddr_request4, length_request4);
+ exit(2);
+ }
+}
+
+/*
+ * send the DHCPv4 REQUEST third packet
+ * (the transaction ID is odd)
+ * (TODO: check for errors in the OFFER)
+ */
+
+void
+send_request4(struct exchange *x0)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL) {
+ locallimit++;
+ perror("send2");
+ return;
+ }
+
+ memcpy(x, x0, sizeof(*x));
+ x->order2 = xscount2++;
+ x->xid |= 1;
+ hash = x->xid >> 1;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent2, x, gchain);
+ hash &= hashsize2 - 1;
+ bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ memcpy(obuf, template_request4, length_request4);
+ /* xid */
+ memcpy(obuf + xid_request4, &x->xid, 4);
+ /* random */
+ randomize(random_request4, x->rnd);
+ /* secs */
+ if (elapsed_request4 > 0) {
+ int secs;
+
+ secs = x->ts1.tv_sec - x->ts0.tv_sec;
+ if (x->ts1.tv_nsec < x->ts0.tv_nsec)
+ secs += 1;
+ if (secs > 0) {
+ obuf[elapsed_request4] = secs >> 8;
+ obuf[elapsed_request4 + 1] = secs & 0xff;
+ }
+ }
+ /* server ID */
+ memcpy(obuf + serverid_request4, x->sid, x->sidlen);
+ /* requested IP address */
+ memcpy(obuf + reqaddr_request4, ibuf + DHCP_OFF_YIADDR, 4);
+
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &x->ts2);
+ if (ret < 0) {
+ perror("clock_gettime(send2)");
+ fatal = 1;
+ return;
+ }
+ ret = sendto(sock, obuf, length_request4, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in));
+ if (ret >= 0)
+ return;
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) || (errno == ENOMEM))
+ locallimit++;
+ perror("send2");
+}
+
+/*
+ * send the DHCPv4 DISCOVER first packet
+ * (for 4-exchange, the transaction ID xid is even)
+ */
+
+int
+send4(void)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL)
+ return -ENOMEM;
+
+ memset(x, 0, sizeof(*x));
+ x->order0 = xscount0++;
+ hash = x->rnd = (uint32_t) random();
+ if (simple == 0)
+ x->xid = hash << 1;
+ else
+ x->xid = hash;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent0, x, gchain);
+ hash &= hashsize0 - 1;
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ memcpy(obuf, template_discover4, length_discover4);
+ /* xid */
+ memcpy(obuf + xid_discover4, &x->xid, 4);
+ /* random */
+ x->rnd = randomize(random_discover4, x->rnd);
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &last);
+ if (ret < 0) {
+ perror("clock_gettime(send)");
+ fatal = 1;
+ return -errno;
+ }
+ x->ts0 = last;
+ errno = 0;
+ ret = sendto(sock, obuf, length_discover4, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in));
+ if (ret == (ssize_t) length_discover4)
+ return 0;
+ return -errno;
+}
+
+/*
+ * scan a DHCPv4 OFFER to get its server-id option
+ */
+
+int
+scan_for_srvid4(struct exchange *x, size_t cc)
+{
+ size_t off = DHCP_OFF_OPTIONS;
+
+ for (;;) {
+ if (off + DHCP_OPTLEN_SRVID > cc) {
+ fprintf(stderr, "truncated\n");
+ return -1;
+ }
+ if (ibuf[off] == DHCP_OPT_DHCP_SRVID)
+ break;
+ if (ibuf[off] == DHCP_OPT_END) {
+ fprintf(stderr, "server-id not found\n");
+ return -1;
+ }
+ if (ibuf[off] == DHCP_OPT_PAD) {
+ off++;
+ continue;
+ }
+ off += 2 + ibuf[off + 1];
+ }
+ /* check length */
+ if (ibuf[off + 1] != DHCP_OPTLEN_SRVID - 2) {
+ fprintf(stderr,
+ "bad server-id length (%hhu)\n",
+ ibuf[off + 1]);
+ return -1;
+ }
+ /* cache it in the global variables when required and not yet done */
+ if ((usefirst != 0) && (gsrvid == NULL)) {
+ memcpy(gsrvidbuf, ibuf + off, DHCP_OPTLEN_SRVID);
+ gsrvid = gsrvidbuf;
+ gsrvidlen = DHCP_OPTLEN_SRVID;
+ }
+ x->sid = ibuf + off;
+ x->sidlen = DHCP_OPTLEN_SRVID;
+ return 0;
+}
+
+/*
+ * receive a DHCPv4 packet
+ */
+
+void
+receive4(void)
+{
+ struct exchange *x, *t;
+ struct xlist *bucket;
+ struct timespec now;
+ ssize_t cc;
+ uint32_t xid, hash;
+ int checklost = 0;
+ double delta;
+
+ cc = recv(sock, ibuf, sizeof(ibuf), 0);
+ if (cc < 0) {
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == EINTR))
+ return;
+ perror("recv");
+ fatal = 1;
+ return;
+ }
+ /* enforce a reasonable length */
+ if (cc < BOOTP_MIN_LEN) {
+ tooshort++;
+ return;
+ }
+ /* must be a BOOTP REPLY */
+ if (ibuf[DHCP_OFF_OPCODE] != BOOTP_OP_REPLY)
+ return;
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(receive)");
+ fatal = 1;
+ return;
+ }
+ memcpy(&xid, ibuf + xid_discover4, 4);
+ /* 4-packet exchange even/odd xid */
+ if (simple == 0) {
+ if ((xid & 1) != 0) {
+ receive_reply(xid, &now);
+ return;
+ }
+ hash = (xid >> 1) & (hashsize0 - 1);
+ } else
+ hash = xid & (hashsize0 - 1);
+ /* now it is the second packet, get the bucket which is needed */
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ /* try the 'next to be received' cache */
+ if ((xnext0 != NULL) && (xnext0->xid == xid)) {
+ x = xnext0;
+ goto found;
+ }
+ /* if the rate is not illimited, garbage collect up to 3
+ timed-out exchanges */
+ if (rate != 0)
+ checklost = 3;
+ /* look for the exchange */
+ ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
+ double waited;
+
+ if (x->xid == xid)
+ goto found;
+ if (checklost <= 0)
+ continue;
+ /* check for a timed-out exchange */
+ waited = now.tv_sec - x->ts0.tv_sec;
+ waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (waited < losttime[0]) {
+ checklost = 0;
+ continue;
+ }
+ /* garbage collect timed-out exchange */
+ checklost--;
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ free(x);
+ collected[0] += 1;
+ }
+ /* no match? very late or not for us */
+ orphans++;
+ return;
+
+ /* got it: update stats and move to the received queue */
+ found:
+ xrcount0++;
+ x->ts1 = now;
+ delta = x->ts1.tv_sec - x->ts0.tv_sec;
+ delta += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (delta < dmin0)
+ dmin0 = delta;
+ if (delta > dmax0)
+ dmax0 = delta;
+ dsum0 += delta;
+ dsumsq0 += delta * delta;
+ xnext0 = ISC_TAILQ_NEXT(x, gchain);
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ ISC_TAILQ_INSERT_TAIL(&xrcvd0, x, gchain);
+ /* if the exchange is not finished, go to the second part */
+ if (simple == 0) {
+ int ret = 0;
+
+ /* the server-ID option is needed */
+ if ((usefirst != 0) && (gsrvid != NULL)) {
+ x->sid = gsrvid;
+ x->sidlen = gsrvidlen;
+ } else
+ ret = scan_for_srvid4(x, cc);
+ if (ret >= 0)
+ send_request4(x);
+ }
+}
+
+/*
+ * get the DHCPv6 socket descriptor
+ */
+
+void
+getsock6(void)
+{
+ struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &serveraddr;
+ int ret;
+
+ /* update local port */
+ if (localport != 0) {
+ uint16_t lp = htons((uint16_t) localport);
+
+ ((struct sockaddr_in6 *) &localaddr)->sin6_port = lp;
+ }
+ sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+ ret = bind(sock,
+ (struct sockaddr *) &localaddr,
+ sizeof(struct sockaddr_in6));
+ if (ret < 0) {
+ perror("Failed to bind v6 socket to local-link address");
+ exit(1);
+ }
+ /* perform the multicast stuff when the destination is multicast */
+ if (IN6_IS_ADDR_MULTICAST(&s6->sin6_addr)) {
+ int hops = 1;
+
+ ret = setsockopt(sock,
+ IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &hops, sizeof(hops));
+ if (ret < 0) {
+ perror("setsockopt(IPV6_MULTICAST_HOPS)");
+ exit(1);
+ }
+ }
+ if (isinterface && IN6_IS_ADDR_MULTICAST(&s6->sin6_addr)) {
+ unsigned idx = if_nametoindex(localname);
+
+ if (idx == 0) {
+ fprintf(stderr,
+ "if_nametoindex(%s) failed\n",
+ localname);
+ exit(1);
+ }
+ ret = setsockopt(sock,
+ IPPROTO_IPV6, IPV6_MULTICAST_IF,
+ &idx, sizeof(idx));
+ if (ret < 0) {
+ perror("setsockopt(IPV6_MULTICAST_IF)");
+ exit(1);
+ }
+ }
+}
+
+/*
+ * build a DHCPv6 SOLICIT template
+ * (implicit parameter is the DUID, don't assume an Ethernet link)
+ */
+
+void
+build_template_solicit6(void)
+{
+ uint8_t *p = template_solicit6;
+
+ xid_solicit6 = DHCP6_OFF_XID;
+ /* message type */
+ p[DHCP6_OFF_MSGTYP] = DHCP6_OP_SOLICIT;
+ /* options */
+ p += DHCP6_OFF_OPTIONS;
+ /* elapsed time */
+ p[1] = DHCP6_OPT_ELAPSED_TIME;
+ p[3] = 2;
+ p += 6;
+ /* rapid commit */
+ if (rapidcommit != 0) {
+ p[1] = DHCP6_OPT_RAPID_COMMIT;
+ p += 4;
+ }
+ /* client ID */
+ p[1] = DHCP6_OPT_CLIENTID;
+ p[3] = duid_length;
+ memcpy(p + 4, duid_prefix, duid_length);
+ p += 4 + duid_length;
+ random_solicit6 = p - template_solicit6;
+ /* option request option */
+ p[1] = DHCP6_OPT_ORO;
+ p[3] = 4;
+ p[5] = DHCP6_OPT_NAME_SERVERS;
+ p[7] = DHCP6_OPT_DOMAIN_SEARCH;
+ p += 8;
+ /* IA_NA (IAID = 1, T1 = 3600, T2 = 5400) */
+ p[1] = DHCP6_OPT_IA_NA;
+ p[3] = 12;
+ p[7] = 1;
+ p[10] = 3600 >> 8;
+ p[11] = 3600 & 0xff;
+ p[14] = 5400 >> 8;
+ p[15] = 5400 & 0xff;
+ p += 16;
+ /* set length */
+ length_solicit6 = p - template_solicit6;
+}
+
+/*
+ * get a DHCPv6 first packet (usually a SOLICIT) template
+ * from the file given in the command line (-T<template-file>)
+ * and xid/rnd offsets (-X<xid-offset> and -O<random-offset>)
+ */
+
+void
+get_template_solicit6(void)
+{
+ uint8_t *p = template_solicit6;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[0], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(1);
+ }
+ if (cc < 10) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[0]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[0]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[0]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[0]);
+ exit(2);
+ }
+ length_solicit6 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[0] >= 0)
+ xid_solicit6 = (size_t) xidoffset[0];
+ else
+ xid_solicit6 = DHCP6_OFF_XID;
+ if (xid_solicit6 + 3 > length_solicit6) {
+ fprintf(stderr,
+ "xid (at %zu) is outside the template (length %zu)?\n",
+ xid_solicit6, length_solicit6);
+ exit(2);
+ }
+ if (rndoffset[0] >= 0)
+ random_solicit6 = (size_t) rndoffset[0];
+ else
+ random_solicit6 = 0;
+ if (random_solicit6 > length_solicit6) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_solicit6, length_solicit6);
+ exit(2);
+ }
+}
+
+/*
+ * build a DHCPv6 REQUEST template
+ * (implicit parameter is the DUID, don't assume an Ethernet link)
+ */
+
+void
+build_template_request6(void)
+{
+ uint8_t *p = template_request6;
+
+ xid_request6 = DHCP6_OFF_XID;
+ /* message type */
+ p[DHCP6_OFF_MSGTYP] = DHCP6_OP_REQUEST;
+ /* options */
+ p += DHCP6_OFF_OPTIONS;
+ /* elapsed time */
+ p[1] = DHCP6_OPT_ELAPSED_TIME;
+ p[3] = 2;
+ p += 4;
+ elapsed_request6 = p - template_request6;
+ p += 2;
+ /* client ID */
+ p[1] = DHCP6_OPT_CLIENTID;
+ p[3] = duid_length;
+ memcpy(p + 4, duid_prefix, duid_length);
+ p += 4 + duid_length;
+ random_request6 = p - template_request6;
+ /* option request option */
+ p[1] = DHCP6_OPT_ORO;
+ p[3] = 4;
+ p[5] = DHCP6_OPT_NAME_SERVERS;
+ p[7] = DHCP6_OPT_DOMAIN_SEARCH;
+ p += 8;
+ /* server ID and IA_NA */
+ serverid_request6 = p - template_request6;
+ reqaddr_request6 = p - template_request6;
+ /* set length */
+ length_request6 = p - template_request6;
+}
+
+/*
+ * get a DHCPv6 third packet (usually a REQUEST) template
+ * from the file given in the command line (-T<template-file>)
+ * and offsets (-X,-O,-E,-S,-I).
+ */
+
+void
+get_template_request6(void)
+{
+ uint8_t *p = template_request6;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[1], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(1);
+ }
+ if (cc < 10) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[1]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[1]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[1]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[1]);
+ exit(2);
+ }
+ length_request6 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[1] >= 0)
+ xid_request6 = (size_t) xidoffset[1];
+ else
+ xid_request6 = DHCP6_OFF_XID;
+ if (xid_request6 + 3 > length_request6) {
+ fprintf(stderr,
+ "xid (at %zu) is outside the template (length %zu)?\n",
+ xid_request6, length_request6);
+ exit(2);
+ }
+ if (rndoffset[1] >= 0)
+ random_request6 = (size_t) rndoffset[1];
+ else
+ random_request6 = 0;
+ if (random_request6 > length_request6) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_request6, length_request6);
+ exit(2);
+ }
+ if (elpoffset >= 0)
+ elapsed_request6 = (size_t) elpoffset;
+ if (elapsed_request6 + 2 > length_request6) {
+ fprintf(stderr,
+ "secs (at %zu) outside the template (length %zu)?\n",
+ elapsed_request6, length_request6);
+ exit(2);
+ }
+ serverid_request6 = (size_t) sidoffset;
+ if (serverid_request6 > length_request6) {
+ fprintf(stderr,
+ "server-id option (at %zu) outside the template "
+ "(length %zu)?\n",
+ serverid_request6, length_request6);
+ exit(2);
+ }
+ reqaddr_request6 = (size_t) ripoffset;
+ if (reqaddr_request6 > length_request6) {
+ fprintf(stderr,
+ "requested-ip-address option (at %zu) outside "
+ "the template (length %zu)?\n",
+ reqaddr_request6, length_request6);
+ exit(2);
+ }
+}
+
+/*
+ * send the DHCPv6 REQUEST third packet
+ * (the transaction ID is odd)
+ * (TODO: check for errors in the ADVERTISE)
+ */
+
+void
+send_request6(struct exchange *x0)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ size_t len;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL) {
+ locallimit++;
+ perror("send2");
+ return;
+ }
+
+ memcpy(x, x0, sizeof(*x));
+ x->order2 = xscount2++;
+ x->xid |= 1;
+ hash = x->xid >> 1;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent2, x, gchain);
+ hash &= hashsize2 - 1;
+ bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ len = length_request6;
+ memcpy(obuf, template_request6, len);
+ /* xid */
+ obuf[xid_request6] = x->xid >> 16;
+ obuf[xid_request6 + 1] = x->xid >> 8;
+ obuf[xid_request6 + 2] = x->xid;
+ /* random */
+ randomize(random_request6, x->rnd);
+ /* elapsed time */
+ if (elapsed_request6 > 0) {
+ int et;
+
+ et = (x->ts1.tv_sec - x->ts0.tv_sec) * 100;
+ et += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 10000000;
+ if (et > 65535) {
+ obuf[elapsed_request6] = 0xff;
+ obuf[elapsed_request6 + 1] = 0xff;
+ } else if (et > 0) {
+ obuf[elapsed_request6] = et >> 8;
+ obuf[elapsed_request6 + 1] = et & 0xff;
+ }
+ }
+ /* server ID */
+ if (serverid_request6 < length_request6)
+ memmove(obuf + serverid_request6 + x->sidlen,
+ obuf + serverid_request6,
+ x->sidlen);
+ memcpy(obuf + serverid_request6, x->sid, x->sidlen);
+ len += x->sidlen;
+ /* IA_NA */
+ if (reqaddr_request6 < serverid_request6) {
+ memmove(obuf + reqaddr_request6 + x->ianalen,
+ obuf + reqaddr_request6,
+ x->ianalen);
+ memcpy(obuf + reqaddr_request6, x->iana, x->ianalen);
+ } else if (reqaddr_request6 < length_request6) {
+ memmove(obuf + reqaddr_request6 + x->sidlen + x->ianalen,
+ obuf + reqaddr_request6 + x->sidlen,
+ x->ianalen);
+ memcpy(obuf + reqaddr_request6+ x->sidlen,
+ x->iana,
+ x->ianalen);
+ } else
+ memcpy(obuf + len, x->iana, x->ianalen);
+ len += x->ianalen;
+
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &x->ts2);
+ if (ret < 0) {
+ perror("clock_gettime(send2)");
+ fatal = 1;
+ return;
+ }
+ ret = sendto(sock, obuf, len, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in6));
+ if (ret >= 0)
+ return;
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) || (errno == ENOMEM))
+ locallimit++;
+ perror("send2");
+}
+
+/*
+ * send the DHCPv6 SOLICIT first packet
+ * (for 4-exchange, the transaction ID xid is even)
+ */
+
+int
+send6(void)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL)
+ return -ENOMEM;
+
+ memset(x, 0, sizeof(*x));
+ x->order0 = xscount0++;
+ hash = x->rnd = (uint32_t) random();
+ if (simple == 0)
+ x->xid = (hash << 1) & 0x00ffffff;
+ else
+ x->xid = hash & 0x00ffffff;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent0, x, gchain);
+ hash &= hashsize0 - 1;
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ memcpy(obuf, template_solicit6, length_solicit6);
+ /* xid */
+ obuf[xid_solicit6] = x->xid >> 16;
+ obuf[xid_solicit6 + 1] = x->xid >> 8;
+ obuf[xid_solicit6 + 2] = x->xid;
+ /* random */
+ x->rnd = randomize(random_solicit6, x->rnd);
+
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &last);
+ if (ret < 0) {
+ perror("clock_gettime(send)");
+ fatal = 1;
+ return -errno;
+ }
+ x->ts0 = last;
+ errno = 0;
+ ret = sendto(sock, obuf, length_solicit6, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in6));
+ if (ret == (ssize_t) length_solicit6)
+ return 0;
+ return -errno;
+}
+
+/*
+ * scan a DHCPv6 ADVERTISE to get its server-id option
+ */
+
+int
+scan_for_srvid6(struct exchange *x, size_t cc)
+{
+ size_t off = DHCP6_OFF_OPTIONS, len;
+
+ for (;;) {
+ if (off + 4 > cc) {
+ fprintf(stderr, "server-id not found\n");
+ return -1;
+ }
+ if ((ibuf[off] == 0) && (ibuf[off + 1] == DHCP6_OPT_SERVERID))
+ break;
+ off += 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ }
+ len = 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ /* cache it in the global variables when required and not yet done */
+ if ((usefirst != 0) && (gsrvid == NULL)) {
+ if (len <= sizeof(gsrvidbuf)) {
+ memcpy(gsrvidbuf, ibuf + off, len);
+ gsrvid = gsrvidbuf;
+ gsrvidlen = len;
+ } else {
+ gsrvid = (uint8_t *) malloc(len);
+ if (gsrvid == NULL) {
+ perror("malloc(gsrvid");
+ return -1;
+ }
+ memcpy(gsrvid, ibuf + off, len);
+ gsrvidlen = len;
+ }
+ }
+ x->sid = ibuf + off;
+ x->sidlen = len;
+ return 0;
+}
+
+/*
+ * scan a DHCPv6 ADVERTISE to get its IA_NA option
+ * (TODO: check for errors)
+ */
+
+int
+scan_for_ia_na(struct exchange *x, size_t cc)
+{
+ size_t off = DHCP6_OFF_OPTIONS, len;
+
+ for (;;) {
+ if (off + 4 > cc) {
+ fprintf(stderr, "ia-na not found\n");
+ return -1;
+ }
+ if ((ibuf[off] == 0) && (ibuf[off + 1] == DHCP6_OPT_IA_NA))
+ break;
+ off += 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ }
+ len = 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ x->iana = ibuf + off;
+ x->ianalen = len;
+ return 0;
+}
+
+/*
+ * receive a DHCPv6 packet
+ */
+
+void
+receive6(void)
+{
+ struct exchange *x, *t;
+ struct xlist *bucket;
+ struct timespec now;
+ ssize_t cc;
+ uint32_t xid, hash;
+ int checklost = 0;
+ double delta;
+
+ cc = recv(sock, ibuf, sizeof(ibuf), 0);
+ if (cc < 0) {
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == EINTR))
+ return;
+ perror("recv");
+ fatal = 1;
+ return;
+ }
+ /* enforce a reasonable length */
+ if (cc < 22) {
+ tooshort++;
+ return;
+ }
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(receive)");
+ fatal = 1;
+ return;
+ }
+ xid = ibuf[xid_solicit6] << 16;
+ xid |= ibuf[xid_solicit6 + 1] << 8;
+ xid |= ibuf[xid_solicit6 + 2];
+ /* 4-packet exchange even/odd xid */
+ if (simple == 0) {
+ if ((xid & 1) != 0) {
+ receive_reply(xid, &now);
+ return;
+ }
+ hash = (xid >> 1) & (hashsize0 - 1);
+ } else
+ hash = xid & (hashsize0 - 1);
+ /* now it is the second packet, get the bucket which is needed */
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ /* try the 'next to be received' cache */
+ if ((xnext0 != NULL) && (xnext0->xid == xid)) {
+ x = xnext0;
+ goto found;
+ }
+ /* if the rate is not illimited, garbage collect up to 3
+ timed-out exchanges */
+ if (rate != 0)
+ checklost = 3;
+ /* look for the exchange */
+ ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
+ double waited;
+
+ if (x->xid == xid)
+ goto found;
+ if (checklost <= 0)
+ continue;
+ /* check for a timed-out exchange */
+ waited = now.tv_sec - x->ts0.tv_sec;
+ waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (waited < losttime[0]) {
+ checklost = 0;
+ continue;
+ }
+ /* garbage collect timed-out exchange */
+ checklost--;
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ free(x);
+ collected[0] += 1;
+ }
+ /* no match? very late or not for us */
+ orphans++;
+ return;
+
+ /* got it: update stats and move to the received queue */
+ found:
+ xrcount0++;
+ x->ts1 = now;
+ delta = x->ts1.tv_sec - x->ts0.tv_sec;
+ delta += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (delta < dmin0)
+ dmin0 = delta;
+ if (delta > dmax0)
+ dmax0 = delta;
+ dsum0 += delta;
+ dsumsq0 += delta * delta;
+ xnext0 = ISC_TAILQ_NEXT(x, gchain);
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ ISC_TAILQ_INSERT_TAIL(&xrcvd0, x, gchain);
+ /* if the exchange is not finished, go to the second part */
+ if (simple == 0) {
+ int ret = 0;
+
+ /* the server-ID option is needed */
+ if ((usefirst != 0) && (gsrvid != NULL)) {
+ x->sid = gsrvid;
+ x->sidlen = gsrvidlen;
+ } else
+ ret = scan_for_srvid6(x, cc);
+ /* and the IA_NA option too */
+ if (ret >= 0)
+ ret = scan_for_ia_na(x, cc);
+ if (ret >= 0)
+ send_request6(x);
+ }
+}
+
+/*
+ * decode a base command line parameter
+ * (currently only MAC address and DUID are supported)
+ */
+
+void
+decodebase(void)
+{
+ char *b0 = base[basecnt];
+
+ /* MAC address (alias Ethernet address) */
+ if ((strncasecmp(b0, "mac=", 4) == 0) ||
+ (strncasecmp(b0, "ether=", 6) == 0)) {
+ char *b;
+
+ b = strchr(b0, '=') + 1;
+ if (sscanf(b, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ &mac_prefix[0], &mac_prefix[1],
+ &mac_prefix[2], &mac_prefix[3],
+ &mac_prefix[4], &mac_prefix[5]) == 6) {
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL))
+ printf("set MAC to %s\n", b);
+ return;
+ }
+ fprintf(stderr,
+ "expected base in '%*s=xx:xx:xx:xx:xx:xx' format\n",
+ (int) (b - b0 - 1), b0);
+ exit(2);
+ }
+ /* DUID */
+ if (strncasecmp(b0, "duid=", 5) == 0) {
+ char *b;
+ size_t i, l;
+
+ if (duid_prefix != NULL) {
+ fprintf(stderr, "duid was already set?\n");
+ exit(2);
+ }
+ b = strchr(b0, '=') + 1;
+ l = 0;
+ while (*b != '\0') {
+ if (!isxdigit((int) *b)) {
+ fprintf(stderr,
+ "illegal char '%c' in duid\n",
+ (int) *b);
+ exit(2);
+ }
+ b++;
+ l++;
+ }
+ if ((l & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in duid\n");
+ exit(2);
+ }
+ l /= 2;
+ if (l > 64) {
+ fprintf(stderr, "duid too large\n");
+ exit(2);
+ }
+ duid_prefix = (uint8_t *) malloc(l);
+ if (duid_prefix == NULL) {
+ perror("malloc(duid)");
+ exit(1);
+ }
+ b = strchr(b0, '=') + 1;
+ for (i = 0; i < l; i++, b += 2)
+ (void) sscanf(b, "%02hhx", &duid_prefix[i]);
+ duid_length = l;
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ b = strchr(b0, '=') + 1;
+ printf("set DUID[%d] to %s\n",
+ duid_length, b);
+ }
+ return;
+ }
+ /* other */
+ fprintf(stderr, "not yet supported base '%s'\n", b0);
+ exit(2);
+}
+
+/*
+ * get the server socket address from the command line:
+ * - flags: inherited from main, 0 or AI_NUMERICHOST (for literals)
+ */
+
+void
+getserveraddr(const int flags)
+{
+ struct addrinfo hints, *res;
+ char *service;
+ int ret;
+
+ memset(&hints, 0, sizeof(hints));
+ if (ipversion == 4) {
+ hints.ai_family = AF_INET;
+ service = "67";
+ } else {
+ hints.ai_family = AF_INET6;
+ service = "547";
+ }
+ hints.ai_socktype = SOCK_DGRAM;
+
+ hints.ai_flags = AI_NUMERICSERV | flags;
+#if defined(AI_ADDRCONFIG)
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ hints.ai_protocol = IPPROTO_UDP;
+
+ ret = getaddrinfo(servername, service, &hints, &res);
+ if (ret != 0) {
+ fprintf(stderr, "bad server=%s: %s\n",
+ servername, gai_strerror(ret));
+ exit(2);
+ }
+ if (res->ai_next != NULL) {
+ fprintf(stderr, "ambiguous server=%s\n", servername);
+ exit(2);
+ }
+ memcpy(&serveraddr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+}
+
+/*
+ * get the interface from the command line:
+ * - name of the interface
+ * - socket address to fill
+ * (in IPv6, get the first link-local address)
+ */
+
+void
+getinterface(const char *name, struct sockaddr_storage *ss)
+{
+ struct ifaddrs *ifaddr, *ifa;
+ int ret;
+ size_t len;
+
+ ret = getifaddrs(&ifaddr);
+ if (ret < 0) {
+ perror("getifaddrs");
+ exit(1);
+ }
+
+ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_name == NULL)
+ continue;
+ if (strcmp(ifa->ifa_name, name) != 0)
+ continue;
+ if ((ifa->ifa_flags & IFF_UP) == 0)
+ continue;
+ if ((ifa->ifa_flags & IFF_MULTICAST) == 0)
+ continue;
+ if (ifa->ifa_addr == NULL)
+ continue;
+ if ((ipversion == 4) &&
+ (ifa->ifa_addr->sa_family != AF_INET))
+ continue;
+ if (ipversion == 6) {
+ struct sockaddr_in6 *a6;
+
+ a6 = (struct sockaddr_in6 *) ifa->ifa_addr;
+ if (a6->sin6_family != AF_INET6)
+ continue;
+ if (!IN6_IS_ADDR_LINKLOCAL(&a6->sin6_addr))
+ continue;
+ }
+ if (ipversion == 4)
+ len = sizeof(struct sockaddr_in);
+ else
+ len = sizeof(struct sockaddr_in6);
+ memcpy(ss, ifa->ifa_addr, len);
+ /* fill the server port */
+ if (ipversion == 4)
+ ((struct sockaddr_in *) ss)->sin_port = htons(67);
+ else
+ ((struct sockaddr_in6 *) ss)->sin6_port = htons(546);
+ return;
+ }
+ fprintf(stderr, "can't find interface %s\n", name);
+ exit(1);
+}
+
+/*
+ * get the local socket address from the command line
+ * (if it doesn't work, try an interface name)
+ */
+
+void
+getlocaladdr(void)
+{
+ struct addrinfo hints, *res;
+ char *service;
+ int ret;
+
+ memset(&hints, 0, sizeof(hints));
+ if (ipversion == 4) {
+ hints.ai_family = AF_INET;
+ service = "67";
+ } else {
+ hints.ai_family = AF_INET6;
+ service = "546";
+ }
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_NUMERICSERV;
+#if defined(AI_ADDRCONFIG)
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ hints.ai_protocol = IPPROTO_UDP;
+
+ ret = getaddrinfo(localname, service, &hints, &res);
+ if ((ret == EAI_NONAME)
+#ifdef EAI_NODATA
+ || (ret == EAI_NODATA)
+#endif
+ ) {
+ isinterface = 1;
+ getinterface(localname, &localaddr);
+ return;
+ }
+ if (ret != 0) {
+ fprintf(stderr,
+ "bad -l<local-addr=%s>: %s\n",
+ localname,
+ gai_strerror(ret));
+ exit(2);
+ }
+ /* refuse multiple addresses */
+ if (res->ai_next != NULL) {
+ fprintf(stderr,
+ "ambiguous -l<local-addr=%s>\n",
+ localname);
+ exit(2);
+ }
+ memcpy(&localaddr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ return;
+}
+
+/*
+ * get the local socket address from the server one
+ */
+
+void
+getlocal(void)
+{
+ int ret, s;
+ socklen_t l;
+
+ if (ipversion == 4) {
+ l = sizeof(struct sockaddr_in);
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ } else {
+ l = sizeof(struct sockaddr_in6);
+ s = socket(PF_INET6, SOCK_DGRAM, 0);
+ }
+ if (s < 0) {
+ perror("socket");
+ exit(1);
+ }
+ if ((ipversion == 4) && (isbroadcast != 0)) {
+ int on = 1;
+
+ ret = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
+ if (ret < 0) {
+ perror("setsockopt(SO_BROADCAST)");
+ exit(1);
+ }
+ }
+ ret = connect(s, (struct sockaddr *) &serveraddr, l);
+ if (ret < 0) {
+ perror("connect");
+ exit(1);
+ }
+ l = sizeof(localaddr);
+ ret = getsockname(s, (struct sockaddr *) &localaddr, &l);
+ if (ret < 0) {
+ perror("getsockname");
+ exit(1);
+ }
+ (void) close(s);
+ /* fill the local port */
+ if (ipversion == 4)
+ ((struct sockaddr_in *) &localaddr)->sin_port = htons(67);
+ else
+ ((struct sockaddr_in6 *) &localaddr)->sin6_port = htons(546);
+}
+
+/*
+ * intermediate reporting
+ * (note: an in-transit packet can be reported as dropped)
+ */
+
+void
+reporting(void)
+{
+ dreport.tv_sec += report;
+
+ if (xscount2 == 0) {
+ printf("sent: %llu, received: %llu (drops: %lld)",
+ (unsigned long long) xscount0,
+ (unsigned long long) xrcount0,
+ (long long) (xscount0 - xrcount0));
+ if (!ISC_TAILQ_EMPTY(&xrcvd0)) {
+ double avg;
+
+ avg = dsum0 / xrcount0;
+ printf(" average: %.3f ms", avg * 1e3);
+ }
+ } else {
+ printf("sent: %llu/%llu received: %llu/%llu "
+ "(drops: %lld/%lld)",
+ (unsigned long long) xscount0,
+ (unsigned long long) xscount2,
+ (unsigned long long) xrcount0,
+ (unsigned long long) xrcount2,
+ (long long) (xscount0 - xrcount0),
+ (long long) (xscount2 - xrcount2));
+ if (!ISC_TAILQ_EMPTY(&xrcvd0)) {
+ double avg0, avg2;
+
+ avg0 = dsum0 / xrcount0;
+ if (xrcount2 != 0)
+ avg2 = dsum2 / xrcount2;
+ else
+ avg2 = 0.;
+ printf(" average: %.3f/%.3f ms",
+ avg0 * 1e3, avg2 * 1e3);
+ }
+ }
+ printf("\n");
+}
+
+/*
+ * SIGCHLD handler
+ */
+
+void
+reapchild(int sig)
+{
+ int status;
+
+ sig = sig;
+ while (wait3(&status, WNOHANG, NULL) > 0)
+ /* continue */;
+}
+
+/*
+ * SIGINT handler
+ */
+
+void
+interrupt(int sig)
+{
+ sig = sig;
+ interrupted = 1;
+}
+
+/*
+ * '-v' handler
+ */
+
+void
+version(void)
+{
+ fprintf(stderr, "version 0.01\n");
+}
+
+/*
+ * usage (from the wiki)
+ */
+
+void
+usage(void)
+{
+ fprintf(stderr, "%s",
+"perfdhcp [-hv] [-4|-6] [-r<rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+" [-n<num-request>] [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
+" [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
+" [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
+" [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
+" [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
+" [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+"\f\n"
+"The [server] argument is the name/address of the DHCP server to\n"
+"contact. For DHCPv4 operation, exchanges are initiated by\n"
+"transmitting a DHCP DISCOVER to this address.\n"
+"\n"
+"For DHCPv6 operation, exchanges are initiated by transmitting a DHCP\n"
+"SOLICIT to this address. In the DHCPv6 case, the special name 'all'\n"
+"can be used to refer to All_DHCP_Relay_Agents_and_Servers (the\n"
+"multicast address FF02::1:2), or the special name 'servers' to refer\n"
+"to All_DHCP_Servers (the multicast address FF05::1:3). The [server]\n"
+"argument is optional only in the case that -l is used to specify an\n"
+"interface, in which case [server] defaults to 'all'.\n"
+"\n"
+"The default is to perform a single 4-way exchange, effectively pinging\n"
+"the server.\n"
+"The -r option is used to set up a performance test, without\n"
+"it exchanges are initiated as fast as possible.\n"
+"\n"
+"Options:\n"
+"-1: Take the server-ID option from the first received message.\n"
+"-4: DHCPv4 operation (default). This is incompatible with the -6 option.\n"
+"-6: DHCPv6 operation. This is incompatible with the -4 option.\n"
+"-a<aggressivity>: When the target sending rate is not yet reached,\n"
+" control how many exchanges are initiated before the next pause.\n"
+"-b<base>: The base MAC, DUID, IP, etc, used to simulate different\n"
+" clients. This can be specified multiple times, each instance is\n"
+" in the <type>=<value> form, for instance:\n"
+" (and default) MAC=00:0c:01:02:03:04.\n"
+"-d<drop-time>: Specify the time after which a request is treated as\n"
+" having been lost. The value is given in seconds and may contain a\n"
+" fractional component. The default is 1 second.\n"
+"-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
+" elapsed-time option in the (second/request) template.\n"
+" The value 0 disables it.\n"
+"-h: Print this help.\n"
+"-i: Do only the initial part of an exchange: DO or SA, depending on\n"
+" whether -6 is given.\n"
+"-I<ip-offset>: Offset of the (DHCPv4) IP address in the requested-IP\n"
+" option / (DHCPv6) IA_NA option in the (second/request) template.\n"
+"-l<local-addr|interface>: For DHCPv4 operation, specify the local\n"
+" hostname/address to use when communicating with the server. By\n"
+" default, the interface address through which traffic would\n"
+" normally be routed to the server is used.\n"
+" For DHCPv6 operation, specify the name of the network interface\n"
+" via which exchanges are initiated.\n"
+"-L<local-port>: Specify the local port to use\n"
+" (the value 0 means to use the default).\n"
+"-O<random-offset>: Offset of the last octet to randomize in the template.\n"
+"-P<preload>: Initiate first <preload> exchanges back to back at startup.\n"
+"-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA)\n"
+" exchanges per second. A periodic report is generated showing the\n"
+" number of exchanges which were not completed, as well as the\n"
+" average response latency. The program continues until\n"
+" interrupted, at which point a final report is generated.\n"
+"-R<range>: Specify how many different clients are used. With 1\n"
+" (the default), all requests seem to come from the same client.\n"
+"-s<seed>: Specify the seed for randomization, making it repeatable.\n"
+"-S<srvid-offset>: Offset of the server-ID option in the\n"
+" (second/request) template.\n"
+"-T<template-file>: The name of a file containing the template to use\n"
+" as a stream of hexadecimal digits.\n"
+"-v: Report the version number of this program.\n"
+"-w<wrapped>: Command to call with start/stop at the beginning/end of\n"
+" the program.\n"
+"-x<diagnostic-selector>: Include extended diagnostics in the output.\n"
+" <diagnostic-selector> is a string of single-keywords specifying\n"
+" the operations for which verbose output is desired. The selector\n"
+" keyletters are:\n"
+" * 'a': print the decoded command line arguments\n"
+" * 'e': print the exit reason\n"
+" * 'i': print rate processing details\n"
+" * 'r': print randomization details\n"
+" * 's': print first server-id\n"
+" * 't': when finished, print timers of all successful exchanges\n"
+" * 'T': when finished, print templates\n"
+"-X<xid-offset>: Transaction ID (aka. xid) offset in the template.\n"
+"\n"
+"DHCPv4 only options:\n"
+"-B: Force broadcast handling.\n"
+"\n"
+"DHCPv6 only options:\n"
+"-c: Add a rapid commit option (exchanges will be SA).\n"
+"\n"
+"The remaining options are used only in conjunction with -r:\n"
+"\n"
+"-D<max-drop>: Abort the test if more than <max-drop> requests have\n"
+" been dropped. Use -D0 to abort if even a single request has been\n"
+" dropped. If <max-drop> includes the suffix '%', it specifies a\n"
+" maximum percentage of requests that may be dropped before abort.\n"
+" In this case, testing of the threshold begins after 10 requests\n"
+" have been expected to be received.\n"
+"-n<num-request>: Initiate <num-request> transactions. No report is\n"
+" generated until all transactions have been initiated/waited-for,\n"
+" after which a report is generated and the program terminates.\n"
+"-p<test-period>: Send requests for the given test period, which is\n"
+" specified in the same manner as -d. This can be used as an\n"
+" alternative to -n, or both options can be given, in which case the\n"
+" testing is completed when either limit is reached.\n"
+"-t<report>: Delay in seconds between two periodic reports.\n"
+"\n"
+"Errors:\n"
+"- tooshort: received a too short message\n"
+"- orphans: received a message which doesn't match an exchange\n"
+" (duplicate, late or not related)\n"
+"- locallimit: reached to local system limits when sending a message.\n"
+"\n"
+"Exit status:\n"
+"The exit status is:\n"
+"0 on complete success.\n"
+"1 for a general error.\n"
+"2 if an error is found in the command line arguments.\n"
+"3 if there are no general failures in operation, but one or more\n"
+" exchanges are not successfully completed.\n");
+}
+
+/*
+ * main function / entry point
+ */
+
+int
+main(const int argc, char * const argv[])
+{
+ int opt, flags = 0, ret, i;
+ long long r;
+ char *pc;
+ extern char *optarg;
+ extern int optind;
+
+#define OPTIONS "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:"
+
+ /* decode options */
+ while ((opt = getopt(argc, argv, OPTIONS)) != -1)
+ switch (opt) {
+ case 'h':
+ usage();
+ exit(0);
+
+ case 'v':
+ version();
+ exit(0);
+
+ case '4':
+ if (ipversion == 6) {
+ fprintf(stderr, "IP version already set to 6\n");
+ usage();
+ exit(2);
+ }
+ ipversion = 4;
+ break;
+
+ case '6':
+ if (ipversion == 4) {
+ fprintf(stderr, "IP version already set to 4\n");
+ usage();
+ exit(2);
+ }
+ ipversion = 6;
+ break;
+
+ case 'r':
+ rate = atoi(optarg);
+ if (rate <= 0) {
+ fprintf(stderr, "rate must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 't':
+ report = atoi(optarg);
+ if (report <= 0) {
+ fprintf(stderr, "report must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'R':
+ r = atoll(optarg);
+ if (r < 0) {
+ fprintf(stderr,
+ "range must not be a negative integer\n");
+ usage();
+ exit(2);
+ }
+ range = (uint32_t) r;
+ if ((range != 0) && (range != UINT32_MAX)) {
+ uint32_t s = range + 1;
+ uint64_t b = UINT32_MAX + 1, m;
+
+ m = (b / s) * s;
+ if (m == b)
+ maxrandom = 0;
+ else
+ maxrandom = (uint32_t) m;
+ }
+ break;
+
+ case 'b':
+ if (basecnt > 3) {
+ fprintf(stderr, "too many bases\n");
+ usage();
+ exit(2);
+ }
+ base[basecnt] = optarg;
+ decodebase();
+ basecnt++;
+ break;
+
+ case 'n':
+ numreq[gotnumreq] = atoi(optarg);
+ if (numreq[gotnumreq] <= 0) {
+ fprintf(stderr,
+ "num-request must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ gotnumreq = 1;
+ break;
+
+ case 'p':
+ period = atoi(optarg);
+ if (period <= 0) {
+ fprintf(stderr,
+ "test-period must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'd':
+ losttime[gotlosttime] = atof(optarg);
+ if (losttime[gotlosttime] <= 0.) {
+ fprintf(stderr,
+ "drop-time must be a positive number\n");
+ usage();
+ exit(2);
+ }
+ gotlosttime = 1;
+ break;
+
+ case 'D':
+ pc = strchr(optarg, '%');
+ if (pc != NULL) {
+ *pc = '\0';
+ maxpdrop[gotmaxdrop] = atof(optarg);
+ if ((maxpdrop[gotmaxdrop] <= 0) ||
+ (maxpdrop[gotmaxdrop] >= 100)) {
+ fprintf(stderr,
+ "invalid drop-time percentage\n");
+ usage();
+ exit(2);
+ }
+ gotmaxdrop = 1;
+ break;
+ }
+ maxdrop[gotmaxdrop] = atoi(optarg);
+ if (maxdrop[gotmaxdrop] <= 0) {
+ fprintf(stderr,
+ "max-drop must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ gotmaxdrop = 1;
+ break;
+
+ case 'l':
+ localname = optarg;
+ break;
+
+ case 'P':
+ preload = atoi(optarg);
+ if (preload < 0) {
+ fprintf(stderr,
+ "preload must not be a negative integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'a':
+ aggressivity = atoi(optarg);
+ if (aggressivity <= 0) {
+ fprintf(stderr,
+ "aggressivity must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'L':
+ localport = atoi(optarg);
+ if (localport < 0) {
+ fprintf(stderr,
+ "local-port must not be a negative integer\n");
+ usage();
+ exit(2);
+ }
+ if (localport > (int) UINT16_MAX) {
+ fprintf(stderr,
+ "local-port must be lower than %d\n",
+ (int) UINT16_MAX);
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 's':
+ seeded = 1;
+ seed = (unsigned int) atol(optarg);
+ break;
+
+ case 'i':
+ simple = 1;
+ break;
+
+ case 'B':
+ isbroadcast = 1;
+ break;
+
+ case 'c':
+ rapidcommit = 1;
+ break;
+
+ case '1':
+ usefirst = 1;
+ break;
+
+ case 'T':
+ if (templatefile[0] != NULL) {
+ if (templatefile[1] != NULL) {
+ fprintf(stderr,
+ "template-files are already set\n");
+ usage();
+ exit(2);
+ }
+ templatefile[1] = optarg;
+ } else
+ templatefile[0] = optarg;
+ break;
+
+ case 'X':
+ if (xidoffset[0] >= 0)
+ i = 1;
+ else
+ i = 0;
+ xidoffset[i] = atoi(optarg);
+ if (xidoffset[i] <= 0) {
+ fprintf(stderr,
+ "xid-offset must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'O':
+ if (rndoffset[0] >= 0)
+ i = 1;
+ else
+ i = 0;
+ rndoffset[i] = atoi(optarg);
+ if (rndoffset[i] < 3) {
+ fprintf(stderr,
+ "random-offset must be greater than 3\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'E':
+ elpoffset = atoi(optarg);
+ if (elpoffset < 0) {
+ fprintf(stderr,
+ "time-offset must not be a "
+ "negative integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'S':
+ sidoffset = atoi(optarg);
+ if (sidoffset <= 0) {
+ fprintf(stderr,
+ "srvid-offset must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'I':
+ ripoffset = atoi(optarg);
+ if (ripoffset <= 0) {
+ fprintf(stderr,
+ "ip-offset must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'x':
+ diags = optarg;
+ break;
+
+ case 'w':
+ wrapped = optarg;
+ break;
+
+ default:
+ usage();
+ exit(2);
+ }
+
+ /* adjust some global variables */
+ if (ipversion == 0)
+ ipversion = 4;
+ if (templatefile[1] != NULL) {
+ if (xidoffset[1] < 0)
+ xidoffset[1] = xidoffset[0];
+ if (rndoffset[1] < 0)
+ rndoffset[1] = rndoffset[0];
+ }
+
+ /* when required, print the internal view of the command line */
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ printf("IPv%d", ipversion);
+ if (simple != 0) {
+ if (ipversion == 4)
+ printf(" DO only");
+ else
+ printf(" SA only");
+ }
+ if (rate != 0)
+ printf(" rate=%d", rate);
+ if (report != 0)
+ printf(" report=%d", report);
+ if (range != 0) {
+ if (strchr(diags, 'r') != NULL)
+ printf(" range=0..%d [0x%x]",
+ range,
+ (unsigned int) maxrandom);
+ else
+ printf(" range=0..%d", range);
+ }
+ if (basecnt != 0)
+ for (i = 0; i < basecnt; i++)
+ printf(" base[%d]='%s'", i, base[i]);
+ if (gotnumreq != 0)
+ printf(" num-request=%d,%d", numreq[0], numreq[1]);
+ if (period != 0)
+ printf(" test-period=%d", period);
+ printf(" drop-time=%g,%g", losttime[0], losttime[1]);
+ if ((maxdrop[0] != 0) || (maxdrop[1] != 0))
+ printf(" max-drop=%d,%d", maxdrop[0], maxdrop[1]);
+ if ((maxpdrop[0] != 0.) || (maxpdrop[1] != 0.))
+ printf(" max-drop=%2.2f%%,%2.2f%%",
+ maxpdrop[0], maxpdrop[1]);
+ if (preload != 0)
+ printf(" preload=%d", preload);
+ printf(" aggressivity=%d", aggressivity);
+ if (localport != 0)
+ printf(" local-port=%d", localport);
+ if (seeded)
+ printf(" seed=%u", seed);
+ if (isbroadcast != 0)
+ printf(" broadcast");
+ if (rapidcommit != 0)
+ printf(" rapid-commit");
+ if (usefirst != 0)
+ printf(" use-first");
+ if ((templatefile[0] != NULL) && (templatefile[1] == NULL))
+ printf(" template-file='%s'", templatefile[0]);
+ else if (templatefile[1] != NULL)
+ printf(" template-file='%s','%s'",
+ templatefile[0], templatefile[1]);
+ if ((xidoffset[0] >= 0) && (xidoffset[1] < 0))
+ printf(" xid-offset=%d", xidoffset[0]);
+ else if (xidoffset[1] >= 0)
+ printf(" xid-offset=%d,%d",
+ xidoffset[0], xidoffset[1]);
+ if ((rndoffset[0] >= 0) && (rndoffset[1] < 0))
+ printf(" xid-offset=%d", rndoffset[0]);
+ else if (rndoffset[1] >= 0)
+ printf(" xid-offset=%d,%d",
+ rndoffset[0], rndoffset[1]);
+ if (elpoffset >= 0)
+ printf(" time-offset=%d", elpoffset);
+ if (sidoffset >= 0)
+ printf(" srvid-offset=%d", sidoffset);
+ if (ripoffset >= 0)
+ printf(" ip-offset=%d", ripoffset);
+ printf(" diagnotic-selectors='%s'", diags);
+ if (wrapped != NULL)
+ printf(" wrapped='%s'", wrapped);
+ printf("\n");
+ }
+
+ /* check DHCPv4 only options */
+ if ((ipversion != 4) && (isbroadcast != 0)) {
+ fprintf(stderr, "-b is not compatible with IPv6 (-6)\n");
+ usage();
+ exit(2);
+ }
+
+ /* check DHCPv6 only options */
+ if ((ipversion != 6) && (rapidcommit != 0)) {
+ fprintf(stderr, "-6 (IPv6) must be set to use -c\n");
+ usage();
+ exit(2);
+ }
+
+ /* check 4-packet (aka not simple) mode options */
+ if ((simple != 0) && (numreq[1] != 0)) {
+ fprintf(stderr,
+ "second -n<num-request> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (losttime[1] != 1.)) {
+ fprintf(stderr,
+ "second -d<drop-time> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) &&
+ ((maxdrop[1] != 0) || (maxpdrop[1] != 0.))) {
+ fprintf(stderr,
+ "second -D<max-drop> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (usefirst != 0)) {
+ fprintf(stderr,
+ "-1 is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (templatefile[1] != NULL)) {
+ fprintf(stderr,
+ "second -T<template-file> is not "
+ "compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (xidoffset[1] >= 0)) {
+ fprintf(stderr,
+ "second -X<xid-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (rndoffset[1] >= 0)) {
+ fprintf(stderr,
+ "second -O<random-offset is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (elpoffset >= 0)) {
+ fprintf(stderr,
+ "-E<time-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (sidoffset >= 0)) {
+ fprintf(stderr,
+ "-S<srvid-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (ripoffset >= 0)) {
+ fprintf(stderr,
+ "-I<ip-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+
+
+ /* check simple mode options */
+ if ((simple == 0) && (rapidcommit != 0)) {
+ fprintf(stderr, "-i must be set to use -c\n");
+ usage();
+ exit(2);
+ }
+
+ /* check rate '-r' options */
+ if ((rate == 0) && (report != 0)) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -t<report>\n");
+ usage();
+ exit(2);
+ }
+ if ((rate == 0) && ((numreq[0] != 0) || (numreq[1] != 0))) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -n<num-request>\n");
+ usage();
+ exit(2);
+ }
+ if ((rate == 0) && (period != 0)) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -p<test-period>\n");
+ usage();
+ exit(2);
+ }
+ if ((rate == 0) &&
+ ((maxdrop[0] != 0) || (maxdrop[1] != 0) ||
+ (maxpdrop[0] != 0.) || (maxpdrop[1] != 0.))) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -D<max-drop>\n");
+ usage();
+ exit(2);
+ }
+
+ /* check (first) template file options */
+ if ((templatefile[0] == NULL) && (xidoffset[0] >= 0)) {
+ fprintf(stderr,
+ "-T<template-file> must be set to "
+ "use -X<xid-offset>\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[0] == NULL) && (rndoffset[0] >= 0)) {
+ fprintf(stderr,
+ "-T<template-file> must be set to "
+ "use -O<random-offset>\n");
+ usage();
+ exit(2);
+ }
+
+ /* check (second) template file options */
+ if ((templatefile[1] == NULL) && (elpoffset >= 0)) {
+ fprintf(stderr,
+ "second/request -T<template-file> must be set to "
+ "use -E<time-offset>\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] == NULL) && (sidoffset >= 0)) {
+ fprintf(stderr,
+ "second/request -T<template-file> must be set to "
+ "use -S<srvid-offset>\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] == NULL) && (ripoffset >= 0)) {
+ fprintf(stderr,
+ "second/request -T<template-file> must be set to "
+ "use -I<ip-offset>\n");
+ usage();
+ exit(2);
+ }
+
+ /* check various template file(s) and other condition(s) options */
+ if ((templatefile[0] != NULL) && (range > 0) && (rndoffset[0] < 0)) {
+ fprintf(stderr,
+ "-O<random-offset> must be set when "
+ "-T<template-file> and -R<range> are used\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] != NULL) && (sidoffset < 0)) {
+ fprintf(stderr,
+ "-S<srvid-offset> must be set when second "
+ "-T<template-file> is used\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] != NULL) && (ripoffset < 0)) {
+ fprintf(stderr,
+ "-I<ip-offset> must be set when second "
+ "-T<template-file> is used\n");
+ usage();
+ exit(2);
+ }
+
+ /* get the server argument */
+ if (optind < argc - 1) {
+ fprintf(stderr, "extra arguments?\n");
+ usage();
+ exit(2);
+ }
+ if (optind == argc - 1) {
+ servername = argv[optind];
+ /* decode special cases */
+ if ((ipversion == 4) &&
+ (strcmp(servername, "all") == 0)) {
+ flags = AI_NUMERICHOST;
+ isbroadcast = 1;
+ servername = "255.255.255.255";
+ } else if ((ipversion == 6) &&
+ (strcmp(servername, "all") == 0)) {
+ flags = AI_NUMERICHOST;
+ servername = "FF02::1:2";
+ } else if ((ipversion == 6) &&
+ (strcmp(servername, "servers") == 0)) {
+ flags = AI_NUMERICHOST;
+ servername = "FF05::1:3";
+ }
+ }
+
+ /* handle the local '-l' address/interface */
+ if (localname != NULL) {
+ /* given */
+ getlocaladdr();
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ if (isinterface)
+ printf("interface='%s'\n", localname);
+ else
+ printf("local-addr='%s'\n", localname);
+ }
+ /* get the not given server from it */
+ if (servername == NULL) {
+ if (isinterface && (ipversion == 4)) {
+ flags = AI_NUMERICHOST;
+ isbroadcast = 1;
+ servername = "255.255.255.255";
+ } else if (isinterface && (ipversion == 6)) {
+ flags = AI_NUMERICHOST;
+ servername = "FF02::1:2";
+ } else {
+ fprintf(stderr,
+ "without an interface "
+ "server is required\n");
+ usage();
+ exit(2);
+ }
+ }
+ } else if (servername == NULL) {
+ fprintf(stderr, "without an interface server is required\n");
+ usage();
+ exit(2);
+ }
+ /* get the server socket address */
+ getserveraddr(flags);
+ /* finish local/server socket address stuff and print it */
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL))
+ printf("server='%s'\n", servername);
+ if (localname == NULL)
+ getlocal();
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ char addr[NI_MAXHOST];
+
+ ret = getnameinfo((struct sockaddr *) &localaddr,
+ sizeof(localaddr),
+ addr,
+ NI_MAXHOST,
+ NULL,
+ 0,
+ NI_NUMERICHOST);
+ if (ret != 0) {
+ fprintf(stderr,
+ "can't get the local address: %s\n",
+ gai_strerror(ret));
+ exit(1);
+ }
+ printf("local address='%s'\n", addr);
+ }
+
+ /* initialize exchange structures */
+ inits();
+
+ /* get the socket descriptor and template(s) */
+ if (ipversion == 4) {
+ getsock4();
+ if (templatefile[0] == NULL)
+ build_template_discover4();
+ else
+ get_template_discover4();
+ if (simple == 0) {
+ if (templatefile[1] == NULL)
+ build_template_request4();
+ else
+ get_template_request4();
+ }
+ } else {
+ getsock6();
+ if (duid_prefix != NULL) {
+ if (templatefile[0] == NULL)
+ build_template_solicit6();
+ else
+ get_template_solicit6();
+ if (simple == 0) {
+ if (templatefile[1] == NULL)
+ build_template_request6();
+ else
+ get_template_request6();
+ }
+ }
+ }
+ /* sanity check */
+ if ((unsigned) sock > FD_SETSIZE) {
+ fprintf(stderr, "socket descriptor (%d) too large?!\n", sock);
+ exit(1);
+ }
+ /* make the socket descriptor not blocking */
+ flags = fcntl(sock, F_GETFL, 0);
+ if (flags < 0) {
+ perror("fcntl(F_GETFL)");
+ exit(1);
+ }
+ if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
+ perror("fcntl(F_SETFL)");
+ exit(1);
+ }
+
+ /* wrapped start */
+ if (wrapped != NULL) {
+ pid_t pid;
+
+ (void) signal(SIGCHLD, reapchild);
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ } else if (pid == 0)
+ (void) execlp(wrapped, "start", (char *) NULL);
+ }
+
+ /* boot is done! */
+ if (clock_gettime(CLOCK_REALTIME, &boot) < 0) {
+ perror("clock_gettime(boot)");
+ exit(1);
+ }
+
+ /* compute the next intermediate reporting date */
+ if (report != 0) {
+ dreport.tv_sec = boot.tv_sec + report;
+ dreport.tv_nsec = boot.tv_nsec;
+ }
+
+ /* compute the DUID (the current date is needed) */
+ if ((ipversion == 6) && (duid_prefix == NULL)) {
+ uint32_t curdate;
+
+ duid_length = 14;
+ duid_prefix = (uint8_t *) malloc(duid_length);
+ if (duid_prefix == NULL) {
+ perror("malloc(duid)");
+ exit(1);
+ }
+ duid_prefix[0] = DHCP6_DUID_LLT >> 8;
+ duid_prefix[1] = DHCP6_DUID_LLT;
+ duid_prefix[2] = DHCP_HTYPE_ETHER >> 8;
+ duid_prefix[3] = DHCP_HTYPE_ETHER;
+ curdate = htonl(boot.tv_sec - DHCP6_DUID_EPOCH);
+ memcpy(duid_prefix + 4, &curdate, 4);
+ memcpy(duid_prefix + 8, mac_prefix, 6);
+ /* the DUID is in template(s) */
+ if (templatefile[0] == NULL)
+ build_template_solicit6();
+ else
+ get_template_solicit6();
+ if (simple == 0) {
+ if (templatefile[1] == NULL)
+ build_template_request6();
+ else
+ get_template_request6();
+ }
+ }
+
+ /* seed the random generator */
+ if (seeded == 0)
+ seed = (unsigned int) (boot.tv_sec + boot.tv_nsec);
+ srandom(seed);
+
+ /* preload the server with at least one packet */
+ compsend = preload + 1;
+ for (i = 0; i <= preload; i++) {
+ if (ipversion == 4)
+ ret = send4();
+ else
+ ret = send6();
+ if (ret < 0) {
+ /* failure at the first packet is fatal */
+ if (i == 0) {
+ fprintf(stderr,
+ "initial send failed: %s\n",
+ strerror(-ret));
+ exit(1);
+ }
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) ||
+ (errno == ENOMEM))
+ locallimit++;
+ fprintf(stderr, "preload send: %s\n", strerror(-ret));
+ break;
+ }
+ }
+
+ /* required only before the interrupted flag check */
+ (void) signal(SIGINT, interrupt);
+
+ /* main loop */
+ for (;;) {
+ struct timespec now, ts;
+ fd_set rfds;
+
+ /* immediate loop exit conditions */
+ if (interrupted) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("interrupted\n");
+ break;
+ }
+ if (fatal) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("got a fatal error\n");
+ break;
+ }
+
+ /* get the date and use it */
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(now)");
+ fatal = 1;
+ continue;
+ }
+ if ((period != 0) &&
+ ((boot.tv_sec + period < now.tv_sec) ||
+ ((boot.tv_sec + period == now.tv_sec) &&
+ (boot.tv_nsec < now.tv_nsec)))) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached test-period\n");
+ break;
+ }
+ if ((report != 0) &&
+ ((dreport.tv_sec < now.tv_sec) ||
+ ((dreport.tv_sec == now.tv_sec) &&
+ (dreport.tv_nsec < now.tv_nsec))))
+ reporting();
+
+ /* compute the delay for the next send */
+ due = last;
+ if (rate == 1)
+ due.tv_sec += 1;
+ else if (rate != 0)
+ due.tv_nsec += 1010000000 / rate;
+ else
+ due.tv_nsec += 1;
+ while (due.tv_nsec >= 1000000000) {
+ due.tv_sec += 1;
+ due.tv_nsec -= 1000000000;
+ }
+ ts = due;
+ ts.tv_sec -= now.tv_sec;
+ ts.tv_nsec -= now.tv_nsec;
+ while (ts.tv_nsec < 0) {
+ ts.tv_sec -= 1;
+ ts.tv_nsec += 1000000000;
+ }
+ /* the send was already due? */
+ if (ts.tv_sec < 0) {
+ ts.tv_sec = ts.tv_nsec = 0;
+ latesent++;
+ }
+
+ /* pselect() */
+ FD_ZERO(&rfds);
+ FD_SET(sock, &rfds);
+ ret = pselect(sock + 1, &rfds, NULL, NULL, &ts, NULL);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("pselect");
+ fatal = 1;
+ continue;
+ }
+
+ /* packet(s) to receive */
+ while (ret == 1) {
+ if (ipversion == 4)
+ receive4();
+ else
+ receive6();
+ if (recv(sock, ibuf, sizeof(ibuf), MSG_PEEK) <= 0)
+ ret = 0;
+ else
+ multrcvd++;
+ }
+ if (fatal)
+ continue;
+
+ /* check receive loop exit conditions */
+ if ((numreq[0] != 0) && ((int) xscount0 >= numreq[0])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached num-request0\n");
+ break;
+ }
+ if ((numreq[1] != 0) && ((int) xscount2 >= numreq[1])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached num-request2\n");
+ break;
+ }
+ if ((maxdrop[0] != 0) &&
+ ((int) (xscount0 - xrcount0) > maxdrop[0])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop%s (absolute)\n",
+ simple != 0 ? "" : "0");
+ break;
+ }
+ if ((maxdrop[1] != 0) &&
+ ((int) (xscount2 - xrcount2) > maxdrop[1])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop2 (absolute)\n");
+ break;
+ }
+ if ((maxpdrop[0] != 0.) &&
+ (xscount0 > 10) &&
+ (((100. * (xscount0 - xrcount0)) / xscount0)
+ > maxpdrop[0])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop%s (percent)\n",
+ simple != 0 ? "" : "0");
+ break;
+ }
+ if ((maxpdrop[1] != 0.) &&
+ (xscount2 > 10) &&
+ (((100. * (xscount2 - xrcount2)) / xscount2)
+ > maxpdrop[1])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop2 (percent)\n");
+ break;
+ }
+
+ /* compute how many packets to send */
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(now2)");
+ fatal = 1;
+ continue;
+ }
+ if ((now.tv_sec > due.tv_sec) ||
+ ((now.tv_sec == due.tv_sec) &&
+ (now.tv_nsec >= due.tv_nsec))) {
+ double tosend;
+
+ if (rate != 0) {
+ tosend = (now.tv_nsec - due.tv_nsec) / 1e9;
+ tosend += now.tv_sec - due.tv_sec;
+ tosend *= rate;
+ tosend += 1;
+ if (tosend > (double) aggressivity)
+ i = aggressivity;
+ else
+ i = (int) tosend;
+ } else
+ i = aggressivity;
+ compsend += i;
+ /* send packets */
+ for (;;) {
+ if (ipversion == 4)
+ ret = send4();
+ else
+ ret = send6();
+ if (ret < 0) {
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) ||
+ (errno == ENOMEM))
+ locallimit++;
+ fprintf(stderr,
+ "send: %s\n", strerror(-ret));
+ break;
+ }
+ i--;
+ if (i == 0)
+ break;
+ /* check for late packets to receive */
+ if (recv(sock, ibuf, sizeof(ibuf),
+ MSG_PEEK) > 0) {
+ latercvd++;
+ if (ipversion == 4)
+ receive4();
+ else
+ receive6();
+ }
+ }
+ } else
+ /* there was no packet to send */
+ shortwait++;
+ }
+
+ /* after main loop: finished */
+ if (clock_gettime(CLOCK_REALTIME, &finished) < 0)
+ perror("clock_gettime(finished)");
+
+ /* wrapped stop */
+ if (wrapped != NULL) {
+ pid_t pid;
+
+ pid = fork();
+ if (pid == 0)
+ (void) execlp(wrapped, "stop", (char *) NULL);
+ }
+
+ /* main statictics */
+ if (xscount2 == 0)
+ printf("sent: %llu, received: %llu (drops: %lld)\n",
+ (unsigned long long) xscount0,
+ (unsigned long long) xrcount0,
+ (long long) (xscount0 - xrcount0));
+ else
+ printf("sent: %llu/%llu, received: %llu/%llu "
+ "(drops: %lld/%lld)\n",
+ (unsigned long long) xscount0,
+ (unsigned long long) xscount2,
+ (unsigned long long) xrcount0,
+ (unsigned long long) xrcount2,
+ (long long) (xscount0 - xrcount0),
+ (long long) (xscount2 - xrcount2));
+ printf("tooshort: %llu, orphans: %llu, local limits: %llu\n",
+ (unsigned long long) tooshort,
+ (unsigned long long) orphans,
+ (unsigned long long) locallimit);
+
+ /* print the rate */
+ if (finished.tv_sec != 0) {
+ double dall, erate;
+
+ dall = (finished.tv_nsec - boot.tv_nsec) / 1e9;
+ dall += finished.tv_sec - boot.tv_sec;
+ erate = xrcount0 / dall;
+ if (rate != 0)
+ printf("rate: %f (expected %d)\n", erate, rate);
+ else
+ printf("rate: %f\n", erate);
+ }
+
+ /* rate processing instrumentation */
+ if ((diags != NULL) && (strchr(diags, 'i') != NULL)) {
+ printf("latesent: %llu, compsend: %llu, shortwait: %llu\n"
+ "multrcvd: %llu, latercvd: %llu, collected:%llu/%llu\n",
+ (unsigned long long) latesent,
+ (unsigned long long) compsend,
+ (unsigned long long) shortwait,
+ (unsigned long long) multrcvd,
+ (unsigned long long) latercvd,
+ (unsigned long long) collected[0],
+ (unsigned long long) collected[1]);
+ }
+
+ /* round-time trip statistics */
+ if (xrcount2 != 0) {
+ double avg0, avg2, stddev0, stddev2;
+
+ avg0 = dsum0 / xrcount0;
+ avg2 = dsum2 / xrcount2;
+ stddev0 = sqrt(dsumsq0 / xrcount0 - avg0 * avg0);
+ stddev2 = sqrt(dsumsq2 / xrcount2 - avg2 * avg2);
+ printf("RTT0: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
+ dmin0 * 1e3, avg0 * 1e3, dmax0 * 1e3, stddev0 * 1e3);
+ printf("RTT2: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
+ dmin2 * 1e3, avg2 * 1e3, dmax2 * 1e3, stddev2 * 1e3);
+ } else if (xrcount0 != 0) {
+ double avg, stddev;
+
+ avg = dsum0 / xrcount0;
+ stddev = sqrt(dsumsq0 / xrcount0 - avg * avg);
+ printf("RTT%s: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
+ simple != 0 ? "" : "0",
+ dmin0 * 1e3, avg * 1e3, dmax0 * 1e3, stddev * 1e3);
+ }
+
+ /* (first) server-ID option content */
+ if ((diags != NULL) && (strchr(diags, 's') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd0)) {
+ struct exchange *x;
+ size_t n;
+
+ printf("server-id: ");
+ x = ISC_TAILQ_FIRST(&xrcvd0);
+ if (ipversion == 4)
+ n = 2;
+ else
+ n = 4;
+ for (; n < x->sidlen; n++)
+ printf("%02hhx", x->sid[n]);
+ printf("\n");
+ }
+
+ /* all time-stamps */
+ if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd0)) {
+ struct exchange *x;
+
+ printf("\n\n");
+ ISC_TAILQ_FOREACH(x, &xrcvd0, gchain)
+ printf("%ld.%09ld %ld.%09ld\n",
+ (long) x->ts0.tv_sec, x->ts0.tv_nsec,
+ (long) x->ts1.tv_sec, x->ts1.tv_nsec);
+
+ }
+ if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd2)) {
+ struct exchange *x;
+
+ printf("--\n");
+ ISC_TAILQ_FOREACH(x, &xrcvd2, gchain)
+ printf("%ld.%09ld %ld.%09ld %ld.%09ld %ld.%09ld\n",
+ (long) x->ts0.tv_sec, x->ts0.tv_nsec,
+ (long) x->ts1.tv_sec, x->ts1.tv_nsec,
+ (long) x->ts2.tv_sec, x->ts2.tv_nsec,
+ (long) x->ts3.tv_sec, x->ts3.tv_nsec);
+
+ }
+ if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd0))
+ printf("\n\n");
+
+ /* template(s) */
+ if ((diags != NULL) && (strchr(diags, 'T') != NULL)) {
+ size_t n;
+
+ if (ipversion == 4) {
+ printf("length = %zu\n", length_discover4);
+ printf("xid offset = %d\n", DHCP_OFF_XID);
+ printf("xid length = 4\n");
+ printf("random offset = %zu\n", random_discover4);
+ printf("content:\n");
+ for (n = 0; n < length_discover4; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_discover4[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ if ((n & 15) != 15)
+ printf("\n");
+ if (simple != 0)
+ goto doneT;
+ printf("--\n");
+ printf("length = %zu\n", length_request4);
+ printf("xid offset = %d\n", DHCP_OFF_XID);
+ printf("xid length = 4\n");
+ printf("random offset = %zu\n", random_request4);
+ if (elapsed_request4 > 0)
+ printf("secs offset = %zu\n",
+ elapsed_request4);
+ printf("server-id offset = %zu\n", serverid_request4);
+ printf("server-id length = %d\n", DHCP_OPTLEN_SRVID);
+ printf("content:\n");
+ printf("requested-ip-address offset = %zu\n",
+ reqaddr_request4);
+ printf("requested-ip-address length = %d\n", 4);
+ for (n = 0; n < length_request4; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_request4[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ printf("\n");
+ } else {
+ printf("length = %zu\n", length_solicit6);
+ printf("xid offset = %d\n", DHCP6_OFF_XID);
+ printf("xid length = 3\n");
+ printf("random offset = %zu\n", random_solicit6);
+ for (n = 0; n < length_solicit6; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_solicit6[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ if ((n & 15) != 15)
+ printf("\n");
+ if (simple != 0)
+ goto doneT;
+ printf("--\n");
+ printf("length = %zu\n", length_request6);
+ printf("xid offset = %d\n", DHCP_OFF_XID);
+ printf("xid length = 4\n");
+ printf("random offset = %zu\n", random_request6);
+ if (elapsed_request6 > 0)
+ printf("secs offset = %zu\n",
+ elapsed_request6);
+ printf("server-id offset = %zu\n", serverid_request6);
+ printf("content:\n");
+ printf("requested-ip-address offset = %zu\n",
+ reqaddr_request6);
+ for (n = 0; n < length_request6; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_request6[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ printf("\n");
+ }
+ }
+ doneT:
+
+ /* compute the exit code (and exit) */
+ if (fatal)
+ exit(1);
+ else if ((xscount0 == xrcount0) && (xscount2 == xrcount2))
+ exit(0);
+ else
+ exit(3);
+}
+
+#endif /* HAVE_GETIFADDRS */
diff --git a/tests/tools/perfdhcp/pkt_transform.cc b/tests/tools/perfdhcp/pkt_transform.cc
new file mode 100644
index 0000000..5ed39bf
--- /dev/null
+++ b/tests/tools/perfdhcp/pkt_transform.cc
@@ -0,0 +1,222 @@
+// 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 <iostream>
+
+#include <exceptions/exceptions.h>
+#include <dhcp/option.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/dhcp6.h>
+
+#include "pkt_transform.h"
+#include "localized_option.h"
+
+using namespace std;
+using namespace isc;
+using namespace dhcp;
+
+namespace isc {
+namespace perfdhcp {
+
+bool
+PktTransform::pack(const Option::Universe universe,
+ const OptionBuffer& in_buffer,
+ const Option::OptionCollection& options,
+ const size_t transid_offset,
+ const uint32_t transid,
+ util::OutputBuffer& out_buffer) {
+
+ // Always override the packet if function is called.
+ out_buffer.clear();
+ // Write whole buffer to output buffer.
+ out_buffer.writeData(&in_buffer[0], in_buffer.size());
+
+ uint8_t transid_len = (universe == Option::V6) ? 3 : 4;
+
+ if ((transid_offset + transid_len >= in_buffer.size()) ||
+ (transid_offset == 0)) {
+ cout << "Failed to build packet: provided transaction id offset: "
+ << transid_offset << " is out of bounds (expected 1.."
+ << in_buffer.size()-1 << ")." << endl;
+ return (false);
+ }
+
+ try {
+ size_t offset_ptr = transid_offset;
+ if (universe == Option::V4) {
+ out_buffer.writeUint8At(transid >> 24 & 0xFF, offset_ptr++);
+ }
+ out_buffer.writeUint8At(transid >> 16 & 0xFF, offset_ptr++);
+ out_buffer.writeUint8At(transid >> 8 & 0xFF, offset_ptr++);
+ out_buffer.writeUint8At(transid & 0xFF, offset_ptr++);
+
+ // We already have packet template stored in output buffer
+ // but still some options have to be updated if client
+ // specified them along with their offsets in the buffer.
+ PktTransform::packOptions(in_buffer, options, out_buffer);
+ } catch (const isc::BadValue& e) {
+ cout << "Building packet failed: " << e.what() << endl;
+ return (false);
+ }
+ return (true);
+}
+
+bool
+PktTransform::unpack(const Option::Universe universe,
+ const OptionBuffer& in_buffer,
+ const Option::OptionCollection& options,
+ const size_t transid_offset,
+ uint32_t& transid) {
+
+ uint8_t transid_len = (universe == Option::V6) ? 3 : 4;
+
+ // Validate transaction id offset.
+ if ((transid_offset + transid_len + 1 > in_buffer.size()) ||
+ (transid_offset == 0)) {
+ cout << "Failed to parse packet: provided transaction id offset: "
+ << transid_offset << " is out of bounds (expected 1.."
+ << in_buffer.size()-1 << ")." << endl;
+ return (false);
+ }
+
+ // Read transaction id from the buffer.
+ // For DHCPv6 we transaction id is 3 bytes long so the high byte
+ // of transid will be zero.
+ OptionBufferConstIter it = in_buffer.begin() + transid_offset;
+ transid = 0;
+ for (int i = 0; i < transid_len; ++i, ++it) {
+ // Read next byte and shift it left to its position in
+ // transid (shift by the number of bytes read so far.
+ transid += *it << (transid_len - i - 1) * 8;
+ }
+
+ try {
+ PktTransform::unpackOptions(in_buffer, options);
+ } catch (const isc::BadValue& e) {
+ cout << "Packet parsing failed: " << e.what() << endl;
+ return (false);
+ }
+
+ return (true);
+}
+
+void
+PktTransform::packOptions(const OptionBuffer& in_buffer,
+ const Option::OptionCollection& options,
+ util::OutputBuffer& out_buffer) {
+ try {
+ // If there are any options on the list, we will use provided
+ // options offsets to override them in the output buffer
+ // with new contents.
+ for (Option::OptionCollection::const_iterator it = options.begin();
+ it != options.end(); ++it) {
+ // Get options with their position (offset).
+ boost::shared_ptr<LocalizedOption> option =
+ boost::dynamic_pointer_cast<LocalizedOption>(it->second);
+ if (option == NULL) {
+ isc_throw(isc::BadValue, "option is null");
+ }
+ uint32_t offset = option->getOffset();
+ if ((offset == 0) ||
+ (offset + option->len() > in_buffer.size())) {
+ isc_throw(isc::BadValue,
+ "option offset for option: " << option->getType()
+ << " is out of bounds (expected 1.."
+ << in_buffer.size() - option->len() << ")");
+ }
+
+ // Create temporary buffer to store option contents.
+ util::OutputBuffer buf(option->len());
+ // Pack option contents into temporary buffer.
+ option->pack(buf);
+ // OutputBuffer class has nice functions that write
+ // data at the specified position so we can use it to
+ // inject contents of temporary buffer to output buffer.
+ const uint8_t *buf_data =
+ static_cast<const uint8_t*>(buf.getData());
+ for (int i = 0; i < buf.getLength(); ++i) {
+ out_buffer.writeUint8At(buf_data[i], offset + i);
+ }
+ }
+ }
+ catch (const Exception&) {
+ isc_throw(isc::BadValue, "failed to pack options into buffer.");
+ }
+}
+
+void
+PktTransform::unpackOptions(const OptionBuffer& in_buffer,
+ const Option::OptionCollection& options) {
+ for (Option::OptionCollection::const_iterator it = options.begin();
+ it != options.end(); ++it) {
+
+ boost::shared_ptr<LocalizedOption> option =
+ boost::dynamic_pointer_cast<LocalizedOption>(it->second);
+ if (option == NULL) {
+ isc_throw(isc::BadValue, "option is null");
+ }
+ size_t opt_pos = option->getOffset();
+ if (opt_pos == 0) {
+ isc_throw(isc::BadValue, "failed to unpack packet from raw buffer "
+ "(Option position not specified)");
+ } else if (opt_pos + option->getHeaderLen() > in_buffer.size()) {
+ isc_throw(isc::BadValue,
+ "failed to unpack options from from raw buffer "
+ "(Option position out of bounds)");
+ }
+
+ size_t offset = opt_pos;
+ size_t offset_step = 1;
+ uint16_t opt_type = 0;
+ if (option->getUniverse() == Option::V6) {
+ offset_step = 2;
+ // For DHCPv6 option type is in first two octets.
+ opt_type = in_buffer[offset] * 256 + in_buffer[offset + 1];
+ } else {
+ // For DHCPv4 option type is in first octet.
+ opt_type = in_buffer[offset];
+ }
+ // Check if we got expected option type.
+ if (opt_type != option->getType()) {
+ isc_throw(isc::BadValue,
+ "failed to unpack option from raw buffer "
+ "(option type mismatch)");
+ }
+
+ // Get option length which is supposed to be after option type.
+ offset += offset_step;
+ uint16_t opt_len = in_buffer[offset] * 256 + in_buffer[offset + 1];
+ if (option->getUniverse() == Option::V6) {
+ opt_len = in_buffer[offset] * 256 + in_buffer[offset + 1];
+ } else {
+ opt_len = in_buffer[offset];
+ }
+
+ // Check if packet is not truncated.
+ if (offset + option->getHeaderLen() + opt_len > in_buffer.size()) {
+ isc_throw(isc::BadValue,
+ "failed to unpack option from raw buffer "
+ "(option truncated)");
+ }
+
+ // Seek to actual option data and replace it.
+ offset += offset_step;
+ option->setData(in_buffer.begin() + offset,
+ in_buffer.begin() + offset + opt_len);
+ }
+}
+
+
+} // namespace perfdhcp
+} // namespace isc
diff --git a/tests/tools/perfdhcp/pkt_transform.h b/tests/tools/perfdhcp/pkt_transform.h
new file mode 100644
index 0000000..7fb19f4
--- /dev/null
+++ b/tests/tools/perfdhcp/pkt_transform.h
@@ -0,0 +1,139 @@
+// 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 __PKT_TRANSFORM_H
+#define __PKT_TRANSFORM_H
+
+#include <dhcp/option.h>
+
+#include "localized_option.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Read and write raw data to DHCP packets.
+///
+/// This class provides static functions to read/write raw data from/to the
+/// packet buffer. When reading data with the unpack() method, the
+/// corresponding options objects are updated. When writing to the packet
+/// buffer with pack(), options objects carry input data to be written.
+///
+/// This class is used both by \ref PerfPkt4 and
+/// \ref PerfPkt6 classes in case DHCP packets are created
+/// from template files. In this case, some of the template
+/// packet's options are replaced before sending it to the
+/// server. Offset of specific options are provided from the
+/// command line by the perfdhcp tool user, and passed in an
+/// options collection.
+class PktTransform {
+public:
+
+ /// \brief Prepares on-wire format from raw buffer.
+ ///
+ /// The method copies the input buffer and options contents
+ /// to the output buffer. The input buffer must contain whole
+ /// initial packet data. Parts of this data will be
+ /// overriden by options data specified in an options
+ /// collection. Such options must have their offsets within
+ /// a packet specified (see \ref LocalizedOption to find out
+ /// how to specify options offset).
+ ///
+ /// \note The specified options must fit into the size of the
+ /// initial packet data. A call to this method will fail
+ /// if the option's offset + its size is beyond the packet's size.
+ ///
+ /// \param universe Universe used, V4 or V6
+ /// \param in_buffer Input buffer holding intial packet
+ /// data, this can be directly read from template file
+ /// \param options Options collection with offsets
+ /// \param transid_offset offset of transaction id in a packet,
+ /// transaction ID will be written to output buffer at this
+ /// offset
+ /// \param transid Transaction ID value
+ /// \param out_buffer Output buffer holding "packed" data
+ ///
+ /// \return false, if pack operation failed.
+ static bool pack(const dhcp::Option::Universe universe,
+ const dhcp::OptionBuffer& in_buffer,
+ const dhcp::Option::OptionCollection& options,
+ const size_t transid_offset,
+ const uint32_t transid,
+ util::OutputBuffer& out_buffer);
+
+ /// \brief Handles selective binary packet parsing.
+ ///
+ /// This method handles the parsing of packets that have non-default
+ /// options or transaction ID offsets. The client class has to use
+ /// \ref isc::dhcp::Pkt6::addOption to specify which options to parse.
+ /// Each option should be of the \ref isc::perfdhcp::LocalizedOption
+ /// type with the offset value specified.
+ ///
+ /// \param universe universe used, V4 or V6
+ /// \param in_buffer input buffer to be parsed
+ /// \param options options collection with options offsets
+ /// \param transid_offset offset of transaction id in input buffer
+ /// \param transid transaction id value read from input buffer
+ ///
+ /// \return false, if unpack operation failed.
+ static bool unpack(const dhcp::Option::Universe universe,
+ const dhcp::OptionBuffer& in_buffer,
+ const dhcp::Option::OptionCollection& options,
+ const size_t transid_offset,
+ uint32_t& transid);
+
+private:
+ /// \brief Replaces contents of options in a buffer.
+ ///
+ /// The method uses a localized options collection to
+ /// replace parts of packet data (e.g. data read
+ /// from template file).
+ /// This private method is called from \ref PktTransform::pack
+ ///
+ /// \param in_buffer input buffer holding initial packet data.
+ /// \param out_buffer output buffer with "packed" options.
+ /// \param options options collection with actual data and offsets.
+ ///
+ /// \throw isc::Unexpected if options update failed.
+ static void packOptions(const dhcp::OptionBuffer& in_buffer,
+ const dhcp::Option::OptionCollection& options,
+ util::OutputBuffer& out_buffer);
+
+ /// \brief Reads contents of specified options from buffer.
+ ///
+ /// The method reads options data from the input buffer
+ /// and stores it in options objects. Offsets of the options
+ /// must be specified (see \ref LocalizedOption to find out how to specify
+ /// the option offset).
+ /// This private method is called by \ref PktTransform::unpack.
+ ///
+ /// \note This method iterates through all options in an
+ /// options collection, checks the offset of the option
+ /// in input buffer and reads data from the buffer to
+ /// update the option's buffer. If the provided options collection
+ /// is empty, a call to this method will have no effect.
+ ///
+ /// \param universe universe used, V4 or V6
+ /// \param in_buffer input buffer to be parsed.
+ /// \param options oprions collection with their offsets
+ /// in input buffer specified.
+ ///
+ /// \throw isc::Unexpected if options unpack failed.
+ static void unpackOptions(const dhcp::OptionBuffer& in_buffer,
+ const dhcp::Option::OptionCollection& options);
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __PKT_TRANSFORM_H
diff --git a/tests/tools/perfdhcp/tests/.gitignore b/tests/tools/perfdhcp/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
new file mode 100644
index 0000000..ab1251c
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/Makefile.am
@@ -0,0 +1,46 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += command_options_unittest.cc
+run_unittests_SOURCES += perf_pkt6_unittest.cc
+run_unittests_SOURCES += perf_pkt4_unittest.cc
+run_unittests_SOURCES += localized_option_unittest.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+run_unittests_CXXFLAGS = -Wno-unused-parameter
+endif
+
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/tests/tools/perfdhcp/tests/command_options_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc
new file mode 100644
index 0000000..8e1053d
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/command_options_unittest.cc
@@ -0,0 +1,454 @@
+// 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 <cstddef>
+#include <stdint.h>
+#include <string>
+#include <gtest/gtest.h>
+
+#include "../command_options.h"
+
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::perfdhcp;
+
+/// \brief Test Fixture Class
+///
+/// This test fixture class is used to perform
+/// unit tests on perfdhcp CommandOptions class.
+class CommandOptionsTest : public virtual ::testing::Test
+{
+public:
+ /// \brief Default Constructor
+ CommandOptionsTest() { }
+
+protected:
+ /// \brief Parse command line and cleanup
+ ///
+ /// The method tokenizes command line to array of C-strings,
+ /// parses arguments using CommandOptions class to set
+ /// its data members and de-allocates array of C-strings.
+ ///
+ /// \param cmdline Command line to parse
+ /// \throws std::bad allocation if tokenization failed
+ void process(const std::string& cmdline) {
+ CommandOptions& opt = CommandOptions::instance();
+ int argc = 0;
+ char** argv = tokenizeString(cmdline, &argc);
+ opt.reset();
+ opt.parse(argc, argv);
+ for(int i = 0; i < argc; ++i) {
+ free(argv[i]);
+ argv[i] = NULL;
+ }
+ free(argv);
+ }
+
+ /// \brief Check default initialized values
+ ///
+ /// Check if initialized values are correct
+ void checkDefaults() {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp");
+ EXPECT_EQ(4, opt.getIpVersion());
+ EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
+ EXPECT_EQ(0, opt.getRate());
+ EXPECT_EQ(0, opt.getReportDelay());
+ EXPECT_EQ(0, opt.getClientsNum());
+
+ // default mac
+ uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
+ std::vector<uint8_t> v1 = opt.getMacPrefix();
+ ASSERT_EQ(6, v1.size());
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+
+ EXPECT_EQ(0, opt.getBase().size());
+ EXPECT_EQ(0, opt.getNumRequests().size());
+ EXPECT_EQ(0, opt.getPeriod());
+ for (int i = 0; i < opt.getDropTime().size(); ++i) {
+ EXPECT_DOUBLE_EQ(1, opt.getDropTime()[i]);
+ }
+ ASSERT_EQ(opt.getMaxDrop().size(), opt.getMaxDropPercentage().size());
+ for (int i = 0; i < opt.getMaxDrop().size(); ++i) {
+ EXPECT_EQ(0, opt.getMaxDrop()[i]);
+ EXPECT_EQ(0, opt.getMaxDropPercentage()[i]);
+ }
+ EXPECT_EQ("", opt.getLocalName());
+ EXPECT_FALSE(opt.isInterface());
+ EXPECT_EQ(0, opt.getPreload());
+ EXPECT_EQ(1, opt.getAggressivity());
+ EXPECT_EQ(0, opt.getLocalPort());
+ EXPECT_FALSE(opt.isSeeded());
+ EXPECT_EQ(0, opt.getSeed());
+ EXPECT_FALSE(opt.isBroadcast());
+ EXPECT_FALSE(opt.isRapidCommit());
+ EXPECT_FALSE(opt.isUseFirst());
+ EXPECT_EQ(0, opt.getTemplateFiles().size());
+ EXPECT_EQ(0, opt.getTransactionIdOffset().size());
+ EXPECT_EQ(0, opt.getRandomOffset().size());
+ EXPECT_GT(0, opt.getElapsedTimeOffset());
+ EXPECT_GT(0, opt.getServerIdOffset());
+ EXPECT_GT(0, opt.getRequestedIpOffset());
+ EXPECT_EQ("", opt.getDiags());
+ EXPECT_EQ("", opt.getWrapped());
+ EXPECT_EQ("", opt.getServerName());
+ }
+
+ /// \brief Split string to array of C-strings
+ ///
+ /// \param s String to split (tokenize)
+ /// \param num Number of tokens returned
+ /// \return array of C-strings (tokens)
+ char** tokenizeString(const std::string& text_to_split, int* num) const {
+ char** results = NULL;
+ // Tokenization with std streams
+ std::stringstream text_stream(text_to_split);
+ // Iterators to be used for tokenization
+ std::istream_iterator<std::string> text_iterator(text_stream);
+ std::istream_iterator<std::string> text_end;
+ // Tokenize string (space is a separator) using begin and end iteratos
+ std::vector<std::string> tokens(text_iterator, text_end);
+
+ if (tokens.size() > 0) {
+ // Allocate array of C-strings where we will store tokens
+ results = static_cast<char**>(malloc(tokens.size() * sizeof(char*)));
+ if (results == NULL) {
+ throw std::bad_alloc();
+ }
+ // Store tokens in C-strings array
+ for (int i = 0; i < tokens.size(); ++i) {
+ char* cs = static_cast<char*>(malloc(tokens[i].length() + 1));
+ strcpy(cs, tokens[i].c_str());
+ results[i] = cs;
+ }
+ // Return number of tokens to calling function
+ if (num != NULL) {
+ *num = tokens.size();
+ }
+ }
+ return results;
+ }
+
+};
+
+TEST_F(CommandOptionsTest, Defaults) {
+ process("perfdhcp");
+ checkDefaults();
+}
+
+TEST_F(CommandOptionsTest, UseFirst) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -1 -B -l ethx");
+ EXPECT_TRUE(opt.isUseFirst());
+}
+TEST_F(CommandOptionsTest, IpVersion) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -l ethx -c -i");
+ EXPECT_EQ(6, opt.getIpVersion());
+ EXPECT_EQ("ethx", opt.getLocalName());
+ EXPECT_TRUE(opt.isRapidCommit());
+ EXPECT_FALSE(opt.isBroadcast());
+ process("perfdhcp -4 -B -l ethx");
+ EXPECT_EQ(4, opt.getIpVersion());
+ EXPECT_TRUE(opt.isBroadcast());
+ EXPECT_FALSE(opt.isRapidCommit());
+
+ // Negative test cases
+ // -4 and -6 must not coexist
+ EXPECT_THROW(process("perfdhcp -4 -6 -l ethx"), isc::InvalidParameter);
+ // -6 and -B must not coexist
+ EXPECT_THROW(process("perfdhcp -6 -B -l ethx"), isc::InvalidParameter);
+ // -c and -4 (default) must not coexist
+ EXPECT_THROW(process("perfdhcp -c -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Rate) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -4 -r 10 -l ethx");
+ EXPECT_EQ(10, opt.getRate());
+
+ // Negative test cases
+ // Rate must not be 0
+ EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx"), isc::InvalidParameter);
+ // -r must be specified to use -n, -p and -D
+ EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ReportDelay) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -r 100 -t 17 -l ethx");
+ EXPECT_EQ(17, opt.getReportDelay());
+
+ // Negative test cases
+ // -t must be positive integer
+ EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ClientsNum) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -R 200 -l ethx");
+ EXPECT_EQ(200, opt.getClientsNum());
+ process("perfdhcp -R 0 -l ethx");
+ EXPECT_EQ(0, opt.getClientsNum());
+
+ // Negative test cases
+ // Number of clients must be non-negative integer
+ EXPECT_THROW(process("perfdhcp -R -5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -R gs -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Base) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -b MAC=10::20::30::40::50::60 -l ethx -b duiD=1AB7F5670901FF");
+ uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
+ uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF };
+
+ // Test Mac
+ std::vector<uint8_t> v1 = opt.getMacPrefix();
+ ASSERT_EQ(6, v1.size());
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+ // "3x" is invalid value in MAC address
+ EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx"), isc::InvalidParameter);
+
+ // Test DUID
+ std::vector<uint8_t> v2 = opt.getDuidPrefix();
+ ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size());
+ EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
+ // "t" is invalid digit in DUID
+ EXPECT_THROW(process("perfdhcp -6 -l ethx -b duiD=1AB7Ft670901FF"), isc::InvalidParameter);
+
+ // Some more negative test cases
+ // Base is not specified
+ EXPECT_THROW(process("perfdhcp -b -l ethx"), isc::InvalidParameter);
+ // Typo: should be mac= instead of mc=
+ EXPECT_THROW(process("perfdhcp -l ethx -b mc=00:01:02:03::04:05"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, DropTime) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -d 12");
+ ASSERT_EQ(2, opt.getDropTime().size());
+ EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]);
+ EXPECT_DOUBLE_EQ(1, opt.getDropTime()[1]);
+
+ process("perfdhcp -l ethx -d 2 -d 4.7");
+ ASSERT_EQ(2, opt.getDropTime().size());
+ EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]);
+ EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]);
+
+ // Negative test cases
+ // Drop time must not be negative
+ EXPECT_THROW(process("perfdhcp -l ethx -d -2 -d 4.7"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, TimeOffset) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -T file1.x -T file2.x -E 4");
+ EXPECT_EQ(4, opt.getElapsedTimeOffset());
+
+ // Negative test cases
+ // Argument -E must be used with -T
+ EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i"), isc::InvalidParameter);
+ // Value in -E not specified
+ EXPECT_THROW(process("perfdhcp -l ethx -T file.x -E -i"), isc::InvalidParameter);
+ // Value for -E must not be negative
+ EXPECT_THROW(process("perfdhcp -l ethx -E -3 -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ExchangeMode) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -i");
+ EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode());
+
+ // Negative test cases
+ // No template file specified
+ EXPECT_THROW(process("perfdhcp -i -l ethx -X 3"), isc::InvalidParameter);
+ // Offsets can't be used in simple exchanges (-i)
+ EXPECT_THROW(process("perfdhcp -i -l ethx -O 2 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Offsets) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx -X3 -T file1.x -T file2.x");
+ EXPECT_EQ(2, opt.getRequestedIpOffset());
+ EXPECT_EQ(5, opt.getElapsedTimeOffset());
+ EXPECT_EQ(3, opt.getServerIdOffset());
+ ASSERT_EQ(2, opt.getRandomOffset().size());
+ EXPECT_EQ(30, opt.getRandomOffset()[0]);
+ EXPECT_EQ(30, opt.getRandomOffset()[1]);
+ ASSERT_EQ(2, opt.getTransactionIdOffset().size());
+ EXPECT_EQ(7, opt.getTransactionIdOffset()[0]);
+ EXPECT_EQ(3, opt.getTransactionIdOffset()[1]);
+
+ // Negative test cases
+ // IP offset/IA_NA offset must be positive
+ EXPECT_THROW(process("perfdhcp -6 -I 0 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx"), isc::InvalidParameter);
+
+ // TODO - other negative cases
+}
+
+TEST_F(CommandOptionsTest, LocalPort) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -L 2000");
+ EXPECT_EQ(2000, opt.getLocalPort());
+
+ // Negative test cases
+ // Local port must be between 0..65535
+ EXPECT_THROW(process("perfdhcp -l ethx -L -2"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L 65540"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Preload) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -1 -P 3 -l ethx");
+ EXPECT_EQ(3, opt.getPreload());
+
+ // Negative test cases
+ // Number of preload packages must not be negative integer
+ EXPECT_THROW(process("perfdhcp -P -1 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -P -3 -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Seed) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -P 2 -s 23 -l ethx");
+ EXPECT_EQ(23, opt.getSeed());
+ EXPECT_TRUE(opt.isSeeded());
+
+ process("perfdhcp -6 -P 2 -s 0 -l ethx");
+ EXPECT_EQ(0, opt.getSeed());
+ EXPECT_FALSE(opt.isSeeded());
+
+ // Negtaive test cases
+ // Seed must be non-negative integer
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, TemplateFiles) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -T file1.x -l ethx");
+ ASSERT_EQ(1, opt.getTemplateFiles().size());
+ EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
+
+ process("perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx");
+ ASSERT_EQ(2, opt.getTemplateFiles().size());
+ EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
+ EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]);
+
+ // Negative test cases
+ // No template file specified
+ EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T"), isc::InvalidParameter);
+ // Too many template files specified
+ EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T file.x -T file.x -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Wrapped) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -B -w start -i -l ethx");
+ EXPECT_EQ("start", opt.getWrapped());
+
+ // Negative test cases
+ // Missing command after -w, expected start/stop
+ EXPECT_THROW(process("perfdhcp -B -i -l ethx -w"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Diagnostics) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -i -x asTe");
+ EXPECT_EQ("asTe", opt.getDiags());
+
+ // Negative test cases
+ // No diagnostics string specified
+ EXPECT_THROW(process("perfdhcp -l ethx -i -x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Aggressivity) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -a 10 -l 192.168.0.1");
+ EXPECT_EQ(10, opt.getAggressivity());
+
+ // Negative test cases
+ // Aggressivity must be non negative integer
+ EXPECT_THROW(process("perfdhcp -l ethx -a 0"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -a"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, MaxDrop) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -D 25 -l ethx -r 10");
+ EXPECT_EQ(25, opt.getMaxDrop()[0]);
+ process("perfdhcp -D 25 -l ethx -D 15 -r 10");
+ EXPECT_EQ(25, opt.getMaxDrop()[0]);
+ EXPECT_EQ(15, opt.getMaxDrop()[1]);
+
+ process("perfdhcp -D 15% -l ethx -r 10");
+ EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
+ process("perfdhcp -D 15% -D25% -l ethx -r 10");
+ EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
+ EXPECT_EQ(25, opt.getMaxDropPercentage()[1]);
+ process("perfdhcp -D 1% -D 99% -l ethx -r 10");
+ EXPECT_EQ(1, opt.getMaxDropPercentage()[0]);
+ EXPECT_EQ(99, opt.getMaxDropPercentage()[1]);
+
+ // Negative test cases
+ // Too many -D<value> options
+ EXPECT_THROW(process("perfdhcp -D 0% -D 1 -l ethx -r20 -D 3"), isc::InvalidParameter);
+ // Too many -D<value%> options
+ EXPECT_THROW(process("perfdhcp -D 99% -D 13% -l ethx -r20 -D 10%"), isc::InvalidParameter);
+ // Percentage is out of bounds
+ EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, NumRequest) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -n 1000 -r 10 -l ethx");
+ EXPECT_EQ(1000, opt.getNumRequests()[0]);
+ process("perfdhcp -n 5 -r 10 -n 500 -l ethx");
+ EXPECT_EQ(5, opt.getNumRequests()[0]);
+ EXPECT_EQ(500, opt.getNumRequests()[1]);
+
+ // Negative test cases
+ // Too many -n<value> parameters, expected maximum 2
+ EXPECT_THROW(process("perfdhcp -n 1 -n 2 -l ethx -n3 -r 20"), isc::InvalidParameter);
+ // Num request must be positive integer
+ EXPECT_THROW(process("perfdhcp -n 1 -n -22 -l ethx -r 10"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Period) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -p 120 -l ethx -r 100");
+ EXPECT_EQ(120, opt.getPeriod());
+
+ // Negative test cases
+ // Test period must be positive integer
+ EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50"), isc::InvalidParameter);
+}
diff --git a/tests/tools/perfdhcp/tests/localized_option_unittest.cc b/tests/tools/perfdhcp/tests/localized_option_unittest.cc
new file mode 100644
index 0000000..e51560e
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/localized_option_unittest.cc
@@ -0,0 +1,48 @@
+// 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 <config.h>
+#include <gtest/gtest.h>
+
+#include <dhcp/option.h>
+#include <dhcp/dhcp6.h>
+
+#include "../localized_option.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::perfdhcp;
+
+namespace {
+
+TEST(LocalizedOptionTest, Constructor) {
+ OptionBuffer opt_buf;
+ // Create option with default offset.
+ boost::scoped_ptr<LocalizedOption> opt1(new LocalizedOption(Option::V6,
+ D6O_CLIENTID,
+ opt_buf));
+ EXPECT_EQ(Option::V6, opt1->getUniverse());
+ EXPECT_EQ(D6O_CLIENTID, opt1->getType());
+ EXPECT_EQ(0, opt1->getOffset());
+
+ // Create option with non-default offset.
+ boost::scoped_ptr<LocalizedOption> opt2(new LocalizedOption(Option::V6,
+ D6O_CLIENTID,
+ opt_buf,
+ 40));
+ EXPECT_EQ(40, opt2->getOffset());
+}
+
+}
diff --git a/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc b/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc
new file mode 100644
index 0000000..3863faa
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc
@@ -0,0 +1,384 @@
+// 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 <config.h>
+#include <iostream>
+#include <sstream>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcp/dhcp4.h>
+
+#include "../localized_option.h"
+#include "../perf_pkt4.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::perfdhcp;
+
+typedef PerfPkt4::LocalizedOptionPtr LocalizedOptionPtr;
+
+namespace {
+
+// A dummy MAC address, padded with 0s
+const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+// Let's use some creative test content here (128 chars + \0)
+const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit. Proin mollis placerat metus, at "
+ "lacinia orci ornare vitae. Mauris amet.";
+
+// Yet another type of test content (64 chars + \0)
+const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit posuere.";
+
+class PerfPkt4Test : public ::testing::Test {
+public:
+ PerfPkt4Test() {
+ }
+
+ /// \brief Returns buffer with sample DHCPDISCOVER message.
+ ///
+ /// This method creates buffer containing on-wire data of
+ /// DHCPDICOSVER message. This buffer is used by tests below
+ /// to create DHCPv4 test packets.
+ ///
+ /// \return vector containing on-wire data
+ std::vector<uint8_t>& capture() {
+
+ // That is only part of the header. It contains all "short" fields,
+ // larger fields are constructed separately.
+ uint8_t hdr[] = {
+ 1, 6, 6, 13, // op, htype, hlen, hops,
+ 0x12, 0x34, 0x56, 0x78, // transaction-id
+ 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
+ 192, 0, 2, 1, // ciaddr
+ 1, 2, 3, 4, // yiaddr
+ 192, 0, 2, 255, // siaddr
+ 255, 255, 255, 255, // giaddr
+ };
+
+ uint8_t v4Opts[] = {
+ DHO_HOST_NAME, 3, 0, 1, 2, // Host name option.
+ DHO_BOOT_SIZE, 3, 10, 11, 12, // Boot file size option
+ DHO_MERIT_DUMP, 3, 20, 21, 22, // Merit dump file
+ DHO_DHCP_MESSAGE_TYPE, 1, 1, // DHCP message type.
+ 128, 3, 30, 31, 32,
+ 254, 3, 40, 41, 42,
+ };
+
+ // Initialize the vector with the header fields defined above.
+ static std::vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+
+ // If this is a first call to this function. Initialize
+ // remaining data.
+ if (buf.size() == sizeof(hdr)) {
+
+ // Append the large header fields.
+ std::copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN,
+ back_inserter(buf));
+ std::copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN,
+ back_inserter(buf));
+ std::copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN,
+ back_inserter(buf));
+
+ // Append magic cookie.
+ buf.push_back(0x63);
+ buf.push_back(0x82);
+ buf.push_back(0x53);
+ buf.push_back(0x63);
+
+ // Append options.
+ std::copy(v4Opts, v4Opts + sizeof(v4Opts), back_inserter(buf));
+ }
+ return buf;
+ }
+};
+
+TEST_F(PerfPkt4Test, Constructor) {
+ // Initialize some dummy payload.
+ uint8_t data[250];
+ for (int i = 0; i < 250; ++i) {
+ data[i] = i;
+ }
+
+ // Test constructor to be used for incoming messages.
+ // Use default (1) offset value and don't specify transaction id.
+ const size_t offset_transid[] = { 1, 10 };
+ boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(data,
+ sizeof(data),
+ offset_transid[0]));
+ EXPECT_EQ(1, pkt1->getTransidOffset());
+
+ // Test constructor to be used for outgoing messages.
+ // Use non-zero offset and specify transaction id.
+ const uint32_t transid = 0x010203;
+ boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(data, sizeof(data),
+ offset_transid[1],
+ transid));
+ EXPECT_EQ(transid, pkt2->getTransid());
+ EXPECT_EQ(offset_transid[1], pkt2->getTransidOffset());
+
+ // Test default constructor. Transaction id offset is expected to be 1.
+ boost::scoped_ptr<PerfPkt4> pkt3(new PerfPkt4(data, sizeof(data)));
+ EXPECT_EQ(1, pkt3->getTransidOffset());
+}
+
+TEST_F(PerfPkt4Test, RawPack) {
+ // Create new packet.
+ std::vector<uint8_t> buf = capture();
+ boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size()));
+
+ // Initialize options data.
+ uint8_t buf_hostname[] = { DHO_HOST_NAME, 3, 4, 5, 6 };
+ uint8_t buf_boot_filesize[] = { DHO_BOOT_SIZE, 3, 1, 2, 3 };
+ OptionBuffer vec_hostname(buf_hostname + 2,
+ buf_hostname + sizeof(buf_hostname));
+ OptionBuffer vec_boot_filesize(buf_boot_filesize + 2,
+ buf_boot_filesize + sizeof(buf_hostname));
+
+ // Create options objects.
+ const size_t offset_hostname = 240;
+ LocalizedOptionPtr pkt_hostname(new LocalizedOption(Option::V4,
+ DHO_HOST_NAME,
+ vec_hostname,
+ offset_hostname));
+ const size_t offset_boot_filesize = 245;
+ LocalizedOptionPtr pkt_boot_filesize(new LocalizedOption(Option::V4,
+ DHO_BOOT_SIZE,
+ vec_boot_filesize,
+ offset_boot_filesize));
+
+ // Try to add options to packet.
+ ASSERT_NO_THROW(pkt->addOption(pkt_boot_filesize));
+ ASSERT_NO_THROW(pkt->addOption(pkt_hostname));
+
+ // We have valid options addedwith valid offsets so
+ // pack operation should succeed.
+ ASSERT_TRUE(pkt->rawPack());
+
+ // Buffer should now contain new values of DHO_HOST_NAME and
+ // DHO_BOOT_SIZE options.
+ util::OutputBuffer pkt_output = pkt->getBuffer();
+ ASSERT_EQ(buf.size(), pkt_output.getLength());
+ const uint8_t* out_buf_data =
+ static_cast<const uint8_t*>(pkt_output.getData());
+
+ // Check if options we read from buffer is valid.
+ EXPECT_EQ(0, memcmp(buf_hostname,
+ out_buf_data + offset_hostname,
+ sizeof(buf_hostname)));
+ EXPECT_EQ(0, memcmp(buf_boot_filesize,
+ out_buf_data + offset_boot_filesize,
+ sizeof(buf_boot_filesize)));
+}
+
+TEST_F(PerfPkt4Test, RawUnpack) {
+ // Create new packet.
+ std::vector<uint8_t> buf = capture();
+ boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size()));
+
+ // Create options (existing in the packet) and specify their offsets.
+ const size_t offset_merit = 250;
+ LocalizedOptionPtr opt_merit(new LocalizedOption(Option::V4,
+ DHO_MERIT_DUMP,
+ OptionBuffer(),
+ offset_merit));
+
+ const size_t offset_msg_type = 255;
+ LocalizedOptionPtr opt_msg_type(new LocalizedOption(Option::V4,
+ DHO_DHCP_MESSAGE_TYPE,
+ OptionBuffer(),
+ offset_msg_type));
+ // Addition should be successful
+ ASSERT_NO_THROW(pkt->addOption(opt_merit));
+ ASSERT_NO_THROW(pkt->addOption(opt_msg_type));
+
+ // Option fit to packet boundaries and offsets are valid,
+ // so this should unpack successfully.
+ ASSERT_TRUE(pkt->rawUnpack());
+
+ // At this point we should have updated options data (read from buffer).
+ // Let's try to retrieve them.
+ opt_merit = boost::dynamic_pointer_cast<LocalizedOption>
+ (pkt->getOption(DHO_MERIT_DUMP));
+ opt_msg_type = boost::dynamic_pointer_cast<LocalizedOption>
+ (pkt->getOption(DHO_DHCP_MESSAGE_TYPE));
+ ASSERT_TRUE(opt_merit);
+ ASSERT_TRUE(opt_msg_type);
+
+ // Get first option payload.
+ OptionBuffer opt_merit_data = opt_merit->getData();
+
+ // Define reference data.
+ uint8_t buf_merit[] = { 20, 21, 22 };
+
+ // Validate first option data.
+ ASSERT_EQ(sizeof(buf_merit), opt_merit_data.size());
+ EXPECT_TRUE(std::equal(opt_merit_data.begin(),
+ opt_merit_data.end(),
+ buf_merit));
+
+ // Get second option payload.
+ OptionBuffer opt_msg_type_data = opt_msg_type->getData();
+
+ // Expect one byte of message type payload.
+ ASSERT_EQ(1, opt_msg_type_data.size());
+ EXPECT_EQ(1, opt_msg_type_data[0]);
+}
+
+TEST_F(PerfPkt4Test, InvalidOptions) {
+ // Create new packet.
+ std::vector<uint8_t> buf = capture();
+ boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&buf[0], buf.size()));
+
+ // Create option with invalid offset.
+ // This option is at offset 250 (not 251).
+ const size_t offset_merit = 251;
+ LocalizedOptionPtr opt_merit(new LocalizedOption(Option::V4,
+ DHO_MERIT_DUMP,
+ OptionBuffer(),
+ offset_merit));
+ ASSERT_NO_THROW(pkt1->addOption(opt_merit));
+
+ cout << "Testing unpack of invalid options. "
+ << "This may produce spurious errors." << endl;
+
+ // Unpack is expected to fail because it is supposed to read
+ // option type from buffer and match it with DHO_MERIT_DUMP.
+ // It will not match because option is shifted by on byte.
+ ASSERT_FALSE(pkt1->rawUnpack());
+
+ // Create another packet.
+ boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&buf[0], buf.size()));
+
+ // Create DHO_DHCP_MESSAGE_TYPE option that has the wrong offset.
+ // With this offset, option goes beyond packet size (268).
+ const size_t offset_msg_type = 266;
+ LocalizedOptionPtr opt_msg_type(new LocalizedOption(Option::V4,
+ DHO_DHCP_MESSAGE_TYPE,
+ OptionBuffer(1, 2),
+ offset_msg_type));
+ // Adding option is expected to be successful because no
+ // offset validation takes place at this point.
+ ASSERT_NO_THROW(pkt2->addOption(opt_msg_type));
+
+ // This is expected to fail because option is out of bounds.
+ ASSERT_FALSE(pkt2->rawPack());
+}
+
+TEST_F(PerfPkt4Test, TruncatedPacket) {
+ // Get the whole packet and truncate it to 249 bytes.
+ std::vector<uint8_t> buf = capture();
+ buf.resize(249);
+ boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size()));
+
+ // Option DHO_BOOT_SIZE is now truncated because whole packet
+ // is truncated. This option ends at 249 while last index of
+ // truncated packet is now 248.
+ const size_t offset_boot_filesize = 245;
+ LocalizedOptionPtr opt_boot_filesize(new LocalizedOption(Option::V4,
+ DHO_BOOT_SIZE,
+ OptionBuffer(3, 1),
+ offset_boot_filesize));
+ ASSERT_NO_THROW(pkt->addOption(opt_boot_filesize));
+
+ cout << "Testing pack and unpack of options in truncated "
+ << "packet. This may produce spurious errors." << endl;
+
+ // Both pack and unpack are expected to fail because
+ // added option is out of bounds.
+ EXPECT_FALSE(pkt->rawUnpack());
+ EXPECT_FALSE(pkt->rawPack());
+}
+
+TEST_F(PerfPkt4Test, PackTransactionId) {
+ // Create dummy packet that consists of zeros.
+ std::vector<uint8_t> buf(268, 0);
+
+ const size_t offset_transid[] = { 10, 265 };
+ const uint32_t transid = 0x0102;
+ // Initialize transaction id 0x00000102 at offset 10.
+ boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&buf[0], buf.size(),
+ offset_transid[0],
+ transid));
+
+ // Pack will inject transaction id at offset 10 into the
+ // packet buffer.
+ ASSERT_TRUE(pkt1->rawPack());
+
+ // Get packet's output buffer and make sure it has valid size.
+ util::OutputBuffer out_buf = pkt1->getBuffer();
+ ASSERT_EQ(buf.size(), out_buf.getLength());
+ const uint8_t *out_buf_data =
+ static_cast<const uint8_t*>(out_buf.getData());
+
+ // Initialize reference data for transaction id.
+ const uint8_t ref_data[] = { 0, 0, 1, 2 };
+
+ // Expect that reference transaction id matches what we have
+ // read from buffer.
+ EXPECT_EQ(0, memcmp(ref_data, out_buf_data + offset_transid[0], 4));
+
+ cout << "Testing pack with invalid transaction id offset. "
+ << "This may produce spurious errors" << endl;
+
+ // Create packet with invalid transaction id offset.
+ // Packet length is 268, transaction id is 4 bytes long so last byte of
+ // transaction id is out of bounds.
+ boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&buf[0], buf.size(),
+ offset_transid[1],
+ transid));
+ EXPECT_FALSE(pkt2->rawPack());
+}
+
+TEST_F(PerfPkt4Test, UnpackTransactionId) {
+ // Initialize packet data, lebgth 268, zeros only.
+ std::vector<uint8_t> in_data(268, 0);
+
+ // Assume that transaction id is at offset 100.
+ // Fill 4 bytes at offset 100 with dummy transaction id.
+ for (int i = 100; i < 104; ++i) {
+ in_data[i] = i - 99;
+ }
+
+ // Create packet from initialized buffer.
+ const size_t offset_transid[] = { 100, 270 };
+ boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&in_data[0],
+ in_data.size(),
+ offset_transid[0]));
+ ASSERT_TRUE(pkt1->rawUnpack());
+
+ // Get unpacked transaction id and compare with reference.
+ EXPECT_EQ(0x01020304, pkt1->getTransid());
+
+ // Create packet with transaction id at invalid offset.
+ boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&in_data[0],
+ in_data.size(),
+ offset_transid[1]));
+
+ cout << "Testing unpack of transaction id at invalid offset. "
+ << "This may produce spurious errors." << endl;
+
+ // Unpack is supposed to fail because transaction id is at
+ // out of bounds offset.
+ EXPECT_FALSE(pkt2->rawUnpack());
+}
+
+}
diff --git a/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc b/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
new file mode 100644
index 0000000..de134cc
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
@@ -0,0 +1,327 @@
+// 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 <config.h>
+#include <iostream>
+#include <sstream>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcp/dhcp6.h>
+
+#include "../localized_option.h"
+#include "../perf_pkt6.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::perfdhcp;
+
+typedef PerfPkt6::LocalizedOptionPtr LocalizedOptionPtr;
+
+namespace {
+
+class PerfPkt6Test : public ::testing::Test {
+public:
+ PerfPkt6Test() {
+ }
+
+ /// \brief Returns captured SOLICIT packet.
+ ///
+ /// Captured SOLICIT packet with transid=0x3d79fb and options: client-id,
+ /// in_na, dns-server, elapsed-time, option-request
+ /// This code was autogenerated
+ /// (see src/bin/dhcp6/tests/iface_mgr_unittest.c),
+ /// but we spent some time to make is less ugly than it used to be.
+ ///
+ /// \return pointer to Pkt6 that represents received SOLICIT
+ PerfPkt6* capture() {
+ uint8_t data[98];
+ data[0] = 1;
+ data[1] = 1; data[2] = 2; data[3] = 3; data[4] = 0;
+ data[5] = 1; data[6] = 0; data[7] = 14; data[8] = 0;
+ data[9] = 1; data[10] = 0; data[11] = 1; data[12] = 21;
+ data[13] = 158; data[14] = 60; data[15] = 22; data[16] = 0;
+ data[17] = 30; data[18] = 140; data[19] = 155; data[20] = 115;
+ data[21] = 73; data[22] = 0; data[23] = 3; data[24] = 0;
+ data[25] = 40; data[26] = 0; data[27] = 0; data[28] = 0;
+ data[29] = 1; data[30] = 255; data[31] = 255; data[32] = 255;
+ data[33] = 255; data[34] = 255; data[35] = 255; data[36] = 255;
+ data[37] = 255; data[38] = 0; data[39] = 5; data[40] = 0;
+ data[41] = 24; data[42] = 32; data[43] = 1; data[44] = 13;
+ data[45] = 184; data[46] = 0; data[47] = 1; data[48] = 0;
+ data[49] = 0; data[50] = 0; data[51] = 0; data[52] = 0;
+ data[53] = 0; data[54] = 0; data[55] = 0; data[56] = 18;
+ data[57] = 52; data[58] = 255; data[59] = 255; data[60] = 255;
+ data[61] = 255; data[62] = 255; data[63] = 255; data[64] = 255;
+ data[65] = 255; data[66] = 0; data[67] = 23; data[68] = 0;
+ data[69] = 16; data[70] = 32; data[71] = 1; data[72] = 13;
+ data[73] = 184; data[74] = 0; data[75] = 1; data[76] = 0;
+ data[77] = 0; data[78] = 0; data[79] = 0; data[80] = 0;
+ data[81] = 0; data[82] = 0; data[83] = 0; data[84] = 221;
+ data[85] = 221; data[86] = 0; data[87] = 8; data[88] = 0;
+ data[89] = 2; data[90] = 0; data[91] = 100; data[92] = 0;
+ data[93] = 6; data[94] = 0; data[95] = 2; data[96] = 0;
+ data[97] = 23;
+
+ PerfPkt6* pkt = new PerfPkt6(data, sizeof(data));
+
+ return (pkt);
+ }
+
+ /// \brief Returns truncated SOLICIT packet.
+ ///
+ /// Returns truncated SOLICIT packet which will be used for
+ /// negative tests: e.g. pack options out of packet.
+ ///
+ /// \return pointer to Pkt6 that represents truncated SOLICIT
+ PerfPkt6* captureTruncated() {
+ uint8_t data[17];
+ data[0] = 1;
+ data[1] = 1; data[2] = 2; data[3] = 3; data[4] = 0;
+ data[5] = 1; data[6] = 0; data[7] = 14; data[8] = 0;
+ data[9] = 1; data[10] = 0; data[11] = 1; data[12] = 21;
+ data[13] = 158; data[14] = 60; data[15] = 22; data[16] = 0;
+
+ PerfPkt6* pkt = new PerfPkt6(data, sizeof(data));
+
+ return (pkt);
+ }
+
+
+};
+
+TEST_F(PerfPkt6Test, Constructor) {
+ // Data to be used to create packet.
+ uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
+
+ // Test constructor to be used for incoming messages.
+ // Use default (1) offset value and don't specify transaction id.
+ boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, sizeof(data)));
+ EXPECT_EQ(sizeof(data), pkt1->getData().size());
+ EXPECT_EQ(0, memcmp(&pkt1->getData()[0], data, sizeof(data)));
+ EXPECT_EQ(1, pkt1->getTransidOffset());
+
+ // Test constructor to be used for outgoing messages.
+ // Use non-zero offset and specify transaction id.
+ const size_t offset_transid = 10;
+ const uint32_t transid = 0x010203;
+ boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, sizeof(data),
+ offset_transid, transid));
+ EXPECT_EQ(sizeof(data), pkt2->getData().size());
+ EXPECT_EQ(0, memcmp(&pkt2->getData()[0], data, sizeof(data)));
+ EXPECT_EQ(0x010203, pkt2->getTransid());
+ EXPECT_EQ(10, pkt2->getTransidOffset());
+}
+
+TEST_F(PerfPkt6Test, RawPackUnpack) {
+ // Create first packet.
+ boost::scoped_ptr<PerfPkt6> pkt1(capture());
+
+ // Create some input buffers to initialize options.
+ uint8_t buf_elapsed_time[] = { 1, 1 };
+ uint8_t buf_duid[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
+
+ // Create options.
+ const size_t offset_elapsed_time = 86;
+ OptionBuffer vec_elapsed_time(buf_elapsed_time,
+ buf_elapsed_time + sizeof(buf_elapsed_time));
+ LocalizedOptionPtr pkt1_elapsed_time(new LocalizedOption(Option::V6,
+ D6O_ELAPSED_TIME,
+ vec_elapsed_time,
+ offset_elapsed_time));
+ const size_t offset_duid = 4;
+ OptionBuffer vec_duid(buf_duid, buf_duid + sizeof(buf_duid));
+ LocalizedOptionPtr pkt1_duid(new LocalizedOption(Option::V6,
+ D6O_CLIENTID,
+ vec_duid,
+ offset_duid));
+
+ // Add option to packet and create on-wire format from added options.
+ // Contents of options will override contents of packet buffer.
+ ASSERT_NO_THROW(pkt1->addOption(pkt1_elapsed_time));
+ ASSERT_NO_THROW(pkt1->addOption(pkt1_duid));
+ ASSERT_TRUE(pkt1->rawPack());
+
+ // Reset so as we can reuse them for another packet.
+ vec_elapsed_time.clear();
+ vec_duid.clear();
+
+ // Get output buffer from packet 1 to create new packet
+ // that will be later validated.
+ util::OutputBuffer pkt1_output = pkt1->getBuffer();
+ ASSERT_EQ(pkt1_output.getLength(), pkt1->getData().size());
+ const uint8_t* pkt1_output_data = static_cast<const uint8_t*>
+ (pkt1_output.getData());
+ boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(pkt1_output_data,
+ pkt1_output.getLength()));
+
+ // Create objects specifying options offset in a packet.
+ // Offsets will inform pkt2 object where to read data from.
+ LocalizedOptionPtr pkt2_elapsed_time(new LocalizedOption(Option::V6,
+ D6O_ELAPSED_TIME,
+ vec_elapsed_time,
+ offset_elapsed_time));
+ LocalizedOptionPtr pkt2_duid(new LocalizedOption(Option::V6,
+ D6O_CLIENTID,
+ vec_duid,
+ offset_duid));
+ // Add options to packet to pass their offsets.
+ pkt2->addOption(pkt2_elapsed_time);
+ pkt2->addOption(pkt2_duid);
+
+ // Unpack: get relevant parts of buffer data into option objects.
+ ASSERT_TRUE(pkt2->rawUnpack());
+
+ // Once option data is stored in options objects we pull it out.
+ pkt2_elapsed_time = boost::dynamic_pointer_cast<LocalizedOption>
+ (pkt2->getOption(D6O_ELAPSED_TIME));
+ pkt2_duid = boost::dynamic_pointer_cast<LocalizedOption>
+ (pkt2->getOption(D6O_CLIENTID));
+
+ // Check if options are present. They have to be there since
+ // we have added them ourselfs.
+ ASSERT_TRUE(pkt2_elapsed_time);
+ ASSERT_TRUE(pkt2_duid);
+
+ // Expecting option contents be the same as original.
+ OptionBuffer pkt2_elapsed_time_data = pkt2_elapsed_time->getData();
+ OptionBuffer pkt2_duid_data = pkt2_duid->getData();
+ EXPECT_EQ(0x0101, pkt2_elapsed_time->getUint16());
+ EXPECT_TRUE(std::equal(pkt2_duid_data.begin(),
+ pkt2_duid_data.end(),
+ buf_duid));
+}
+
+TEST_F(PerfPkt6Test, InvalidOptions) {
+ // Create packet.
+ boost::scoped_ptr<PerfPkt6> pkt1(capture());
+ OptionBuffer vec_server_id;
+ vec_server_id.resize(10);
+ // Testing invalid offset of the option (greater than packet size)
+ const size_t offset_serverid[] = { 150, 85 };
+ LocalizedOptionPtr pkt1_serverid(new LocalizedOption(Option::V6,
+ D6O_SERVERID,
+ vec_server_id,
+ offset_serverid[0]));
+ pkt1->addOption(pkt1_serverid);
+ // Pack has to fail due to invalid offset.
+ EXPECT_FALSE(pkt1->rawPack());
+
+ // Create packet.
+ boost::scoped_ptr<PerfPkt6> pkt2(capture());
+ // Testing offset of the option (lower than pakcet size but
+ // tail of the option out of bounds).
+ LocalizedOptionPtr pkt2_serverid(new LocalizedOption(Option::V6,
+ D6O_SERVERID,
+ vec_server_id,
+ offset_serverid[1]));
+ pkt2->addOption(pkt2_serverid);
+ // Pack must fail due to invalid offset.
+ EXPECT_FALSE(pkt2->rawPack());
+}
+
+
+TEST_F(PerfPkt6Test, TruncatedPacket) {
+ cout << "Testing parsing options from truncated packet."
+ << "This may produce spurious errors" << endl;
+
+ // Create truncated (in the middle of duid options)
+ boost::scoped_ptr<PerfPkt6> pkt1(captureTruncated());
+ OptionBuffer vec_duid;
+ vec_duid.resize(30);
+ const size_t offset_duid = 4;
+ LocalizedOptionPtr pkt1_duid(new LocalizedOption(Option::V6,
+ D6O_CLIENTID,
+ vec_duid,
+ offset_duid));
+ pkt1->addOption(pkt1_duid);
+ // Pack/unpack must fail because length of the option read from buffer
+ // will extend over the actual packet length.
+ EXPECT_FALSE(pkt1->rawUnpack());
+ EXPECT_FALSE(pkt1->rawPack());
+}
+
+TEST_F(PerfPkt6Test, PackTransactionId) {
+ uint8_t data[100];
+ memset(&data, 0, sizeof(data));
+
+ const size_t offset_transid[] = { 50, 100 };
+ const uint32_t transid = 0x010203;
+
+ // Create dummy packet that is simply filled with zeros.
+ boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data,
+ sizeof(data),
+ offset_transid[0],
+ transid));
+
+ // Reference data are non zero so we can detect them in dummy packet.
+ uint8_t ref_data[3] = { 1, 2, 3 };
+
+ // This will store given transaction id in the packet data at
+ // offset of 50.
+ ASSERT_TRUE(pkt1->rawPack());
+
+ // Get the output buffer so we can validate it.
+ util::OutputBuffer out_buf = pkt1->getBuffer();
+ ASSERT_EQ(sizeof(data), out_buf.getLength());
+ const uint8_t *out_buf_data = static_cast<const uint8_t*>
+ (out_buf.getData());
+
+ // Validate transaction id.
+ EXPECT_EQ(0, memcmp(out_buf_data + offset_transid[0], ref_data, 3));
+
+
+ // Out of bounds transaction id offset.
+ boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data,
+ sizeof(data),
+ offset_transid[1],
+ transid));
+ cout << "Testing out of bounds offset. "
+ "This may produce spurious errors ..." << endl;
+ EXPECT_FALSE(pkt2->rawPack());
+}
+
+TEST_F(PerfPkt6Test, UnpackTransactionId) {
+ // Initialize data for dummy packet (zeros only).
+ uint8_t data[100] = { 0 };
+
+ // Generate transaction id = 0x010203 and inject at offset = 50.
+ for (int i = 50; i < 53; ++i) {
+ data[i] = i - 49;
+ }
+ // Create packet and point out that transaction id is at offset 50.
+ const size_t offset_transid[] = { 50, 300 };
+ boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data,
+ sizeof(data),
+ offset_transid[0]));
+
+ // Get transaction id out of buffer and store in class member.
+ ASSERT_TRUE(pkt1->rawUnpack());
+ // Test value of transaction id.
+ EXPECT_EQ(0x010203, pkt1->getTransid());
+
+ // Out of bounds transaction id offset.
+ boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data,
+ sizeof(data),
+ offset_transid[1]));
+ cout << "Testing out of bounds offset. "
+ "This may produce spurious errors ..." << endl;
+ EXPECT_FALSE(pkt2->rawUnpack());
+
+}
+
+}
diff --git a/tests/tools/perfdhcp/tests/run_unittests.cc b/tests/tools/perfdhcp/tests/run_unittests.cc
new file mode 100644
index 0000000..6eeca75
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/run_unittests.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2009 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 <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (isc::util::unittests::run_all());
+}
diff --git a/tools/git-obsolete-branch.py b/tools/git-obsolete-branch.py
new file mode 100755
index 0000000..70a7788
--- /dev/null
+++ b/tools/git-obsolete-branch.py
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+#
+# 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 script lists obsolete (fully merged) branches. It is useful for periodic
+# maintenance of our GIT tree.
+#
+# It is good idea to use following command before running this script:
+#
+# git pull
+# git remote prune origin
+#
+# This script requires python 2.7 or 3.
+#
+# I have limited experience in Python. If things are done in a strange or
+# uncommon way, there are no obscure reasons to do it that way, just plain
+# lack of experience.
+#
+# tomek
+
+import string
+import subprocess
+import sys
+from optparse import OptionParser
+
+class Branch:
+ MERGED = 1
+ NOTMERGED = 2
+ name = None
+ status = NOTMERGED
+ last_commit = None
+
+
+def branch_list_get(verbose):
+ """ Generates a list of available remote branches and
+ checks their status (merged/unmerged). A branch is merged
+ if all changes on that branch are also on master. """
+
+ # call git branch -r (list of remote branches)
+ txt_list = subprocess.check_output(["git", "branch", "-r"])
+
+ txt_list = txt_list.split(b"\n")
+
+ # we will store list of suitable branches here
+ out = []
+ for branch in txt_list:
+ # skip empty lines
+ if len(branch) == 0:
+ continue
+
+ # skip branches that are aliases (something -> something_else)
+ if branch.find(b"->") != -1:
+ continue
+
+ # don't complain about master
+ if branch == b"origin/master":
+ continue
+
+ branch_info = Branch()
+
+ # get branch name
+ branch_info.name = branch.strip(b" ")
+ branch_info.name = branch_info.name.decode("utf-8")
+
+ # check if branch is merged or not
+ if verbose:
+ print("Checking branch %s" % branch_info.name)
+
+ # get a diff with changes that are on that branch only
+ # i.e. all unmerged code.
+ cmd = ["git", "diff", "master..." + branch_info.name ]
+ diff = subprocess.check_output(cmd)
+ if len(diff) == 0:
+ # No diff? Then all changes from that branch are on master as well.
+ branch_info.status = Branch.MERGED
+
+ # let's get the last contributor with extra formatting
+ # see man git-log and search for PRETTY FORMATS.
+ # %ai = date, %ae = author e-mail, %an = author name
+ cmd = [ "git" , "log", "-n", "1", "--pretty=\"%ai,%ae,%an\"",
+ branch_info.name ]
+ offender = subprocess.check_output(cmd)
+ offender = offender.strip(b"\n\"")
+
+ # comment out this 2 lines to disable obfuscation
+ offender = offender.replace(b"@", b"(at)")
+ # Obfuscating a dot does not work too well for folks that use
+ # initials
+ #offender = offender.replace(b".", b"(dot)")
+
+ branch_info.last_commit = offender.decode("utf-8")
+
+ else:
+ # diff is not empty, so there is something to merge
+ branch_info.status = Branch.NOTMERGED
+
+ out.append(branch_info)
+ return out
+
+def branch_print(branches, csv, print_merged, print_notmerged, print_stats):
+ """ prints out list of branches with specified details (using
+ human-readable (or CSV) format. It is possible to specify,
+ which branches should be printed (merged, notmerged) and
+ also print out summary statistics """
+
+ # counters used for statistics
+ merged = 0
+ notmerged = 0
+
+ # compact list of merged/notmerged branches
+ merged_str = ""
+ notmerged_str = ""
+ for branch in branches:
+ if branch.status == Branch.MERGED:
+ merged = merged + 1
+ if not print_merged:
+ continue
+ if csv:
+ print("%s,merged,%s" % (branch.name, branch.last_commit) )
+ else:
+ merged_str = merged_str + " " + branch.name
+ else:
+ # NOT MERGED
+ notmerged = notmerged + 1
+ if not print_notmerged:
+ continue
+ if csv:
+ print("%s,notmerged,%s" % (branch.name, branch.last_commit) )
+ else:
+ notmerged_str = notmerged_str + " " + branch.name
+
+ if not csv:
+ if print_merged:
+ print("Merged branches : %s" % (merged_str))
+ if print_notmerged:
+ print("NOT merged branches: %s" % (notmerged_str))
+
+ if print_stats:
+ print("#----------")
+ print("#Merged : %d" % merged)
+ print("#Not merged: %d" % notmerged)
+
+
+def parse_args(args=sys.argv[1:], Parser=OptionParser):
+
+ parser = Parser(description="This script prints out merged and/or unmerged"
+ " branches of a GIT tree.")
+
+ parser.add_option("-c", "--csv", action="store_true",
+ default=False, help="generates CSV output")
+ parser.add_option("-u", "--unmerged", action="store_true",
+ default=False, help="lists unmerged branches")
+ parser.add_option("-m", "--skip-merged", action="store_true",
+ default=False, help="omits listing merged branches")
+ parser.add_option("-s", "--stats", action="store_true",
+ default=False, help="prints also statistics")
+
+ (options, args) = parser.parse_args(args)
+
+ if args:
+ parser.print_help()
+ sys.exit(1)
+
+ return options
+
+def main():
+ usage = """%prog
+ Lists all obsolete (fully merged into master) branches.
+ """
+
+ options = parse_args()
+ csv = options.csv
+ merged = not options.skip_merged
+ unmerged = options.unmerged
+ stats = options.stats
+
+ if csv:
+ print("branch name,status,date,last commit(mail),last commit(name)")
+
+ branch_list = branch_list_get(not csv)
+
+ branch_print(branch_list, csv, merged, unmerged, stats)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/reorder_message_file.py b/tools/reorder_message_file.py
new file mode 100644
index 0000000..2ba4d7c
--- /dev/null
+++ b/tools/reorder_message_file.py
@@ -0,0 +1,196 @@
+# 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.
+
+# Reorder Message File
+#
+# Reads a message file into memory, then outputs it with the messages and
+# associated descriptions in alphabetical order.
+#
+# Invocation:
+# The code is invoked using the command line:
+#
+# python reorder.py message_file
+#
+# Output is written to stdout.
+
+import sys
+
+def remove_empty_leading_trailing(lines):
+ """
+ Removes leading and trailing empty lines.
+
+ A list of strings is passed as argument, some of which may be empty.
+ This function removes from the start and end of the list a contiguous
+ sequence of empty lines and returns the result. Embedded sequences of
+ empty lines are not touched.
+
+ Parameters:
+ lines List of strings to be modified.
+
+ Return:
+ Input list of strings with leading/trailing blank line sequences
+ removed.
+ """
+
+ retlines = []
+
+ # Dispose of degenerate case of empty array
+ if len(lines) == 0:
+ return retlines
+
+ # Search for first non-blank line
+ start = 0
+ while start < len(lines):
+ if len(lines[start]) > 0:
+ break
+ start = start + 1
+
+ # Handle case when entire list is empty
+ if start >= len(lines):
+ return retlines
+
+ # Search for last non-blank line
+ finish = len(lines) - 1
+ while finish >= 0:
+ if len(lines[finish]) > 0:
+ break
+ finish = finish - 1
+
+ retlines = lines[start:finish + 1]
+ return retlines
+
+
+def canonicalise_message_line(line):
+ """
+ Given a line known to start with the '%' character (i.e. a line
+ introducing a message), canonicalise it by ensuring that the result
+ is of the form '%<single-space>MESSAGE_IDENTIFIER<single-space>text'.
+
+ Parameters:
+ line - input line. Known to start with a '%' and to have leading
+ and trailing spaces removed.
+
+ Return:
+ Canonicalised line.
+ """
+ # Cope with degenerate case of a single "%"
+ if len(line) == 1:
+ return line
+
+ # Get the rest of the line
+ line = line[1:].lstrip()
+
+ # Extract the first word (the message ID)
+ words = line.split()
+ message_line = "% " + words[0]
+
+ # ... and now the rest of the line
+ if len(line) > len(words[0]):
+ message_line = message_line + " " + line[len(words[0]):].lstrip()
+
+ return message_line
+
+
+def make_dict(lines):
+ """
+ Split the lines into segments starting with the message definition and
+ place into a dictionary.
+
+ Parameters:
+ lines - list of lines containing the text of the message file (less the
+ header).
+
+ Returns:
+ dictionary - map of the messages, keyed by the line that holds the message
+ ID.
+ """
+
+ dictionary = {}
+
+ message_key = canonicalise_message_line(lines[0])
+ message_lines = [message_key]
+ index = 1;
+ while index < len(lines):
+ if lines[index].startswith("%"):
+ # Start of new message
+ dictionary[message_key] = remove_empty_leading_trailing(message_lines)
+ message_key = canonicalise_message_line(lines[index])
+ message_lines = [message_key]
+ else:
+ message_lines.append(lines[index])
+
+ index = index + 1
+
+ dictionary[message_key] = remove_empty_leading_trailing(message_lines)
+
+ return dictionary
+
+
+def print_dict(dictionary):
+ """
+ Prints the dictionary with a blank line between entries.
+
+ Parameters:
+ dicitionary - Map holding the message dictionary
+ """
+ count = 0
+ for msgid in sorted(dictionary):
+
+ # Blank line before all entries but the first
+ if count > 0:
+ print("")
+ count = count + 1
+
+ # ... and the entry itself.
+ for l in dictionary[msgid]:
+ print(l.strip())
+
+
+def process_file(filename):
+ """
+ Processes a file by reading it and searching for the first line starting
+ with the '%' sign. Everything before that line is treated as the file
+ header and is copied to the output with leading and trailing spaces removed.
+ After that, each message block is read and stored for later sorting.
+
+ Parameters:
+ filename Name of the message file to process
+ """
+ lines = open(filename).read().splitlines()
+
+ # Search for the first line starting with the percent character. Everything
+ # before it is considered the file header and is copied to the output with
+ # leading and trailing spaces removed.
+ index = 0
+ while index < len(lines):
+ if lines[index].startswith("%"):
+ break
+ print(lines[index].strip())
+ index = index + 1
+
+ # Now put the remaining lines into the message dictionary
+ dictionary = make_dict(lines[index:])
+
+ # ...and print it
+ print_dict(dictionary)
+
+
+# Main program
+if __name__ == "__main__":
+
+ # Read the files and load the data
+ if len(sys.argv) != 2:
+ print("Usage: python reorder.py message_file")
+ else:
+ process_file(sys.argv[1])
diff --git a/tools/system_messages.py b/tools/system_messages.py
index 7b0d60c..fd4c186 100644
--- a/tools/system_messages.py
+++ b/tools/system_messages.py
@@ -71,7 +71,7 @@ SEC_HEADER="""<?xml version="1.0" encoding="UTF-8"?>
<title>BIND 10 Messages Manual</title>
<copyright>
- <year>2011</year><holder>Internet Systems Consortium, Inc.</holder>
+ <year>2011-2012</year><holder>Internet Systems Consortium, Inc.</holder>
</copyright>
<abstract>
More information about the bind10-changes
mailing list