summaryrefslogtreecommitdiffstats
path: root/src/bin/dhcp6/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/dhcp6/tests
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/bin/dhcp6/tests/Makefile.am185
-rw-r--r--src/bin/dhcp6/tests/Makefile.in1781
-rw-r--r--src/bin/dhcp6/tests/callout_library_1.cc34
-rw-r--r--src/bin/dhcp6/tests/callout_library_2.cc24
-rw-r--r--src/bin/dhcp6/tests/callout_library_3.cc93
-rw-r--r--src/bin/dhcp6/tests/callout_library_common.h110
-rw-r--r--src/bin/dhcp6/tests/classify_unittests.cc2589
-rw-r--r--src/bin/dhcp6/tests/client_handler_unittest.cc495
-rw-r--r--src/bin/dhcp6/tests/config_backend_unittest.cc547
-rw-r--r--src/bin/dhcp6/tests/config_parser_unittest.cc8416
-rw-r--r--src/bin/dhcp6/tests/confirm_unittest.cc348
-rw-r--r--src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc2261
-rw-r--r--src/bin/dhcp6/tests/d2_unittest.cc415
-rw-r--r--src/bin/dhcp6/tests/d2_unittest.h114
-rw-r--r--src/bin/dhcp6/tests/decline_unittest.cc332
-rw-r--r--src/bin/dhcp6/tests/dhcp6_client.cc1053
-rw-r--r--src/bin/dhcp6/tests/dhcp6_client.h973
-rw-r--r--src/bin/dhcp6/tests/dhcp6_message_test.cc83
-rw-r--r--src/bin/dhcp6/tests/dhcp6_message_test.h85
-rw-r--r--src/bin/dhcp6/tests/dhcp6_process_tests.sh.in611
-rw-r--r--src/bin/dhcp6/tests/dhcp6_srv_unittest.cc3904
-rw-r--r--src/bin/dhcp6/tests/dhcp6_test_utils.cc1194
-rw-r--r--src/bin/dhcp6/tests/dhcp6_test_utils.h1010
-rw-r--r--src/bin/dhcp6/tests/dhcp6_unittests.cc23
-rw-r--r--src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc318
-rw-r--r--src/bin/dhcp6/tests/fqdn_unittest.cc2177
-rw-r--r--src/bin/dhcp6/tests/get_config_unittest.cc11137
-rw-r--r--src/bin/dhcp6/tests/get_config_unittest.cc.skel377
-rw-r--r--src/bin/dhcp6/tests/get_config_unittest.h27
-rw-r--r--src/bin/dhcp6/tests/hooks_unittest.cc5817
-rw-r--r--src/bin/dhcp6/tests/host_unittest.cc2653
-rw-r--r--src/bin/dhcp6/tests/infrequest_unittest.cc363
-rw-r--r--src/bin/dhcp6/tests/kea_controller_unittest.cc1087
-rw-r--r--src/bin/dhcp6/tests/marker_file.cc60
-rw-r--r--src/bin/dhcp6/tests/marker_file.h.in62
-rw-r--r--src/bin/dhcp6/tests/parser_unittest.cc980
-rw-r--r--src/bin/dhcp6/tests/rebind_unittest.cc1169
-rw-r--r--src/bin/dhcp6/tests/renew_unittest.cc743
-rw-r--r--src/bin/dhcp6/tests/sarr_unittest.cc1335
-rw-r--r--src/bin/dhcp6/tests/shared_network_unittest.cc3052
-rw-r--r--src/bin/dhcp6/tests/simple_parser6_unittest.cc261
-rw-r--r--src/bin/dhcp6/tests/tee_times_unittest.cc245
-rw-r--r--src/bin/dhcp6/tests/test_data_files_config.h.in15
-rw-r--r--src/bin/dhcp6/tests/test_libraries.h.in30
-rw-r--r--src/bin/dhcp6/tests/vendor_opts_unittest.cc1921
45 files changed, 60509 insertions, 0 deletions
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
new file mode 100644
index 0000000..7955bb6
--- /dev/null
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -0,0 +1,185 @@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = get_config_unittest.cc.skel
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Shell tests
+SHTESTS = dhcp6_process_tests.sh
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS)
+DISTCLEANFILES += marker_file.h
+DISTCLEANFILES += test_data_files_config.h
+DISTCLEANFILES += test_libraries.h
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += -I$(top_srcdir)/src -I$(top_builddir)/src
+AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/kea6\"
+AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../dhcp6_parser.yy\"
+AM_CPPFLAGS += -DKEA_LFC_EXECUTABLE=\"$(abs_top_builddir)/src/bin/lfc/kea-lfc\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+# Build shared libraries for testing. The libtool way to create a shared library
+# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS
+# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html).
+# Use of these switches will guarantee that the .so files are created in the
+# .libs folder and they can be dlopened.
+# Note that the shared libraries with callouts should not be used together with
+# the --enable-static-link option. With this option, the bind10 libraries are
+# statically linked with the program and if the callout invokes the methods
+# which belong to these libraries, the library with the callout will get its
+# own copy of the static objects (e.g. logger, ServerHooks) and that will lead
+# to unexpected errors. For this reason, the --enable-static-link option is
+# ignored for unit tests built here.
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+
+libco1_la_SOURCES = callout_library_1.cc callout_library_common.h
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco1_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+libco2_la_SOURCES = callout_library_2.cc callout_library_common.h
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+libco3_la_SOURCES = callout_library_3.cc callout_library_common.h
+libco3_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco3_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco3_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# Don't install test libraries.
+noinst_LTLIBRARIES = libco1.la libco2.la libco3.la
+
+if HAVE_GTEST
+
+# C++ tests
+PROGRAM_TESTS = dhcp6_unittests
+
+# This list is ordered alphabetically. When adding new files, please maintain
+# this order.
+dhcp6_unittests_SOURCES = classify_unittests.cc
+dhcp6_unittests_SOURCES += client_handler_unittest.cc
+dhcp6_unittests_SOURCES += config_parser_unittest.cc
+dhcp6_unittests_SOURCES += config_backend_unittest.cc
+dhcp6_unittests_SOURCES += confirm_unittest.cc
+dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h
+dhcp6_unittests_SOURCES += decline_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
+dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h
+dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
+dhcp6_unittests_SOURCES += dhcp6_unittests.cc
+dhcp6_unittests_SOURCES += dhcp6to4_ipc_unittest.cc
+dhcp6_unittests_SOURCES += fqdn_unittest.cc
+dhcp6_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
+dhcp6_unittests_SOURCES += hooks_unittest.cc
+dhcp6_unittests_SOURCES += host_unittest.cc
+dhcp6_unittests_SOURCES += infrequest_unittest.cc
+dhcp6_unittests_SOURCES += kea_controller_unittest.cc
+dhcp6_unittests_SOURCES += marker_file.cc
+dhcp6_unittests_SOURCES += parser_unittest.cc
+dhcp6_unittests_SOURCES += rebind_unittest.cc
+dhcp6_unittests_SOURCES += renew_unittest.cc
+dhcp6_unittests_SOURCES += sarr_unittest.cc
+dhcp6_unittests_SOURCES += simple_parser6_unittest.cc
+dhcp6_unittests_SOURCES += shared_network_unittest.cc
+dhcp6_unittests_SOURCES += tee_times_unittest.cc
+dhcp6_unittests_SOURCES += vendor_opts_unittest.cc
+
+nodist_dhcp6_unittests_SOURCES = marker_file.h test_libraries.h
+
+dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+if HAVE_MYSQL
+dhcp6_unittests_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+dhcp6_unittests_LDFLAGS += $(PGSQL_LIBS)
+endif
+dhcp6_unittests_LDFLAGS += $(GTEST_LDFLAGS)
+
+dhcp6_unittests_LDADD = $(top_builddir)/src/bin/dhcp6/libdhcp6.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+
+if HAVE_PGSQL
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+endif
+
+if HAVE_MYSQL
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+endif
+
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+dhcp6_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+dhcp6_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+
+# Run C++ tests on "make check".
+TESTS = $(PROGRAM_TESTS)
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS += $(SHTESTS)
+
+# Don't install C++ tests.
+noinst_PROGRAMS = $(PROGRAM_TESTS)
+
+# Use this target if you want to rebuild the get-config unit-tests.
+#
+# TODO: We could also automate the replacement step with some variation
+# of this: https://stackoverflow.com/questions/6790631
+rebuild-tests:
+ rm -f x u get_config_unittest.cc
+ cp -f get_config_unittest.cc.skel get_config_unittest.cc
+ $(MAKE) CXXFLAGS=-DEXTRACT_CONFIG V=1
+ ./dhcp6_unittests --gtest_filter="Dhcp6Parser*" > /dev/null 2> x
+ echo "Please copy content of x file into EXTRACTED_CONFIGS in get_config_unittest.cc"
+ read -p "Press ENTER when ready"
+ $(MAKE) CXXFLAGS=-DGENERATE_ACTION V=1
+ ./dhcp6_unittests --gtest_filter="Dhcp6GetConfig*" > /dev/null 2> u
+ echo "Please copy content of u file into UNPARSED_CONFIGS in get_config_unittest.cc"
+ read -p "Press ENTER when ready"
+ touch get_config_unittest.cc
+ $(MAKE)
+
+endif
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
diff --git a/src/bin/dhcp6/tests/Makefile.in b/src/bin/dhcp6/tests/Makefile.in
new file mode 100644
index 0000000..cd1a6bf
--- /dev/null
+++ b/src/bin/dhcp6/tests/Makefile.in
@@ -0,0 +1,1781 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_1 = $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_2 = $(PGSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_3 = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_4 = $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la \
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@ $(top_builddir)/src/lib/mysql/libkea-mysql.la
+@HAVE_GTEST_TRUE@TESTS = $(am__EXEEXT_1) $(SHTESTS)
+@HAVE_GTEST_TRUE@noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/bin/dhcp6/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_cpp20.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_netconf.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = dhcp6_process_tests.sh marker_file.h \
+ test_data_files_config.h test_libraries.h
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = dhcp6_unittests$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libco1_la_LIBADD =
+am_libco1_la_OBJECTS = libco1_la-callout_library_1.lo
+libco1_la_OBJECTS = $(am_libco1_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libco1_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libco1_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libco1_la_LDFLAGS) $(LDFLAGS) -o $@
+libco2_la_LIBADD =
+am_libco2_la_OBJECTS = libco2_la-callout_library_2.lo
+libco2_la_OBJECTS = $(am_libco2_la_OBJECTS)
+libco2_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libco2_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libco2_la_LDFLAGS) $(LDFLAGS) -o $@
+libco3_la_LIBADD =
+am_libco3_la_OBJECTS = libco3_la-callout_library_3.lo
+libco3_la_OBJECTS = $(am_libco3_la_OBJECTS)
+libco3_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libco3_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libco3_la_LDFLAGS) $(LDFLAGS) -o $@
+am__dhcp6_unittests_SOURCES_DIST = classify_unittests.cc \
+ client_handler_unittest.cc config_parser_unittest.cc \
+ config_backend_unittest.cc confirm_unittest.cc \
+ ctrl_dhcp6_srv_unittest.cc d2_unittest.cc d2_unittest.h \
+ decline_unittest.cc dhcp6_client.cc dhcp6_client.h \
+ dhcp6_message_test.cc dhcp6_message_test.h \
+ dhcp6_srv_unittest.cc dhcp6_test_utils.cc dhcp6_test_utils.h \
+ dhcp6_unittests.cc dhcp6to4_ipc_unittest.cc fqdn_unittest.cc \
+ get_config_unittest.cc get_config_unittest.h hooks_unittest.cc \
+ host_unittest.cc infrequest_unittest.cc \
+ kea_controller_unittest.cc marker_file.cc parser_unittest.cc \
+ rebind_unittest.cc renew_unittest.cc sarr_unittest.cc \
+ simple_parser6_unittest.cc shared_network_unittest.cc \
+ tee_times_unittest.cc vendor_opts_unittest.cc
+@HAVE_GTEST_TRUE@am_dhcp6_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-classify_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-client_handler_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-config_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-config_backend_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-confirm_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-ctrl_dhcp6_srv_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-d2_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-decline_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-dhcp6_client.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-dhcp6_message_test.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-dhcp6_srv_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-dhcp6_test_utils.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-dhcp6_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-dhcp6to4_ipc_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-fqdn_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-get_config_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-hooks_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-host_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-infrequest_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-kea_controller_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-marker_file.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-rebind_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-renew_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-sarr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-simple_parser6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-shared_network_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-tee_times_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ dhcp6_unittests-vendor_opts_unittest.$(OBJEXT)
+nodist_dhcp6_unittests_OBJECTS =
+dhcp6_unittests_OBJECTS = $(am_dhcp6_unittests_OBJECTS) \
+ $(nodist_dhcp6_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@dhcp6_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/dhcp6/libdhcp6.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(am__append_3) $(am__append_4) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/testutils/libdatabasetest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+dhcp6_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(dhcp6_unittests_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SCRIPTS = $(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/dhcp6_unittests-classify_unittests.Po \
+ ./$(DEPDIR)/dhcp6_unittests-client_handler_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-config_backend_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-config_parser_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-confirm_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-d2_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-decline_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-dhcp6_client.Po \
+ ./$(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Po \
+ ./$(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Po \
+ ./$(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Po \
+ ./$(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-fqdn_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-get_config_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-hooks_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-host_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-infrequest_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-marker_file.Po \
+ ./$(DEPDIR)/dhcp6_unittests-parser_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-rebind_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-renew_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-sarr_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-shared_network_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-tee_times_unittest.Po \
+ ./$(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Po \
+ ./$(DEPDIR)/libco1_la-callout_library_1.Plo \
+ ./$(DEPDIR)/libco2_la-callout_library_2.Plo \
+ ./$(DEPDIR)/libco3_la-callout_library_3.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libco1_la_SOURCES) $(libco2_la_SOURCES) \
+ $(libco3_la_SOURCES) $(dhcp6_unittests_SOURCES) \
+ $(nodist_dhcp6_unittests_SOURCES)
+DIST_SOURCES = $(libco1_la_SOURCES) $(libco2_la_SOURCES) \
+ $(libco3_la_SOURCES) $(am__dhcp6_unittests_SOURCES_DIST)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/dhcp6_process_tests.sh.in $(srcdir)/marker_file.h.in \
+ $(srcdir)/test_data_files_config.h.in \
+ $(srcdir)/test_libraries.h.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@
+DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@
+DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_NETCONF = @HAVE_NETCONF@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@
+LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@
+LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@
+LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@
+LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@
+LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@
+LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@
+LIBYANG_LIBS = @LIBYANG_LIBS@
+LIBYANG_PREFIX = @LIBYANG_PREFIX@
+LIBYANG_VERSION = @LIBYANG_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_PLUGINS_PATH = @SR_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@
+SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@
+SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@
+SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_PREFIX = @SYSREPO_PREFIX@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = get_config_unittest.cc.skel
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Shell tests
+SHTESTS = dhcp6_process_tests.sh
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS) marker_file.h test_data_files_config.h \
+ test_libraries.h
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin \
+ -I$(top_srcdir)/src -I$(top_builddir)/src \
+ -DTOP_BUILDDIR="\"$(top_builddir)\"" $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" \
+ -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/kea6\" \
+ -DSYNTAX_FILE=\"$(abs_srcdir)/../dhcp6_parser.yy\" \
+ -DKEA_LFC_EXECUTABLE=\"$(abs_top_builddir)/src/bin/lfc/kea-lfc\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+
+# Build shared libraries for testing. The libtool way to create a shared library
+# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS
+# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html).
+# Use of these switches will guarantee that the .so files are created in the
+# .libs folder and they can be dlopened.
+# Note that the shared libraries with callouts should not be used together with
+# the --enable-static-link option. With this option, the bind10 libraries are
+# statically linked with the program and if the callout invokes the methods
+# which belong to these libraries, the library with the callout will get its
+# own copy of the static objects (e.g. logger, ServerHooks) and that will lead
+# to unexpected errors. For this reason, the --enable-static-link option is
+# ignored for unit tests built here.
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+libco1_la_SOURCES = callout_library_1.cc callout_library_common.h
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco1_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+libco2_la_SOURCES = callout_library_2.cc callout_library_common.h
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+libco3_la_SOURCES = callout_library_3.cc callout_library_common.h
+libco3_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco3_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco3_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# Don't install test libraries.
+noinst_LTLIBRARIES = libco1.la libco2.la libco3.la
+
+# C++ tests
+@HAVE_GTEST_TRUE@PROGRAM_TESTS = dhcp6_unittests
+
+# This list is ordered alphabetically. When adding new files, please maintain
+# this order.
+@HAVE_GTEST_TRUE@dhcp6_unittests_SOURCES = classify_unittests.cc \
+@HAVE_GTEST_TRUE@ client_handler_unittest.cc \
+@HAVE_GTEST_TRUE@ config_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ config_backend_unittest.cc \
+@HAVE_GTEST_TRUE@ confirm_unittest.cc \
+@HAVE_GTEST_TRUE@ ctrl_dhcp6_srv_unittest.cc d2_unittest.cc \
+@HAVE_GTEST_TRUE@ d2_unittest.h decline_unittest.cc \
+@HAVE_GTEST_TRUE@ dhcp6_client.cc dhcp6_client.h \
+@HAVE_GTEST_TRUE@ dhcp6_message_test.cc dhcp6_message_test.h \
+@HAVE_GTEST_TRUE@ dhcp6_srv_unittest.cc dhcp6_test_utils.cc \
+@HAVE_GTEST_TRUE@ dhcp6_test_utils.h dhcp6_unittests.cc \
+@HAVE_GTEST_TRUE@ dhcp6to4_ipc_unittest.cc fqdn_unittest.cc \
+@HAVE_GTEST_TRUE@ get_config_unittest.cc get_config_unittest.h \
+@HAVE_GTEST_TRUE@ hooks_unittest.cc host_unittest.cc \
+@HAVE_GTEST_TRUE@ infrequest_unittest.cc \
+@HAVE_GTEST_TRUE@ kea_controller_unittest.cc marker_file.cc \
+@HAVE_GTEST_TRUE@ parser_unittest.cc rebind_unittest.cc \
+@HAVE_GTEST_TRUE@ renew_unittest.cc sarr_unittest.cc \
+@HAVE_GTEST_TRUE@ simple_parser6_unittest.cc \
+@HAVE_GTEST_TRUE@ shared_network_unittest.cc \
+@HAVE_GTEST_TRUE@ tee_times_unittest.cc vendor_opts_unittest.cc
+@HAVE_GTEST_TRUE@nodist_dhcp6_unittests_SOURCES = marker_file.h test_libraries.h
+@HAVE_GTEST_TRUE@dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LDFLAGS) $(am__append_1) \
+@HAVE_GTEST_TRUE@ $(am__append_2) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@dhcp6_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/dhcp6/libdhcp6.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(am__append_3) $(am__append_4) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/testutils/libdatabasetest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(GTEST_LDADD)
+
+# Run shell tests on "make check".
+@HAVE_GTEST_TRUE@check_SCRIPTS = $(SHTESTS)
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/bin/dhcp6/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/dhcp6/tests/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+dhcp6_process_tests.sh: $(top_builddir)/config.status $(srcdir)/dhcp6_process_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+marker_file.h: $(top_builddir)/config.status $(srcdir)/marker_file.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_data_files_config.h: $(top_builddir)/config.status $(srcdir)/test_data_files_config.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_libraries.h: $(top_builddir)/config.status $(srcdir)/test_libraries.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+clean-noinstPROGRAMS:
+ @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libco1.la: $(libco1_la_OBJECTS) $(libco1_la_DEPENDENCIES) $(EXTRA_libco1_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libco1_la_LINK) $(libco1_la_OBJECTS) $(libco1_la_LIBADD) $(LIBS)
+
+libco2.la: $(libco2_la_OBJECTS) $(libco2_la_DEPENDENCIES) $(EXTRA_libco2_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libco2_la_LINK) $(libco2_la_OBJECTS) $(libco2_la_LIBADD) $(LIBS)
+
+libco3.la: $(libco3_la_OBJECTS) $(libco3_la_DEPENDENCIES) $(EXTRA_libco3_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libco3_la_LINK) $(libco3_la_OBJECTS) $(libco3_la_LIBADD) $(LIBS)
+
+dhcp6_unittests$(EXEEXT): $(dhcp6_unittests_OBJECTS) $(dhcp6_unittests_DEPENDENCIES) $(EXTRA_dhcp6_unittests_DEPENDENCIES)
+ @rm -f dhcp6_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(dhcp6_unittests_LINK) $(dhcp6_unittests_OBJECTS) $(dhcp6_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-classify_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-client_handler_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-config_backend_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-config_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-confirm_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-d2_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-decline_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-dhcp6_client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-fqdn_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-get_config_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-hooks_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-host_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-infrequest_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-marker_file.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-rebind_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-renew_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-sarr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-shared_network_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-tee_times_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libco1_la-callout_library_1.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libco2_la-callout_library_2.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libco3_la-callout_library_3.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+libco1_la-callout_library_1.lo: callout_library_1.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco1_la_CPPFLAGS) $(CPPFLAGS) $(libco1_la_CXXFLAGS) $(CXXFLAGS) -MT libco1_la-callout_library_1.lo -MD -MP -MF $(DEPDIR)/libco1_la-callout_library_1.Tpo -c -o libco1_la-callout_library_1.lo `test -f 'callout_library_1.cc' || echo '$(srcdir)/'`callout_library_1.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libco1_la-callout_library_1.Tpo $(DEPDIR)/libco1_la-callout_library_1.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_library_1.cc' object='libco1_la-callout_library_1.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco1_la_CPPFLAGS) $(CPPFLAGS) $(libco1_la_CXXFLAGS) $(CXXFLAGS) -c -o libco1_la-callout_library_1.lo `test -f 'callout_library_1.cc' || echo '$(srcdir)/'`callout_library_1.cc
+
+libco2_la-callout_library_2.lo: callout_library_2.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco2_la_CPPFLAGS) $(CPPFLAGS) $(libco2_la_CXXFLAGS) $(CXXFLAGS) -MT libco2_la-callout_library_2.lo -MD -MP -MF $(DEPDIR)/libco2_la-callout_library_2.Tpo -c -o libco2_la-callout_library_2.lo `test -f 'callout_library_2.cc' || echo '$(srcdir)/'`callout_library_2.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libco2_la-callout_library_2.Tpo $(DEPDIR)/libco2_la-callout_library_2.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_library_2.cc' object='libco2_la-callout_library_2.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco2_la_CPPFLAGS) $(CPPFLAGS) $(libco2_la_CXXFLAGS) $(CXXFLAGS) -c -o libco2_la-callout_library_2.lo `test -f 'callout_library_2.cc' || echo '$(srcdir)/'`callout_library_2.cc
+
+libco3_la-callout_library_3.lo: callout_library_3.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco3_la_CPPFLAGS) $(CPPFLAGS) $(libco3_la_CXXFLAGS) $(CXXFLAGS) -MT libco3_la-callout_library_3.lo -MD -MP -MF $(DEPDIR)/libco3_la-callout_library_3.Tpo -c -o libco3_la-callout_library_3.lo `test -f 'callout_library_3.cc' || echo '$(srcdir)/'`callout_library_3.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libco3_la-callout_library_3.Tpo $(DEPDIR)/libco3_la-callout_library_3.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_library_3.cc' object='libco3_la-callout_library_3.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco3_la_CPPFLAGS) $(CPPFLAGS) $(libco3_la_CXXFLAGS) $(CXXFLAGS) -c -o libco3_la-callout_library_3.lo `test -f 'callout_library_3.cc' || echo '$(srcdir)/'`callout_library_3.cc
+
+dhcp6_unittests-classify_unittests.o: classify_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-classify_unittests.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-classify_unittests.Tpo -c -o dhcp6_unittests-classify_unittests.o `test -f 'classify_unittests.cc' || echo '$(srcdir)/'`classify_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-classify_unittests.Tpo $(DEPDIR)/dhcp6_unittests-classify_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittests.cc' object='dhcp6_unittests-classify_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-classify_unittests.o `test -f 'classify_unittests.cc' || echo '$(srcdir)/'`classify_unittests.cc
+
+dhcp6_unittests-classify_unittests.obj: classify_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-classify_unittests.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-classify_unittests.Tpo -c -o dhcp6_unittests-classify_unittests.obj `if test -f 'classify_unittests.cc'; then $(CYGPATH_W) 'classify_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-classify_unittests.Tpo $(DEPDIR)/dhcp6_unittests-classify_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittests.cc' object='dhcp6_unittests-classify_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-classify_unittests.obj `if test -f 'classify_unittests.cc'; then $(CYGPATH_W) 'classify_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittests.cc'; fi`
+
+dhcp6_unittests-client_handler_unittest.o: client_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-client_handler_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-client_handler_unittest.Tpo -c -o dhcp6_unittests-client_handler_unittest.o `test -f 'client_handler_unittest.cc' || echo '$(srcdir)/'`client_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-client_handler_unittest.Tpo $(DEPDIR)/dhcp6_unittests-client_handler_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_handler_unittest.cc' object='dhcp6_unittests-client_handler_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-client_handler_unittest.o `test -f 'client_handler_unittest.cc' || echo '$(srcdir)/'`client_handler_unittest.cc
+
+dhcp6_unittests-client_handler_unittest.obj: client_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-client_handler_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-client_handler_unittest.Tpo -c -o dhcp6_unittests-client_handler_unittest.obj `if test -f 'client_handler_unittest.cc'; then $(CYGPATH_W) 'client_handler_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/client_handler_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-client_handler_unittest.Tpo $(DEPDIR)/dhcp6_unittests-client_handler_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_handler_unittest.cc' object='dhcp6_unittests-client_handler_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-client_handler_unittest.obj `if test -f 'client_handler_unittest.cc'; then $(CYGPATH_W) 'client_handler_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/client_handler_unittest.cc'; fi`
+
+dhcp6_unittests-config_parser_unittest.o: config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-config_parser_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-config_parser_unittest.Tpo -c -o dhcp6_unittests-config_parser_unittest.o `test -f 'config_parser_unittest.cc' || echo '$(srcdir)/'`config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-config_parser_unittest.Tpo $(DEPDIR)/dhcp6_unittests-config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_parser_unittest.cc' object='dhcp6_unittests-config_parser_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-config_parser_unittest.o `test -f 'config_parser_unittest.cc' || echo '$(srcdir)/'`config_parser_unittest.cc
+
+dhcp6_unittests-config_parser_unittest.obj: config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-config_parser_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-config_parser_unittest.Tpo -c -o dhcp6_unittests-config_parser_unittest.obj `if test -f 'config_parser_unittest.cc'; then $(CYGPATH_W) 'config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/config_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-config_parser_unittest.Tpo $(DEPDIR)/dhcp6_unittests-config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_parser_unittest.cc' object='dhcp6_unittests-config_parser_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-config_parser_unittest.obj `if test -f 'config_parser_unittest.cc'; then $(CYGPATH_W) 'config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/config_parser_unittest.cc'; fi`
+
+dhcp6_unittests-config_backend_unittest.o: config_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-config_backend_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-config_backend_unittest.Tpo -c -o dhcp6_unittests-config_backend_unittest.o `test -f 'config_backend_unittest.cc' || echo '$(srcdir)/'`config_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-config_backend_unittest.Tpo $(DEPDIR)/dhcp6_unittests-config_backend_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_unittest.cc' object='dhcp6_unittests-config_backend_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-config_backend_unittest.o `test -f 'config_backend_unittest.cc' || echo '$(srcdir)/'`config_backend_unittest.cc
+
+dhcp6_unittests-config_backend_unittest.obj: config_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-config_backend_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-config_backend_unittest.Tpo -c -o dhcp6_unittests-config_backend_unittest.obj `if test -f 'config_backend_unittest.cc'; then $(CYGPATH_W) 'config_backend_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/config_backend_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-config_backend_unittest.Tpo $(DEPDIR)/dhcp6_unittests-config_backend_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_unittest.cc' object='dhcp6_unittests-config_backend_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-config_backend_unittest.obj `if test -f 'config_backend_unittest.cc'; then $(CYGPATH_W) 'config_backend_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/config_backend_unittest.cc'; fi`
+
+dhcp6_unittests-confirm_unittest.o: confirm_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-confirm_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-confirm_unittest.Tpo -c -o dhcp6_unittests-confirm_unittest.o `test -f 'confirm_unittest.cc' || echo '$(srcdir)/'`confirm_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-confirm_unittest.Tpo $(DEPDIR)/dhcp6_unittests-confirm_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='confirm_unittest.cc' object='dhcp6_unittests-confirm_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-confirm_unittest.o `test -f 'confirm_unittest.cc' || echo '$(srcdir)/'`confirm_unittest.cc
+
+dhcp6_unittests-confirm_unittest.obj: confirm_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-confirm_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-confirm_unittest.Tpo -c -o dhcp6_unittests-confirm_unittest.obj `if test -f 'confirm_unittest.cc'; then $(CYGPATH_W) 'confirm_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/confirm_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-confirm_unittest.Tpo $(DEPDIR)/dhcp6_unittests-confirm_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='confirm_unittest.cc' object='dhcp6_unittests-confirm_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-confirm_unittest.obj `if test -f 'confirm_unittest.cc'; then $(CYGPATH_W) 'confirm_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/confirm_unittest.cc'; fi`
+
+dhcp6_unittests-ctrl_dhcp6_srv_unittest.o: ctrl_dhcp6_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-ctrl_dhcp6_srv_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Tpo -c -o dhcp6_unittests-ctrl_dhcp6_srv_unittest.o `test -f 'ctrl_dhcp6_srv_unittest.cc' || echo '$(srcdir)/'`ctrl_dhcp6_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Tpo $(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ctrl_dhcp6_srv_unittest.cc' object='dhcp6_unittests-ctrl_dhcp6_srv_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-ctrl_dhcp6_srv_unittest.o `test -f 'ctrl_dhcp6_srv_unittest.cc' || echo '$(srcdir)/'`ctrl_dhcp6_srv_unittest.cc
+
+dhcp6_unittests-ctrl_dhcp6_srv_unittest.obj: ctrl_dhcp6_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-ctrl_dhcp6_srv_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Tpo -c -o dhcp6_unittests-ctrl_dhcp6_srv_unittest.obj `if test -f 'ctrl_dhcp6_srv_unittest.cc'; then $(CYGPATH_W) 'ctrl_dhcp6_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ctrl_dhcp6_srv_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Tpo $(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ctrl_dhcp6_srv_unittest.cc' object='dhcp6_unittests-ctrl_dhcp6_srv_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-ctrl_dhcp6_srv_unittest.obj `if test -f 'ctrl_dhcp6_srv_unittest.cc'; then $(CYGPATH_W) 'ctrl_dhcp6_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ctrl_dhcp6_srv_unittest.cc'; fi`
+
+dhcp6_unittests-d2_unittest.o: d2_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-d2_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-d2_unittest.Tpo -c -o dhcp6_unittests-d2_unittest.o `test -f 'd2_unittest.cc' || echo '$(srcdir)/'`d2_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-d2_unittest.Tpo $(DEPDIR)/dhcp6_unittests-d2_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittest.cc' object='dhcp6_unittests-d2_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-d2_unittest.o `test -f 'd2_unittest.cc' || echo '$(srcdir)/'`d2_unittest.cc
+
+dhcp6_unittests-d2_unittest.obj: d2_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-d2_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-d2_unittest.Tpo -c -o dhcp6_unittests-d2_unittest.obj `if test -f 'd2_unittest.cc'; then $(CYGPATH_W) 'd2_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-d2_unittest.Tpo $(DEPDIR)/dhcp6_unittests-d2_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittest.cc' object='dhcp6_unittests-d2_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-d2_unittest.obj `if test -f 'd2_unittest.cc'; then $(CYGPATH_W) 'd2_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_unittest.cc'; fi`
+
+dhcp6_unittests-decline_unittest.o: decline_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-decline_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-decline_unittest.Tpo -c -o dhcp6_unittests-decline_unittest.o `test -f 'decline_unittest.cc' || echo '$(srcdir)/'`decline_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-decline_unittest.Tpo $(DEPDIR)/dhcp6_unittests-decline_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='decline_unittest.cc' object='dhcp6_unittests-decline_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-decline_unittest.o `test -f 'decline_unittest.cc' || echo '$(srcdir)/'`decline_unittest.cc
+
+dhcp6_unittests-decline_unittest.obj: decline_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-decline_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-decline_unittest.Tpo -c -o dhcp6_unittests-decline_unittest.obj `if test -f 'decline_unittest.cc'; then $(CYGPATH_W) 'decline_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/decline_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-decline_unittest.Tpo $(DEPDIR)/dhcp6_unittests-decline_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='decline_unittest.cc' object='dhcp6_unittests-decline_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-decline_unittest.obj `if test -f 'decline_unittest.cc'; then $(CYGPATH_W) 'decline_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/decline_unittest.cc'; fi`
+
+dhcp6_unittests-dhcp6_client.o: dhcp6_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_client.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_client.Tpo -c -o dhcp6_unittests-dhcp6_client.o `test -f 'dhcp6_client.cc' || echo '$(srcdir)/'`dhcp6_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_client.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_client.cc' object='dhcp6_unittests-dhcp6_client.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_client.o `test -f 'dhcp6_client.cc' || echo '$(srcdir)/'`dhcp6_client.cc
+
+dhcp6_unittests-dhcp6_client.obj: dhcp6_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_client.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_client.Tpo -c -o dhcp6_unittests-dhcp6_client.obj `if test -f 'dhcp6_client.cc'; then $(CYGPATH_W) 'dhcp6_client.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_client.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_client.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_client.cc' object='dhcp6_unittests-dhcp6_client.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_client.obj `if test -f 'dhcp6_client.cc'; then $(CYGPATH_W) 'dhcp6_client.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_client.cc'; fi`
+
+dhcp6_unittests-dhcp6_message_test.o: dhcp6_message_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_message_test.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Tpo -c -o dhcp6_unittests-dhcp6_message_test.o `test -f 'dhcp6_message_test.cc' || echo '$(srcdir)/'`dhcp6_message_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_message_test.cc' object='dhcp6_unittests-dhcp6_message_test.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_message_test.o `test -f 'dhcp6_message_test.cc' || echo '$(srcdir)/'`dhcp6_message_test.cc
+
+dhcp6_unittests-dhcp6_message_test.obj: dhcp6_message_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_message_test.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Tpo -c -o dhcp6_unittests-dhcp6_message_test.obj `if test -f 'dhcp6_message_test.cc'; then $(CYGPATH_W) 'dhcp6_message_test.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_message_test.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_message_test.cc' object='dhcp6_unittests-dhcp6_message_test.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_message_test.obj `if test -f 'dhcp6_message_test.cc'; then $(CYGPATH_W) 'dhcp6_message_test.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_message_test.cc'; fi`
+
+dhcp6_unittests-dhcp6_srv_unittest.o: dhcp6_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_srv_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Tpo -c -o dhcp6_unittests-dhcp6_srv_unittest.o `test -f 'dhcp6_srv_unittest.cc' || echo '$(srcdir)/'`dhcp6_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_srv_unittest.cc' object='dhcp6_unittests-dhcp6_srv_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_srv_unittest.o `test -f 'dhcp6_srv_unittest.cc' || echo '$(srcdir)/'`dhcp6_srv_unittest.cc
+
+dhcp6_unittests-dhcp6_srv_unittest.obj: dhcp6_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_srv_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Tpo -c -o dhcp6_unittests-dhcp6_srv_unittest.obj `if test -f 'dhcp6_srv_unittest.cc'; then $(CYGPATH_W) 'dhcp6_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_srv_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_srv_unittest.cc' object='dhcp6_unittests-dhcp6_srv_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_srv_unittest.obj `if test -f 'dhcp6_srv_unittest.cc'; then $(CYGPATH_W) 'dhcp6_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_srv_unittest.cc'; fi`
+
+dhcp6_unittests-dhcp6_test_utils.o: dhcp6_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_test_utils.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Tpo -c -o dhcp6_unittests-dhcp6_test_utils.o `test -f 'dhcp6_test_utils.cc' || echo '$(srcdir)/'`dhcp6_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_test_utils.cc' object='dhcp6_unittests-dhcp6_test_utils.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_test_utils.o `test -f 'dhcp6_test_utils.cc' || echo '$(srcdir)/'`dhcp6_test_utils.cc
+
+dhcp6_unittests-dhcp6_test_utils.obj: dhcp6_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_test_utils.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Tpo -c -o dhcp6_unittests-dhcp6_test_utils.obj `if test -f 'dhcp6_test_utils.cc'; then $(CYGPATH_W) 'dhcp6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_test_utils.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_test_utils.cc' object='dhcp6_unittests-dhcp6_test_utils.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_test_utils.obj `if test -f 'dhcp6_test_utils.cc'; then $(CYGPATH_W) 'dhcp6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_test_utils.cc'; fi`
+
+dhcp6_unittests-dhcp6_unittests.o: dhcp6_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_unittests.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Tpo -c -o dhcp6_unittests-dhcp6_unittests.o `test -f 'dhcp6_unittests.cc' || echo '$(srcdir)/'`dhcp6_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_unittests.cc' object='dhcp6_unittests-dhcp6_unittests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_unittests.o `test -f 'dhcp6_unittests.cc' || echo '$(srcdir)/'`dhcp6_unittests.cc
+
+dhcp6_unittests-dhcp6_unittests.obj: dhcp6_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6_unittests.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Tpo -c -o dhcp6_unittests-dhcp6_unittests.obj `if test -f 'dhcp6_unittests.cc'; then $(CYGPATH_W) 'dhcp6_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6_unittests.cc' object='dhcp6_unittests-dhcp6_unittests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6_unittests.obj `if test -f 'dhcp6_unittests.cc'; then $(CYGPATH_W) 'dhcp6_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6_unittests.cc'; fi`
+
+dhcp6_unittests-dhcp6to4_ipc_unittest.o: dhcp6to4_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6to4_ipc_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Tpo -c -o dhcp6_unittests-dhcp6to4_ipc_unittest.o `test -f 'dhcp6to4_ipc_unittest.cc' || echo '$(srcdir)/'`dhcp6to4_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6to4_ipc_unittest.cc' object='dhcp6_unittests-dhcp6to4_ipc_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6to4_ipc_unittest.o `test -f 'dhcp6to4_ipc_unittest.cc' || echo '$(srcdir)/'`dhcp6to4_ipc_unittest.cc
+
+dhcp6_unittests-dhcp6to4_ipc_unittest.obj: dhcp6to4_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-dhcp6to4_ipc_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Tpo -c -o dhcp6_unittests-dhcp6to4_ipc_unittest.obj `if test -f 'dhcp6to4_ipc_unittest.cc'; then $(CYGPATH_W) 'dhcp6to4_ipc_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6to4_ipc_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Tpo $(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp6to4_ipc_unittest.cc' object='dhcp6_unittests-dhcp6to4_ipc_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-dhcp6to4_ipc_unittest.obj `if test -f 'dhcp6to4_ipc_unittest.cc'; then $(CYGPATH_W) 'dhcp6to4_ipc_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp6to4_ipc_unittest.cc'; fi`
+
+dhcp6_unittests-fqdn_unittest.o: fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-fqdn_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-fqdn_unittest.Tpo -c -o dhcp6_unittests-fqdn_unittest.o `test -f 'fqdn_unittest.cc' || echo '$(srcdir)/'`fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-fqdn_unittest.Tpo $(DEPDIR)/dhcp6_unittests-fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fqdn_unittest.cc' object='dhcp6_unittests-fqdn_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-fqdn_unittest.o `test -f 'fqdn_unittest.cc' || echo '$(srcdir)/'`fqdn_unittest.cc
+
+dhcp6_unittests-fqdn_unittest.obj: fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-fqdn_unittest.Tpo -c -o dhcp6_unittests-fqdn_unittest.obj `if test -f 'fqdn_unittest.cc'; then $(CYGPATH_W) 'fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/fqdn_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-fqdn_unittest.Tpo $(DEPDIR)/dhcp6_unittests-fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fqdn_unittest.cc' object='dhcp6_unittests-fqdn_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-fqdn_unittest.obj `if test -f 'fqdn_unittest.cc'; then $(CYGPATH_W) 'fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/fqdn_unittest.cc'; fi`
+
+dhcp6_unittests-get_config_unittest.o: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-get_config_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-get_config_unittest.Tpo -c -o dhcp6_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-get_config_unittest.Tpo $(DEPDIR)/dhcp6_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='dhcp6_unittests-get_config_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+
+dhcp6_unittests-get_config_unittest.obj: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-get_config_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-get_config_unittest.Tpo -c -o dhcp6_unittests-get_config_unittest.obj `if test -f 'get_config_unittest.cc'; then $(CYGPATH_W) 'get_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/get_config_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-get_config_unittest.Tpo $(DEPDIR)/dhcp6_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='dhcp6_unittests-get_config_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-get_config_unittest.obj `if test -f 'get_config_unittest.cc'; then $(CYGPATH_W) 'get_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/get_config_unittest.cc'; fi`
+
+dhcp6_unittests-hooks_unittest.o: hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-hooks_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-hooks_unittest.Tpo -c -o dhcp6_unittests-hooks_unittest.o `test -f 'hooks_unittest.cc' || echo '$(srcdir)/'`hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-hooks_unittest.Tpo $(DEPDIR)/dhcp6_unittests-hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_unittest.cc' object='dhcp6_unittests-hooks_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-hooks_unittest.o `test -f 'hooks_unittest.cc' || echo '$(srcdir)/'`hooks_unittest.cc
+
+dhcp6_unittests-hooks_unittest.obj: hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-hooks_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-hooks_unittest.Tpo -c -o dhcp6_unittests-hooks_unittest.obj `if test -f 'hooks_unittest.cc'; then $(CYGPATH_W) 'hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hooks_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-hooks_unittest.Tpo $(DEPDIR)/dhcp6_unittests-hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_unittest.cc' object='dhcp6_unittests-hooks_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-hooks_unittest.obj `if test -f 'hooks_unittest.cc'; then $(CYGPATH_W) 'hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hooks_unittest.cc'; fi`
+
+dhcp6_unittests-host_unittest.o: host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-host_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-host_unittest.Tpo -c -o dhcp6_unittests-host_unittest.o `test -f 'host_unittest.cc' || echo '$(srcdir)/'`host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-host_unittest.Tpo $(DEPDIR)/dhcp6_unittests-host_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_unittest.cc' object='dhcp6_unittests-host_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-host_unittest.o `test -f 'host_unittest.cc' || echo '$(srcdir)/'`host_unittest.cc
+
+dhcp6_unittests-host_unittest.obj: host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-host_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-host_unittest.Tpo -c -o dhcp6_unittests-host_unittest.obj `if test -f 'host_unittest.cc'; then $(CYGPATH_W) 'host_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-host_unittest.Tpo $(DEPDIR)/dhcp6_unittests-host_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_unittest.cc' object='dhcp6_unittests-host_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-host_unittest.obj `if test -f 'host_unittest.cc'; then $(CYGPATH_W) 'host_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_unittest.cc'; fi`
+
+dhcp6_unittests-infrequest_unittest.o: infrequest_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-infrequest_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-infrequest_unittest.Tpo -c -o dhcp6_unittests-infrequest_unittest.o `test -f 'infrequest_unittest.cc' || echo '$(srcdir)/'`infrequest_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-infrequest_unittest.Tpo $(DEPDIR)/dhcp6_unittests-infrequest_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='infrequest_unittest.cc' object='dhcp6_unittests-infrequest_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-infrequest_unittest.o `test -f 'infrequest_unittest.cc' || echo '$(srcdir)/'`infrequest_unittest.cc
+
+dhcp6_unittests-infrequest_unittest.obj: infrequest_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-infrequest_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-infrequest_unittest.Tpo -c -o dhcp6_unittests-infrequest_unittest.obj `if test -f 'infrequest_unittest.cc'; then $(CYGPATH_W) 'infrequest_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/infrequest_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-infrequest_unittest.Tpo $(DEPDIR)/dhcp6_unittests-infrequest_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='infrequest_unittest.cc' object='dhcp6_unittests-infrequest_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-infrequest_unittest.obj `if test -f 'infrequest_unittest.cc'; then $(CYGPATH_W) 'infrequest_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/infrequest_unittest.cc'; fi`
+
+dhcp6_unittests-kea_controller_unittest.o: kea_controller_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-kea_controller_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Tpo -c -o dhcp6_unittests-kea_controller_unittest.o `test -f 'kea_controller_unittest.cc' || echo '$(srcdir)/'`kea_controller_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Tpo $(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kea_controller_unittest.cc' object='dhcp6_unittests-kea_controller_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-kea_controller_unittest.o `test -f 'kea_controller_unittest.cc' || echo '$(srcdir)/'`kea_controller_unittest.cc
+
+dhcp6_unittests-kea_controller_unittest.obj: kea_controller_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-kea_controller_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Tpo -c -o dhcp6_unittests-kea_controller_unittest.obj `if test -f 'kea_controller_unittest.cc'; then $(CYGPATH_W) 'kea_controller_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/kea_controller_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Tpo $(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='kea_controller_unittest.cc' object='dhcp6_unittests-kea_controller_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-kea_controller_unittest.obj `if test -f 'kea_controller_unittest.cc'; then $(CYGPATH_W) 'kea_controller_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/kea_controller_unittest.cc'; fi`
+
+dhcp6_unittests-marker_file.o: marker_file.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-marker_file.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-marker_file.Tpo -c -o dhcp6_unittests-marker_file.o `test -f 'marker_file.cc' || echo '$(srcdir)/'`marker_file.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-marker_file.Tpo $(DEPDIR)/dhcp6_unittests-marker_file.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='marker_file.cc' object='dhcp6_unittests-marker_file.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-marker_file.o `test -f 'marker_file.cc' || echo '$(srcdir)/'`marker_file.cc
+
+dhcp6_unittests-marker_file.obj: marker_file.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-marker_file.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-marker_file.Tpo -c -o dhcp6_unittests-marker_file.obj `if test -f 'marker_file.cc'; then $(CYGPATH_W) 'marker_file.cc'; else $(CYGPATH_W) '$(srcdir)/marker_file.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-marker_file.Tpo $(DEPDIR)/dhcp6_unittests-marker_file.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='marker_file.cc' object='dhcp6_unittests-marker_file.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-marker_file.obj `if test -f 'marker_file.cc'; then $(CYGPATH_W) 'marker_file.cc'; else $(CYGPATH_W) '$(srcdir)/marker_file.cc'; fi`
+
+dhcp6_unittests-parser_unittest.o: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-parser_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-parser_unittest.Tpo -c -o dhcp6_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-parser_unittest.Tpo $(DEPDIR)/dhcp6_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='dhcp6_unittests-parser_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+
+dhcp6_unittests-parser_unittest.obj: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-parser_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-parser_unittest.Tpo -c -o dhcp6_unittests-parser_unittest.obj `if test -f 'parser_unittest.cc'; then $(CYGPATH_W) 'parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-parser_unittest.Tpo $(DEPDIR)/dhcp6_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='dhcp6_unittests-parser_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-parser_unittest.obj `if test -f 'parser_unittest.cc'; then $(CYGPATH_W) 'parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittest.cc'; fi`
+
+dhcp6_unittests-rebind_unittest.o: rebind_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-rebind_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-rebind_unittest.Tpo -c -o dhcp6_unittests-rebind_unittest.o `test -f 'rebind_unittest.cc' || echo '$(srcdir)/'`rebind_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-rebind_unittest.Tpo $(DEPDIR)/dhcp6_unittests-rebind_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rebind_unittest.cc' object='dhcp6_unittests-rebind_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-rebind_unittest.o `test -f 'rebind_unittest.cc' || echo '$(srcdir)/'`rebind_unittest.cc
+
+dhcp6_unittests-rebind_unittest.obj: rebind_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-rebind_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-rebind_unittest.Tpo -c -o dhcp6_unittests-rebind_unittest.obj `if test -f 'rebind_unittest.cc'; then $(CYGPATH_W) 'rebind_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rebind_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-rebind_unittest.Tpo $(DEPDIR)/dhcp6_unittests-rebind_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rebind_unittest.cc' object='dhcp6_unittests-rebind_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-rebind_unittest.obj `if test -f 'rebind_unittest.cc'; then $(CYGPATH_W) 'rebind_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rebind_unittest.cc'; fi`
+
+dhcp6_unittests-renew_unittest.o: renew_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-renew_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-renew_unittest.Tpo -c -o dhcp6_unittests-renew_unittest.o `test -f 'renew_unittest.cc' || echo '$(srcdir)/'`renew_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-renew_unittest.Tpo $(DEPDIR)/dhcp6_unittests-renew_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='renew_unittest.cc' object='dhcp6_unittests-renew_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-renew_unittest.o `test -f 'renew_unittest.cc' || echo '$(srcdir)/'`renew_unittest.cc
+
+dhcp6_unittests-renew_unittest.obj: renew_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-renew_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-renew_unittest.Tpo -c -o dhcp6_unittests-renew_unittest.obj `if test -f 'renew_unittest.cc'; then $(CYGPATH_W) 'renew_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/renew_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-renew_unittest.Tpo $(DEPDIR)/dhcp6_unittests-renew_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='renew_unittest.cc' object='dhcp6_unittests-renew_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-renew_unittest.obj `if test -f 'renew_unittest.cc'; then $(CYGPATH_W) 'renew_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/renew_unittest.cc'; fi`
+
+dhcp6_unittests-sarr_unittest.o: sarr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-sarr_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-sarr_unittest.Tpo -c -o dhcp6_unittests-sarr_unittest.o `test -f 'sarr_unittest.cc' || echo '$(srcdir)/'`sarr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-sarr_unittest.Tpo $(DEPDIR)/dhcp6_unittests-sarr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sarr_unittest.cc' object='dhcp6_unittests-sarr_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-sarr_unittest.o `test -f 'sarr_unittest.cc' || echo '$(srcdir)/'`sarr_unittest.cc
+
+dhcp6_unittests-sarr_unittest.obj: sarr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-sarr_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-sarr_unittest.Tpo -c -o dhcp6_unittests-sarr_unittest.obj `if test -f 'sarr_unittest.cc'; then $(CYGPATH_W) 'sarr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/sarr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-sarr_unittest.Tpo $(DEPDIR)/dhcp6_unittests-sarr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sarr_unittest.cc' object='dhcp6_unittests-sarr_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-sarr_unittest.obj `if test -f 'sarr_unittest.cc'; then $(CYGPATH_W) 'sarr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/sarr_unittest.cc'; fi`
+
+dhcp6_unittests-simple_parser6_unittest.o: simple_parser6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-simple_parser6_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Tpo -c -o dhcp6_unittests-simple_parser6_unittest.o `test -f 'simple_parser6_unittest.cc' || echo '$(srcdir)/'`simple_parser6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Tpo $(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser6_unittest.cc' object='dhcp6_unittests-simple_parser6_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-simple_parser6_unittest.o `test -f 'simple_parser6_unittest.cc' || echo '$(srcdir)/'`simple_parser6_unittest.cc
+
+dhcp6_unittests-simple_parser6_unittest.obj: simple_parser6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-simple_parser6_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Tpo -c -o dhcp6_unittests-simple_parser6_unittest.obj `if test -f 'simple_parser6_unittest.cc'; then $(CYGPATH_W) 'simple_parser6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Tpo $(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser6_unittest.cc' object='dhcp6_unittests-simple_parser6_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-simple_parser6_unittest.obj `if test -f 'simple_parser6_unittest.cc'; then $(CYGPATH_W) 'simple_parser6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser6_unittest.cc'; fi`
+
+dhcp6_unittests-shared_network_unittest.o: shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-shared_network_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-shared_network_unittest.Tpo -c -o dhcp6_unittests-shared_network_unittest.o `test -f 'shared_network_unittest.cc' || echo '$(srcdir)/'`shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-shared_network_unittest.Tpo $(DEPDIR)/dhcp6_unittests-shared_network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_unittest.cc' object='dhcp6_unittests-shared_network_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-shared_network_unittest.o `test -f 'shared_network_unittest.cc' || echo '$(srcdir)/'`shared_network_unittest.cc
+
+dhcp6_unittests-shared_network_unittest.obj: shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-shared_network_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-shared_network_unittest.Tpo -c -o dhcp6_unittests-shared_network_unittest.obj `if test -f 'shared_network_unittest.cc'; then $(CYGPATH_W) 'shared_network_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_network_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-shared_network_unittest.Tpo $(DEPDIR)/dhcp6_unittests-shared_network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_unittest.cc' object='dhcp6_unittests-shared_network_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-shared_network_unittest.obj `if test -f 'shared_network_unittest.cc'; then $(CYGPATH_W) 'shared_network_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_network_unittest.cc'; fi`
+
+dhcp6_unittests-tee_times_unittest.o: tee_times_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-tee_times_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-tee_times_unittest.Tpo -c -o dhcp6_unittests-tee_times_unittest.o `test -f 'tee_times_unittest.cc' || echo '$(srcdir)/'`tee_times_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-tee_times_unittest.Tpo $(DEPDIR)/dhcp6_unittests-tee_times_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tee_times_unittest.cc' object='dhcp6_unittests-tee_times_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-tee_times_unittest.o `test -f 'tee_times_unittest.cc' || echo '$(srcdir)/'`tee_times_unittest.cc
+
+dhcp6_unittests-tee_times_unittest.obj: tee_times_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-tee_times_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-tee_times_unittest.Tpo -c -o dhcp6_unittests-tee_times_unittest.obj `if test -f 'tee_times_unittest.cc'; then $(CYGPATH_W) 'tee_times_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tee_times_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-tee_times_unittest.Tpo $(DEPDIR)/dhcp6_unittests-tee_times_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tee_times_unittest.cc' object='dhcp6_unittests-tee_times_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-tee_times_unittest.obj `if test -f 'tee_times_unittest.cc'; then $(CYGPATH_W) 'tee_times_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tee_times_unittest.cc'; fi`
+
+dhcp6_unittests-vendor_opts_unittest.o: vendor_opts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-vendor_opts_unittest.o -MD -MP -MF $(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Tpo -c -o dhcp6_unittests-vendor_opts_unittest.o `test -f 'vendor_opts_unittest.cc' || echo '$(srcdir)/'`vendor_opts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Tpo $(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='vendor_opts_unittest.cc' object='dhcp6_unittests-vendor_opts_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-vendor_opts_unittest.o `test -f 'vendor_opts_unittest.cc' || echo '$(srcdir)/'`vendor_opts_unittest.cc
+
+dhcp6_unittests-vendor_opts_unittest.obj: vendor_opts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT dhcp6_unittests-vendor_opts_unittest.obj -MD -MP -MF $(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Tpo -c -o dhcp6_unittests-vendor_opts_unittest.obj `if test -f 'vendor_opts_unittest.cc'; then $(CYGPATH_W) 'vendor_opts_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/vendor_opts_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Tpo $(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='vendor_opts_unittest.cc' object='dhcp6_unittests-vendor_opts_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dhcp6_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o dhcp6_unittests-vendor_opts_unittest.obj `if test -f 'vendor_opts_unittest.cc'; then $(CYGPATH_W) 'vendor_opts_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/vendor_opts_unittest.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+check-TESTS: $(TESTS)
+ @failed=0; all=0; xfail=0; xpass=0; skip=0; \
+ srcdir=$(srcdir); export srcdir; \
+ list=' $(TESTS) '; \
+ $(am__tty_colors); \
+ if test -n "$$list"; then \
+ for tst in $$list; do \
+ if test -f ./$$tst; then dir=./; \
+ elif test -f $$tst; then dir=; \
+ else dir="$(srcdir)/"; fi; \
+ if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xpass=`expr $$xpass + 1`; \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=XPASS; \
+ ;; \
+ *) \
+ col=$$grn; res=PASS; \
+ ;; \
+ esac; \
+ elif test $$? -ne 77; then \
+ all=`expr $$all + 1`; \
+ case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$tst[\ \ ]*) \
+ xfail=`expr $$xfail + 1`; \
+ col=$$lgn; res=XFAIL; \
+ ;; \
+ *) \
+ failed=`expr $$failed + 1`; \
+ col=$$red; res=FAIL; \
+ ;; \
+ esac; \
+ else \
+ skip=`expr $$skip + 1`; \
+ col=$$blu; res=SKIP; \
+ fi; \
+ echo "$${col}$$res$${std}: $$tst"; \
+ done; \
+ if test "$$all" -eq 1; then \
+ tests="test"; \
+ All=""; \
+ else \
+ tests="tests"; \
+ All="All "; \
+ fi; \
+ if test "$$failed" -eq 0; then \
+ if test "$$xfail" -eq 0; then \
+ banner="$$All$$all $$tests passed"; \
+ else \
+ if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \
+ banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \
+ fi; \
+ else \
+ if test "$$xpass" -eq 0; then \
+ banner="$$failed of $$all $$tests failed"; \
+ else \
+ if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \
+ banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \
+ fi; \
+ fi; \
+ dashes="$$banner"; \
+ skipped=""; \
+ if test "$$skip" -ne 0; then \
+ if test "$$skip" -eq 1; then \
+ skipped="($$skip test was not run)"; \
+ else \
+ skipped="($$skip tests were not run)"; \
+ fi; \
+ test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$skipped"; \
+ fi; \
+ report=""; \
+ if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \
+ dashes="$$report"; \
+ fi; \
+ dashes=`echo "$$dashes" | sed s/./=/g`; \
+ if test "$$failed" -eq 0; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ fi; \
+ echo "$${col}$$dashes$${std}"; \
+ echo "$${col}$$banner$${std}"; \
+ test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \
+ test -z "$$report" || echo "$${col}$$report$${std}"; \
+ echo "$${col}$$dashes$${std}"; \
+ test "$$failed" -eq 0; \
+ else :; fi
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_SCRIPTS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(SCRIPTS)
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-classify_unittests.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-client_handler_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-config_backend_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-confirm_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-d2_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-decline_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_client.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-hooks_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-host_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-infrequest_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-marker_file.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-rebind_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-renew_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-sarr_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-shared_network_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-tee_times_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Po
+ -rm -f ./$(DEPDIR)/libco1_la-callout_library_1.Plo
+ -rm -f ./$(DEPDIR)/libco2_la-callout_library_2.Plo
+ -rm -f ./$(DEPDIR)/libco3_la-callout_library_3.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-classify_unittests.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-client_handler_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-config_backend_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-confirm_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-ctrl_dhcp6_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-d2_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-decline_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_client.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_message_test.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_test_utils.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6_unittests.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-dhcp6to4_ipc_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-hooks_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-host_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-infrequest_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-kea_controller_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-marker_file.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-rebind_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-renew_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-sarr_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-shared_network_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-simple_parser6_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-tee_times_unittest.Po
+ -rm -f ./$(DEPDIR)/dhcp6_unittests-vendor_opts_unittest.Po
+ -rm -f ./$(DEPDIR)/libco1_la-callout_library_1.Plo
+ -rm -f ./$(DEPDIR)/libco2_la-callout_library_2.Plo
+ -rm -f ./$(DEPDIR)/libco3_la-callout_library_3.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) check-am install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-TESTS check-am clean clean-generic \
+ clean-libtool clean-noinstLTLIBRARIES clean-noinstPROGRAMS \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Use this target if you want to rebuild the get-config unit-tests.
+#
+# TODO: We could also automate the replacement step with some variation
+# of this: https://stackoverflow.com/questions/6790631
+@HAVE_GTEST_TRUE@rebuild-tests:
+@HAVE_GTEST_TRUE@ rm -f x u get_config_unittest.cc
+@HAVE_GTEST_TRUE@ cp -f get_config_unittest.cc.skel get_config_unittest.cc
+@HAVE_GTEST_TRUE@ $(MAKE) CXXFLAGS=-DEXTRACT_CONFIG V=1
+@HAVE_GTEST_TRUE@ ./dhcp6_unittests --gtest_filter="Dhcp6Parser*" > /dev/null 2> x
+@HAVE_GTEST_TRUE@ echo "Please copy content of x file into EXTRACTED_CONFIGS in get_config_unittest.cc"
+@HAVE_GTEST_TRUE@ read -p "Press ENTER when ready"
+@HAVE_GTEST_TRUE@ $(MAKE) CXXFLAGS=-DGENERATE_ACTION V=1
+@HAVE_GTEST_TRUE@ ./dhcp6_unittests --gtest_filter="Dhcp6GetConfig*" > /dev/null 2> u
+@HAVE_GTEST_TRUE@ echo "Please copy content of u file into UNPARSED_CONFIGS in get_config_unittest.cc"
+@HAVE_GTEST_TRUE@ read -p "Press ENTER when ready"
+@HAVE_GTEST_TRUE@ touch get_config_unittest.cc
+@HAVE_GTEST_TRUE@ $(MAKE)
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/bin/dhcp6/tests/callout_library_1.cc b/src/bin/dhcp6/tests/callout_library_1.cc
new file mode 100644
index 0000000..356862b
--- /dev/null
+++ b/src/bin/dhcp6/tests/callout_library_1.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests. See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 1;
+#include <config.h>
+
+#include <dhcp6/tests/callout_library_common.h>
+
+// Functions accessed by the hooks framework use C linkage to avoid the name
+// mangling that accompanies use of the C++ compiler as well as to avoid
+// issues related to namespaces.
+extern "C" {
+
+int (*do_load)(isc::hooks::LibraryHandle& handle);
+
+int (*do_unload)();
+
+/// @brief This function is called to retrieve the multi-threading compatibility.
+///
+/// @return 1 which means compatible with multi-threading.
+int multi_threading_compatible() {
+ return (1);
+}
+
+} // end extern "C"
diff --git a/src/bin/dhcp6/tests/callout_library_2.cc b/src/bin/dhcp6/tests/callout_library_2.cc
new file mode 100644
index 0000000..12354e6
--- /dev/null
+++ b/src/bin/dhcp6/tests/callout_library_2.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests. See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 2;
+#include <config.h>
+
+#include <dhcp6/tests/callout_library_common.h>
+
+extern "C" {
+
+int (*do_load)(isc::hooks::LibraryHandle& handle);
+
+int (*do_unload)();
+
+}
diff --git a/src/bin/dhcp6/tests/callout_library_3.cc b/src/bin/dhcp6/tests/callout_library_3.cc
new file mode 100644
index 0000000..c8e212e
--- /dev/null
+++ b/src/bin/dhcp6/tests/callout_library_3.cc
@@ -0,0 +1,93 @@
+// Copyright (C) 2018-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file
+/// @brief Callout library for testing execution of the dhcp6_srv_configured
+/// hook point.
+///
+static const int LIBRARY_NUMBER = 3;
+#include <config.h>
+
+#include <dhcp6/tests/callout_library_common.h>
+#include <dhcpsrv/srv_config.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::hooks;
+
+// Functions accessed by the hooks framework use C linkage to avoid the name
+// mangling that accompanies use of the C++ compiler as well as to avoid
+// issues related to namespaces.
+extern "C" {
+
+int
+do_load_impl(LibraryHandle& handle) {
+ // Determine if this callout is configured to fail.
+ isc::dhcp::SrvConfigPtr config;
+ isc::data::ConstElementPtr const& parameters(handle.getParameters());
+ isc::data::ConstElementPtr mode_element(parameters ? parameters->get("mode") : 0);
+ std::string mode(mode_element ? mode_element->stringValue() : "");
+ if (mode == "fail-on-load") {
+ return (1);
+ }
+ return (0);
+}
+
+int (*do_load)(isc::hooks::LibraryHandle& handle) = do_load_impl;
+
+int (*do_unload)();
+
+/// @brief Callout which appends library number and provided arguments to
+/// the marker file for dhcp6_srv_configured callout.
+///
+/// @param handle callout handle passed to the callout.
+///
+/// @return 0 on success, 1 otherwise.
+int
+dhcp6_srv_configured(CalloutHandle& handle) {
+ // Append library number.
+ if (appendDigit(SRV_CONFIG_MARKER_FILE)) {
+ return (1);
+ }
+
+ // Append argument names.
+ std::vector<std::string> args = handle.getArgumentNames();
+ for (auto arg = args.begin(); arg != args.end(); ++arg) {
+ if (appendArgument(SRV_CONFIG_MARKER_FILE, arg->c_str()) != 0) {
+ return (1);
+ }
+ }
+
+ // Determine if this callout is configured to fail.
+ isc::dhcp::SrvConfigPtr config;
+ handle.getArgument("server_config", config);
+ isc::data::ConstElementPtr mode_element(config ? config->toElement() ?
+ config->toElement()->find("Dhcp6/hooks-libraries") ?
+ config->toElement()->find("Dhcp6/hooks-libraries")->get(0) ?
+ config->toElement()->find("Dhcp6/hooks-libraries")->get(0)->get("parameters") ?
+ config->toElement()->find("Dhcp6/hooks-libraries")->get(0)->get("parameters")->get("mode")
+ : 0 : 0 : 0 : 0 : 0);
+ std::string mode(mode_element ? mode_element->stringValue() : "");
+ if (mode == "fail-without-error") {
+ handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+ } else if (mode == "fail-with-error") {
+ std::string error("user explicitly configured me to fail");
+ handle.setArgument("error", error);
+ handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+ }
+
+ return (0);
+}
+
+/// @brief This function is called to retrieve the multi-threading compatibility.
+///
+/// @return 0 which means not compatible with multi-threading.
+int multi_threading_compatible() {
+ return (0);
+}
+
+} // end extern "C"
diff --git a/src/bin/dhcp6/tests/callout_library_common.h b/src/bin/dhcp6/tests/callout_library_common.h
new file mode 100644
index 0000000..d3c6d94
--- /dev/null
+++ b/src/bin/dhcp6/tests/callout_library_common.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.
+///
+/// To check that they libraries are loaded and unloaded correctly, the load
+/// and unload functions in this library maintain two marker files - the load
+/// marker file and the unload marker file. The functions append a single
+/// line to the file, creating the file if need be. In this way, the test code
+/// can determine whether the load/unload functions have been run and, if so,
+/// in what order.
+///
+/// The additional marker file is created for the dhcp6_srv_configured hook
+/// point. It records the library number and the names of the parameters
+/// provided to the callout.
+///
+/// This file is the common library file for the tests. It will not compile
+/// by itself - it is included into each callout library which specifies the
+/// missing constant LIBRARY_NUMBER before the inclusion.
+
+#include <config.h>
+#include <hooks/hooks.h>
+#include "marker_file.h"
+
+#include <fstream>
+
+extern "C" {
+
+extern int (*do_load)(isc::hooks::LibraryHandle& handle);
+
+extern int (*do_unload)();
+
+/// @brief Append digit to marker file
+///
+/// If the marker file does not exist, create it. Then append the single
+/// digit (given by the constant LIBRARY_NUMBER) defined earlier to it and
+/// close the file.
+///
+/// @param name Name of the file to open
+///
+/// @return 0 on success, non-zero on error.
+int
+appendDigit(const char* name) {
+ // Open the file and check if successful.
+ std::fstream file(name, std::fstream::out | std::fstream::app);
+ if (!file.good()) {
+ return (1);
+ }
+
+ // Add the library number to it and close.
+ file << LIBRARY_NUMBER;
+ file.close();
+
+ return (0);
+}
+
+/// @brief Append argument name passed to the callout to a marker file.
+///
+/// @param file_name Name of the file to open.
+/// @param parameter Parameter name.
+///
+/// @return 0 on success, non-zero on error.
+int appendArgument(const char* file_name, const char* argument) {
+ // Open the file and check if successful.
+ std::fstream file(file_name, std::fstream::out | std::fstream::app);
+ if (!file.good()) {
+ return (1);
+ }
+
+ // Add the library number to it and close.
+ file << argument;
+ file.close();
+
+ return (0);
+}
+
+// Framework functions
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+int
+load(isc::hooks::LibraryHandle& handle) {
+ int result = 0;
+ result = appendDigit(LOAD_MARKER_FILE);
+ if (result == 0 && do_load) {
+ result = do_load(handle);
+ }
+ return (result);
+}
+
+int
+unload() {
+ int result = 0;
+ result = appendDigit(UNLOAD_MARKER_FILE);
+ if (result == 0 && do_unload) {
+ result = do_unload();
+ }
+ return (result);
+}
+
+};
diff --git a/src/bin/dhcp6/tests/classify_unittests.cc b/src/bin/dhcp6/tests/classify_unittests.cc
new file mode 100644
index 0000000..fdc82ca
--- /dev/null
+++ b/src/bin/dhcp6/tests/classify_unittests.cc
@@ -0,0 +1,2589 @@
+// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <asiolink/io_address.h>
+#include <stats/stats_mgr.h>
+#include <boost/pointer_cast.hpp>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used by the classification unit tests.
+///
+/// - Configuration 0:
+/// - Specifies 3 classes: 'router', 'reserved-class1' and 'reserved-class2'.
+/// - 'router' class is assigned when the client sends option 1234 (string)
+/// equal to 'foo'.
+/// - The other two classes are reserved for the client having
+/// DUID '01:02:03:04'
+/// - Class 'router' includes option 'ipv6-forwarding'.
+/// - Class 'reserved-class1' includes option DNS servers.
+/// - Class 'reserved-class2' includes option NIS servers.
+/// - All three options are sent when client has reservations for the
+/// 'reserved-class1', 'reserved-class2' and sends option 1234 with
+/// the 'foo' value.
+/// - There is one subnet specified 2001:db8:1::/48 with pool of
+/// IPv6 addresses.
+///
+/// - Configuration 1:
+/// - Used for complex membership (example taken from HA)
+/// - 1 subnet: 2001:db8:1::/48
+/// - 4 pools: 2001:db8:1:1::/64, 2001:db8:1:2::/64,
+/// 2001:db8:1:3::/64 and 2001:db8:1:4::/64
+/// - 4 classes to compose:
+/// server1 and server2 for each HA server
+/// option 1234 'foo' aka telephones
+/// option 1234 'bar' aka computers
+///
+/// - Configuration 2:
+/// - Used for complex membership (example taken from HA) and pd-pools
+/// - 1 subnet: 2001:db8::/32
+/// - 4 pd-pools: 2001:db8:1::/48, 2001:db8:2::/48,
+/// 2001:db8:3::/48 and 2001:db8:4::/48
+/// - 4 classes to compose:
+/// server1 and server2 for each HA server
+/// option 1234 'foo' aka telephones
+/// option 1234 'bar' aka computers
+///
+/// - Configuration 3:
+/// - Used for the DROP class
+/// - 1 subnet: 2001:db8:1::/48
+/// - 2 pool: 2001:db8:1:1::/64
+/// - the following class defined: option 1234 'foo', DROP
+///
+/// - Configuration 4:
+/// - Used for the DROP class and reservation existence
+/// - 1 subnet: 2001:db8:1::/48
+/// - 2 pool: 2001:db8:1:1::/64
+/// - the following class defined: not member('KNOWN'), DROP
+/// @note the reservation includes a hostname because raw reservations are
+/// not yet allowed
+///
+/// - Configuration 5:
+/// - Used for the DROP class and reservation class
+/// - 1 subnet: 2001:db8:1::/48
+/// - 1 pool: 2001:db8:1:1::/64
+/// - the following class defined:
+/// - allowed
+/// - member('KNOWN') or member('UNKNOWN'), t
+/// - not member('allowed') and member('t'), DROP
+/// The function of the always true 't' class is to move the DROP
+/// evaluation to the classification point after the host reservation
+/// lookup, i.e. indirect KNOWN / UNKNOWN dependency
+///
+/// - Configuration 6:
+/// - Used for the early global reservations lookup / select subnet.
+/// - 2 subnets: 2001:db8:1::/48 (guarded) and 2001:db8:2::/48
+/// - 2 pools: 2001:db8:1:1::/64 and 2001:db8:2:1::/64
+/// - 1 global reservation setting the first class
+/// - the following class defined: first
+///
+/// - Configuration 7:
+/// - Used for the early global reservations lookup / drop.
+/// - 1 subnet: 2001:db8:1::/48
+/// - 1 pool: 2001:db8:1:1::/64
+/// - 1 global reservation setting the DROP class
+///
+const char* CONFIGS[] = {
+ // Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ "
+ "{"
+ " \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\""
+ "},"
+ "{"
+ " \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\""
+ "} ],"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"router\","
+ " \"test\": \"option[host-name].text == 'foo'\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\""
+ " } ]"
+ "},"
+ "{"
+ " \"name\": \"reserved-class1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::50\""
+ " }"
+ " ]"
+ "},"
+ "{"
+ " \"name\": \"reserved-class2\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"2001:db8:1::100\""
+ " }"
+ " ]"
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"id\": 1, "
+ " \"interface\": \"eth1\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04\","
+ " \"client-classes\": [ \"reserved-class1\", \"reserved-class2\" ]"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ "
+ "{"
+ " \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\""
+ "} ],"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"server1\""
+ "},"
+ "{"
+ " \"name\": \"server2\""
+ "},"
+ "{"
+ " \"name\": \"telephones\","
+ " \"test\": \"option[host-name].text == 'foo'\""
+ "},"
+ "{"
+ " \"name\": \"computers\","
+ " \"test\": \"option[host-name].text == 'bar'\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_telephones\","
+ " \"test\": \"member('server1') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_computers\","
+ " \"test\": \"member('server1') and member('computers')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_telephones\","
+ " \"test\": \"member('server2') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_computers\","
+ " \"test\": \"member('server2') and member('computers')\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"subnet\": \"2001:db8:1::/48\", "
+ " \"id\": 1, "
+ " \"interface\": \"eth1\","
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8:1:1::/64\","
+ " \"client-class\": \"server1_and_telephones\" },"
+ " { \"pool\": \"2001:db8:1:2::/64\","
+ " \"client-class\": \"server1_and_computers\" },"
+ " { \"pool\": \"2001:db8:1:3::/64\","
+ " \"client-class\": \"server2_and_telephones\" },"
+ " { \"pool\": \"2001:db8:1:4::/64\","
+ " \"client-class\": \"server2_and_computers\" } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ "
+ "{"
+ " \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\""
+ "} ],"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"server1\""
+ "},"
+ "{"
+ " \"name\": \"server2\""
+ "},"
+ "{"
+ " \"name\": \"telephones\","
+ " \"test\": \"option[host-name].text == 'foo'\""
+ "},"
+ "{"
+ " \"name\": \"computers\","
+ " \"test\": \"option[host-name].text == 'bar'\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_telephones\","
+ " \"test\": \"member('server1') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_computers\","
+ " \"test\": \"member('server1') and member('computers')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_telephones\","
+ " \"test\": \"member('server2') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_computers\","
+ " \"test\": \"member('server2') and member('computers')\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"subnet\": \"2001:db8::/32\", "
+ " \"id\": 1, "
+ " \"interface\": \"eth1\","
+ " \"pd-pools\": [ "
+ " { \"prefix\": \"2001:db8:1::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server1_and_telephones\" },"
+ " { \"prefix\": \"2001:db8:2::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server1_and_computers\" },"
+ " { \"prefix\": \"2001:db8:3::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server2_and_telephones\" },"
+ " { \"prefix\": \"2001:db8:4::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server2_and_computers\" } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ "
+ "{"
+ " \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\""
+ "},"
+ "{"
+ " \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\""
+ "} ],"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"DROP\","
+ " \"test\": \"option[host-name].text == 'foo'\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"DROP\","
+ " \"test\": \"not member('KNOWN')\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04\","
+ " \"hostname\": \"allowed\""
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 5
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"allowed\""
+ "},"
+ "{"
+ " \"name\": \"t\","
+ " \"test\": \"member('KNOWN') or member('UNKNOWN')\""
+ "},"
+ "{"
+ " \"name\": \"DROP\","
+ " \"test\": \"not member('allowed') and member('t')\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04\","
+ " \"client-classes\": [ \"allowed\" ]"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 6
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"early-global-reservations-lookup\": true, "
+ "\"preferred-lifetime\": 3000, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"first\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\","
+ " \"id\": 1,"
+ " \"client-class\": \"first\""
+ "},"
+ "{"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ], "
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"interface\": \"eth1\","
+ " \"id\": 2"
+ "}"
+ "],"
+ "\"reservations\": ["
+ "{"
+ " \"duid\": \"01:02:03:04\","
+ " \"client-classes\": [ \"first\" ]"
+ "}"
+ "],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 7
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"early-global-reservations-lookup\": true, "
+ "\"preferred-lifetime\": 3000, "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ "{"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\","
+ " \"id\": 1"
+ "}"
+ "],"
+ "\"reservations\": ["
+ "{"
+ " \"duid\": \"01:02:03:04\","
+ " \"client-classes\": [ \"DROP\" ]"
+ "}"
+ "],"
+ "\"valid-lifetime\": 4000 }"
+};
+
+/// @brief Test fixture class for testing client classification by the
+/// DHCPv6 server.
+///
+/// @todo There are numerous tests not using Dhcp6Client class. They should be
+/// migrated to use it one day.
+class ClassifyTest : public Dhcpv6SrvTest {
+public:
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ ClassifyTest()
+ : Dhcpv6SrvTest(),
+ iface_mgr_test_config_(true) {
+ }
+
+ /// @brief Verify values of options returned by the server when the server
+ /// uses configuration with index 0.
+ ///
+ /// @param config Reference to DHCP client's configuration received.
+ /// @param ip_forwarding Expected value of IP forwarding option. This option
+ /// is expected to always be present.
+ /// @param dns_servers String holding an address carried within DNS
+ /// servers option. If this value is empty, the option is expected to not
+ /// be included in the response.
+ /// @param nis_servers String holding an address carried within NIS
+ /// servers option. If this value is empty, the option is expected to not
+ /// be included in the response.
+ void verifyConfig0Options(const Dhcp6Client::Configuration& config,
+ const uint8_t ip_forwarding = 1,
+ const std::string& dns_servers = "",
+ const std::string& nis_servers = "") {
+ // IP forwarding option should always exist.
+ OptionPtr ip_forwarding_opt = config.findOption(2345);
+ ASSERT_TRUE(ip_forwarding_opt);
+ // The option comprises 2 bytes of option code, 2 bytes of option length,
+ // and a single 1 byte value. This makes it 5 bytes of a total length.
+ ASSERT_EQ(5, ip_forwarding_opt->len());
+ ASSERT_EQ(static_cast<int>(ip_forwarding),
+ static_cast<int>(ip_forwarding_opt->getUint8()));
+
+ // DNS servers.
+ Option6AddrLstPtr dns_servers_opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(config.findOption(D6O_NAME_SERVERS));
+ if (!dns_servers.empty()) {
+ ASSERT_TRUE(dns_servers_opt);
+ Option6AddrLst::AddressContainer addresses = dns_servers_opt->getAddresses();
+ // For simplicity, we expect only a single address.
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ(dns_servers, addresses[0].toText());
+
+ } else {
+ EXPECT_FALSE(dns_servers_opt);
+ }
+
+ // NIS servers.
+ Option6AddrLstPtr nis_servers_opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(config.findOption(D6O_NIS_SERVERS));
+ if (!nis_servers.empty()) {
+ ASSERT_TRUE(nis_servers_opt);
+ Option6AddrLst::AddressContainer addresses = nis_servers_opt->getAddresses();
+ // For simplicity, we expect only a single address.
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ(nis_servers, addresses[0].toText());
+
+ } else {
+ EXPECT_FALSE(nis_servers_opt);
+ }
+ }
+
+ /// @brief Create a solicit
+ Pkt6Ptr createSolicit(std::string remote_addr = "fe80::abcd") {
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query->setRemoteAddr(IOAddress(remote_addr));
+ query->addOption(clientid);
+ query->setIface("eth1");
+ query->setIndex(ETH1_INDEX);
+ query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ return (query);
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+// Checks if DOCSIS client packets are classified properly
+TEST_F(ClassifyTest, docsisClientClassification) {
+
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a relayed SOLICIT. This particular relayed SOLICIT has
+ // vendor-class set to docsis3.0
+ Pkt6Ptr sol1;
+ ASSERT_NO_THROW(sol1 = PktCaptures::captureDocsisRelayedSolicit());
+ ASSERT_NO_THROW(sol1->unpack());
+
+ srv.classifyPacket(sol1);
+
+ // It should belong to docsis3.0 class. It should not belong to eRouter1.0
+ EXPECT_TRUE(sol1->inClass("VENDOR_CLASS_docsis3.0"));
+ EXPECT_FALSE(sol1->inClass("eRouter1.0"));
+
+ // Let's get a relayed SOLICIT. This particular relayed SOLICIT has
+ // vendor-class set to eRouter1.0
+ Pkt6Ptr sol2;
+ ASSERT_NO_THROW(sol2 = PktCaptures::captureeRouterRelayedSolicit());
+ ASSERT_NO_THROW(sol2->unpack());
+
+ srv.classifyPacket(sol2);
+
+ EXPECT_TRUE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "eRouter1.0"));
+ EXPECT_FALSE(sol2->inClass(srv.VENDOR_CLASS_PREFIX + "docsis3.0"));
+}
+
+// Checks if client packets are classified properly using match expressions.
+// Note option names and definitions are used.
+TEST_F(ClassifyTest, matchClassification) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[host-name].text == 'foo'\" },"
+ "{ \"name\": \"template-client-id\","
+ " \"template-test\": \"substring(option[1].hex,0,3)\" },"
+ "{ \"name\": \"SPAWN_template-hostname_foo\" },"
+ "{ \"name\": \"template-hostname\","
+ " \"template-test\": \"option[host-name].text\"} ] }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ Pkt6Ptr query1 = createSolicit();
+ Pkt6Ptr query2 = createSolicit();
+ Pkt6Ptr query3 = createSolicit();
+
+ // Create and add an ORO option to the first 2 queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ EXPECT_EQ(query1->classes_.size(), 6);
+ EXPECT_EQ(query2->classes_.size(), 3);
+ EXPECT_EQ(query3->classes_.size(), 6);
+
+ EXPECT_TRUE(query1->inClass("ALL"));
+ EXPECT_TRUE(query2->inClass("ALL"));
+ EXPECT_TRUE(query3->inClass("ALL"));
+
+ // Packets with the exception of the second should be in the router class
+ EXPECT_TRUE(query1->inClass("router"));
+ EXPECT_FALSE(query2->inClass("router"));
+ EXPECT_TRUE(query3->inClass("router"));
+
+ EXPECT_TRUE(query1->inClass("template-hostname"));
+ EXPECT_FALSE(query2->inClass("template-hostname"));
+ EXPECT_TRUE(query3->inClass("template-hostname"));
+
+ EXPECT_TRUE(query1->inClass("SPAWN_template-hostname_foo"));
+ EXPECT_FALSE(query2->inClass("SPAWN_template-hostname_foo"));
+ EXPECT_TRUE(query3->inClass("SPAWN_template-hostname_foo"));
+
+ EXPECT_TRUE(query1->inClass("template-client-id"));
+ EXPECT_TRUE(query2->inClass("template-client-id"));
+ EXPECT_TRUE(query3->inClass("template-client-id"));
+
+ EXPECT_TRUE(query1->inClass("SPAWN_template-client-id_def"));
+ EXPECT_TRUE(query2->inClass("SPAWN_template-client-id_def"));
+ EXPECT_TRUE(query3->inClass("SPAWN_template-client-id_def"));
+
+ // Process queries
+ AllocEngine::ClientContext6 ctx1;
+ bool drop = !srv.earlyGHRLookup(query1, ctx1);
+ ASSERT_FALSE(drop);
+ srv.initContext(query1, ctx1, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response1 = srv.processSolicit(ctx1);
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv.earlyGHRLookup(query2, ctx2);
+ ASSERT_FALSE(drop);
+ srv.initContext(query2, ctx2, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response2 = srv.processSolicit(ctx2);
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv.earlyGHRLookup(query3, ctx3);
+ ASSERT_FALSE(drop);
+ srv.initContext(query3, ctx3, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response3 = srv.processSolicit(ctx3);
+
+ // Classification processing should add an ip-forwarding option
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_TRUE(opt1);
+
+ // But only for the first query: second was not classified
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+
+ // But only for the first query: third has no ORO
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_FALSE(opt3);
+}
+
+// Check that only-if-required classes are not evaluated by classifyPacket
+TEST_F(ClassifyTest, required) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"only-if-required\": true, "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[host-name].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->setIndex(ETH1_INDEX);
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->setIndex(ETH1_INDEX);
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->setIndex(ETH1_INDEX);
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to the first 2 queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // No packet is in the router class
+ EXPECT_FALSE(query1->inClass("router"));
+ EXPECT_FALSE(query2->inClass("router"));
+ EXPECT_FALSE(query3->inClass("router"));
+
+ // Process queries
+ AllocEngine::ClientContext6 ctx1;
+ bool drop = !srv.earlyGHRLookup(query1, ctx1);
+ ASSERT_FALSE(drop);
+ srv.initContext(query1, ctx1, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response1 = srv.processSolicit(ctx1);
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv.earlyGHRLookup(query2, ctx2);
+ ASSERT_FALSE(drop);
+ srv.initContext(query2, ctx2, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response2 = srv.processSolicit(ctx2);
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv.earlyGHRLookup(query3, ctx3);
+ ASSERT_FALSE(drop);
+ srv.initContext(query3, ctx3, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response3 = srv.processSolicit(ctx3);
+
+ // Classification processing should do nothing
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_FALSE(opt1);
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_FALSE(opt3);
+}
+
+// Checks that when only-if-required classes are still evaluated
+TEST_F(ClassifyTest, requiredClassification) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"require-client-classes\": [ \"router\" ], "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"only-if-required\": true, "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[host-name].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->setIndex(ETH1_INDEX);
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->setIndex(ETH1_INDEX);
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->setIndex(ETH1_INDEX);
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to the first 2 queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // No packet is in the router class yet
+ EXPECT_FALSE(query1->inClass("router"));
+ EXPECT_FALSE(query2->inClass("router"));
+ EXPECT_FALSE(query3->inClass("router"));
+
+ // Process queries
+ AllocEngine::ClientContext6 ctx1;
+ bool drop = !srv.earlyGHRLookup(query1, ctx1);
+ ASSERT_FALSE(drop);
+ srv.initContext(query1, ctx1, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response1 = srv.processSolicit(ctx1);
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv.earlyGHRLookup(query2, ctx2);
+ ASSERT_FALSE(drop);
+ srv.initContext(query2, ctx2, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response2 = srv.processSolicit(ctx2);
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv.earlyGHRLookup(query3, ctx3);
+ ASSERT_FALSE(drop);
+ srv.initContext(query3, ctx3, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response3 = srv.processSolicit(ctx3);
+
+ // Classification processing should add an ip-forwarding option
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_TRUE(opt1);
+
+ // But only for the first query: second was not classified
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+
+ // But only for the first query: third has no ORO
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_FALSE(opt3);
+}
+
+// Checks subnet options have the priority over class options
+TEST_F(ClassifyTest, subnetClassPriority) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // Subnet sets an ipv6-forwarding option in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\" } ] } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\","
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[1234].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create a packet with enough to select the subnet and go through
+ // the SOLICIT processing
+ Pkt6Ptr query = createSolicit();
+
+ // Create and add an ORO option to the query
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query->addOption(oro);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Classify the packet
+ srv.classifyPacket(query);
+
+ // The packet should be in the router class
+ EXPECT_TRUE(query->inClass("router"));
+
+ // Process the query
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(query, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(query, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response = srv.processSolicit(ctx);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(2345);
+ ASSERT_TRUE(opt);
+ ASSERT_GT(opt->len(), opt->getHeaderLen());
+ // Classification sets the value to true/1, subnet to false/0
+ // Here subnet has the priority
+ EXPECT_EQ(0, opt->getUint8());
+}
+
+// Checks subnet options have the priority over global options
+TEST_F(ClassifyTest, subnetGlobalPriority) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // Subnet sets an ipv6-forwarding option in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\" } ], "
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\" } ] } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create a packet with enough to select the subnet and go through
+ // the SOLICIT processing
+ Pkt6Ptr query = createSolicit();
+
+ // Create and add an ORO option to the query
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query->addOption(oro);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Process the query
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(query, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(query, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response = srv.processSolicit(ctx);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(2345);
+ ASSERT_TRUE(opt);
+ ASSERT_GT(opt->len(), opt->getHeaderLen());
+ // Global sets the value to true/1, subnet to false/0
+ // Here subnet has the priority
+ EXPECT_EQ(0, opt->getUint8());
+}
+
+// Checks class options have the priority over global options
+TEST_F(ClassifyTest, classGlobalPriority) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // A global ipv6-forwarding option is set in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" } ],"
+ "\"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\" } ], "
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\","
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[1234].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create a packet with enough to select the subnet and go through
+ // the SOLICIT processing
+ Pkt6Ptr query = createSolicit();
+
+ // Create and add an ORO option to the query
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query->addOption(oro);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Classify the packet
+ srv.classifyPacket(query);
+
+ // The packet should be in the router class
+ EXPECT_TRUE(query->inClass("router"));
+
+ // Process the query
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(query, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(query, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response = srv.processSolicit(ctx);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(2345);
+ ASSERT_TRUE(opt);
+ ASSERT_GT(opt->len(), opt->getHeaderLen());
+ // Classification sets the value to true/1, global to false/0
+ // Here class has the priority
+ EXPECT_NE(0, opt->getUint8());
+}
+
+// Checks class options have the priority over global persistent options
+TEST_F(ClassifyTest, classGlobalPersistency) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // Subnet sets an ipv6-forwarding option in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ // Note the persistency flag follows a "OR" semantic so to set
+ // it to false (or to leave the default) has no effect.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\", "
+ " \"always-send\": true, "
+ " \"never-send\": false } ], "
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\", "
+ " \"always-send\": false, "
+ " \"never-send\": false } ] } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create a packet with enough to select the subnet and go through
+ // the SOLICIT processing
+ Pkt6Ptr query = createSolicit();
+
+ // Do not add an ORO.
+ OptionPtr oro = query->getOption(D6O_ORO);
+ EXPECT_FALSE(oro);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Process the query
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(query, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(query, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response = srv.processSolicit(ctx);
+
+ // Processing should add an ip-forwarding option
+ OptionPtr opt = response->getOption(2345);
+ ASSERT_TRUE(opt);
+ ASSERT_GT(opt->len(), opt->getHeaderLen());
+ // Global sets the value to true/1, subnet to false/0
+ // Here subnet has the priority
+ EXPECT_EQ(0, opt->getUint8());
+}
+
+// Checks class never-send options have the priority over everything else.
+TEST_F(ClassifyTest, classNeverSend) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // Subnet sets an ipv6-forwarding option in the response.
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ // Note the cancellation flag follows a "OR" semantic so to set
+ // it to false (or to leave the default) has no effect.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\", "
+ " \"always-send\": true, "
+ " \"never-send\": false } ], "
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"always-send\": false, "
+ " \"never-send\": true } ] } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create a packet with enough to select the subnet and go through
+ // the SOLICIT processing
+ Pkt6Ptr query = createSolicit();
+
+ // Do not add an ORO.
+ OptionPtr oro = query->getOption(D6O_ORO);
+ EXPECT_FALSE(oro);
+
+ // Create and add a host-name option to the query
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query->addOption(hostname);
+
+ // Process the query
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(query, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(query, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response = srv.processSolicit(ctx);
+
+ // Processing should not add an ip-forwarding option
+ EXPECT_FALSE(response->getOption(2345));
+}
+
+// Checks if the client-class field is indeed used for subnet selection.
+// Note that packet classification is already checked in ClassifyTest
+// .*Classification above.
+TEST_F(ClassifyTest, clientClassifySubnet) {
+
+ // This test configures 2 subnets. We actually only need the
+ // first one, but since there's still this ugly hack that picks
+ // the pool if there is only one, we must use more than one
+ // subnet. That ugly hack will be removed in #3242, currently
+ // under review.
+
+ // The second subnet does not play any role here. The client's
+ // IP address belongs to the first subnet, so only that first
+ // subnet is being tested.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"client-class\": \"foo\" "
+ " }, "
+ " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"id\": 2, "
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"client-class\": \"xyzzy\" "
+ " } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ Pkt6Ptr sol = createSolicit("2001:db8:1::3");
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ bool drop = false;
+ EXPECT_FALSE(srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+
+ // Let's add the packet to bar class and try again.
+ sol->addClass("bar");
+
+ // Still not supported, because it belongs to wrong class.
+ EXPECT_FALSE(srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+
+ // Let's add it to matching class.
+ sol->addClass("foo");
+
+ // This time it should work
+ EXPECT_TRUE(srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+}
+
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(ClassifyTest, clientClassifyPool) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // This test configures 2 pools.
+ // The second pool does not play any role here. The client's
+ // IP address belongs to the first pool, so only that first
+ // pool is being tested.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"client-classes\": [ "
+ " { "
+ " \"name\": \"foo\" "
+ " }, "
+ " { "
+ " \"name\": \"bar\" "
+ " } "
+ "], "
+ "\"subnet6\": [ "
+ " { \"pools\": [ "
+ " { "
+ " \"pool\": \"2001:db8:1::/64\", "
+ " \"client-class\": \"foo\" "
+ " }, "
+ " { "
+ " \"pool\": \"2001:db8:2::/64\", "
+ " \"client-class\": \"xyzzy\" "
+ " } "
+ " ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8::/40\" "
+ " } "
+ "], "
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ Pkt6Ptr query1 = createSolicit("2001:db8:1::3");
+ Pkt6Ptr query2 = createSolicit("2001:db8:1::3");
+ Pkt6Ptr query3 = createSolicit("2001:db8:1::3");
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ srv.classifyPacket(query1);
+ AllocEngine::ClientContext6 ctx1;
+ bool drop = !srv.earlyGHRLookup(query1, ctx1);
+ ASSERT_FALSE(drop);
+ srv.initContext(query1, ctx1, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response1 = srv.processSolicit(ctx1);
+ ASSERT_TRUE(response1);
+ OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na1);
+ EXPECT_TRUE(ia_na1->getOption(D6O_STATUS_CODE));
+ EXPECT_FALSE(ia_na1->getOption(D6O_IAADDR));
+
+ // Let's add the packet to bar class and try again.
+ query2->addClass("bar");
+ // Still not supported, because it belongs to wrong class.
+ srv.classifyPacket(query2);
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv.earlyGHRLookup(query2, ctx2);
+ ASSERT_FALSE(drop);
+ srv.initContext(query2, ctx2, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response2 = srv.processSolicit(ctx2);
+ ASSERT_TRUE(response2);
+ OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na2);
+ EXPECT_TRUE(ia_na2->getOption(D6O_STATUS_CODE));
+ EXPECT_FALSE(ia_na2->getOption(D6O_IAADDR));
+
+ // Let's add it to matching class.
+ query3->addClass("foo");
+ // This time it should work
+ srv.classifyPacket(query3);
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv.earlyGHRLookup(query3, ctx3);
+ ASSERT_FALSE(drop);
+ srv.initContext(query3, ctx3, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response3 = srv.processSolicit(ctx3);
+ ASSERT_TRUE(response3);
+ OptionPtr ia_na3 = response3->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na3);
+ EXPECT_FALSE(ia_na3->getOption(D6O_STATUS_CODE));
+ EXPECT_TRUE(ia_na3->getOption(D6O_IAADDR));
+}
+
+// Checks if the [UN]KNOWN built-in classes is indeed used for pool selection.
+TEST_F(ClassifyTest, clientClassifyPoolKnown) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // This test configures 2 pools.
+ // The first one requires reservation, the second does the opposite.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { \"pools\": [ "
+ " { "
+ " \"pool\": \"2001:db8:1::/64\", "
+ " \"client-class\": \"KNOWN\" "
+ " }, "
+ " { "
+ " \"pool\": \"2001:db8:2::/64\", "
+ " \"client-class\": \"UNKNOWN\" "
+ " } "
+ " ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8::/40\", "
+ " \"reservations\": [ "
+ " { \"duid\": \"01:02:03:04\", \"hostname\": \"foo\" } ] "
+ " } "
+ "], "
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ OptionPtr clientid1 = generateClientId();
+ Pkt6Ptr query1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query1->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query1->addOption(clientid1);
+ query1->setIface("eth1");
+ query1->setIndex(ETH1_INDEX);
+
+ // First pool requires reservation so the second will be used
+ srv.classifyPacket(query1);
+ AllocEngine::ClientContext6 ctx1;
+ bool drop = !srv.earlyGHRLookup(query1, ctx1);
+ ASSERT_FALSE(drop);
+ srv.initContext(query1, ctx1, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response1 = srv.processSolicit(ctx1);
+ ASSERT_TRUE(response1);
+ OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na1);
+ EXPECT_FALSE(ia_na1->getOption(D6O_STATUS_CODE));
+ OptionPtr iaaddr1 = ia_na1->getOption(D6O_IAADDR);
+ ASSERT_TRUE(iaaddr1);
+ boost::shared_ptr<Option6IAAddr> addr1 =
+ boost::dynamic_pointer_cast<Option6IAAddr>(iaaddr1);
+ ASSERT_TRUE(addr1);
+ EXPECT_EQ("2001:db8:2::", addr1->getAddress().toText());
+
+ // Try with DUID 01:02:03:04
+ uint8_t duid[] = { 0x01, 0x02, 0x03, 0x04 };
+ OptionBuffer buf(duid, duid + sizeof(duid));
+ OptionPtr clientid2(new Option(Option::V6, D6O_CLIENTID, buf));
+ Pkt6Ptr query2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
+ query2->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query2->addOption(clientid2);
+ query2->setIface("eth1");
+ query2->setIndex(ETH1_INDEX);
+
+ // Now the first pool will be used
+ srv.classifyPacket(query2);
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv.earlyGHRLookup(query2, ctx2);
+ ASSERT_FALSE(drop);
+ srv.initContext(query2, ctx2, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response2 = srv.processSolicit(ctx2);
+ ASSERT_TRUE(response2);
+ OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na2);
+ EXPECT_FALSE(ia_na2->getOption(D6O_STATUS_CODE));
+ OptionPtr iaaddr2 = ia_na2->getOption(D6O_IAADDR);
+ ASSERT_TRUE(iaaddr2);
+ boost::shared_ptr<Option6IAAddr> addr2 =
+ boost::dynamic_pointer_cast<Option6IAAddr>(iaaddr2);
+ ASSERT_TRUE(addr2);
+ EXPECT_EQ("2001:db8:1::", addr2->getAddress().toText());
+}
+
+// Tests whether a packet with custom vendor-class (not erouter or docsis)
+// is classified properly.
+TEST_F(ClassifyTest, vendorClientClassification2) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a SOLICIT.
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Now let's add a vendor-class with id=1234 and content "foo"
+ OptionVendorClassPtr vendor_class(new OptionVendorClass(Option::V6, 1234));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "foo";
+ vendor_class->addTuple(tuple);
+ sol->addOption(vendor_class);
+
+ // Now the server classifies the packet.
+ srv.classifyPacket(sol);
+
+ // The packet should now belong to VENDOR_CLASS_foo.
+ EXPECT_TRUE(sol->inClass(srv.VENDOR_CLASS_PREFIX + "foo"));
+
+ // It should not belong to "foo"
+ EXPECT_FALSE(sol->inClass("foo"));
+}
+
+// Checks if relay IP address specified in the relay-info structure can be
+// used together with client-classification.
+TEST_F(ClassifyTest, relayOverrideAndClientClass) {
+
+ // This test configures 2 subnets. They both are on the same link, so they
+ // have the same relay-ip address. Furthermore, the first subnet is
+ // reserved for clients that belong to class "foo".
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"client-class\": \"foo\", "
+ " \"relay\": { "
+ " \"ip-address\": \"2001:db8:3::1\""
+ " }"
+ " }, "
+ " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"id\": 2, "
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"relay\": { "
+ " \"ip-address\": \"2001:db8:3::1\""
+ " }"
+ " } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Use this config to set up the server
+ ASSERT_NO_THROW(configure(config));
+
+ // Let's get the subnet configuration objects
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ // Let's get them for easy reference
+ Subnet6Ptr subnet1 = *subnets->begin();
+ Subnet6Ptr subnet2 = *std::next(subnets->begin());
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet2);
+
+ Pkt6Ptr sol = createSolicit("2001:db8:1::3");
+
+ // Now pretend the packet came via one relay.
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:3::1");
+ relay.peeraddr_ = IOAddress("fe80::1");
+
+ sol->relay_info_.push_back(relay);
+
+ // This packet does not belong to class foo, so it should be rejected in
+ // subnet[0], even though the relay-ip matches. It should be accepted in
+ // subnet[1], because the subnet matches and there are no class
+ // requirements.
+ bool drop = false;
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+
+ // Now let's add this packet to class foo and recheck. This time it should
+ // be accepted in the first subnet, because both class and relay-ip match.
+ sol->addClass("foo");
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+}
+
+// This test checks that it is possible to specify static reservations for
+// client classes.
+TEST_F(ClassifyTest, clientClassesInHostReservations) {
+ Dhcp6Client client;
+ // Initially use a DUID for which there are no reservations. As a result,
+ // the client should be assigned a single class "router".
+ client.setDUID("01:02:03:05");
+ client.setInterface("eth1");
+ client.requestAddress();
+ // Request all options we may potentially get. Otherwise, the server will
+ // not return them, even when the client is assigned to the classes for
+ // which these options should be sent.
+ client.requestOption(2345);
+ client.requestOption(D6O_NAME_SERVERS);
+ client.requestOption(D6O_NIS_SERVERS);
+
+ ASSERT_NO_THROW(configure(CONFIGS[0], *client.getServer()));
+
+ // Adding this option to the client's message will cause the client to
+ // belong to the 'router' class.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ // IP forwarding should be present, but DNS and NIS servers should not.
+ ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_));
+
+ // Modify the DUID of our client to the one for which class reservations
+ // have been made.
+ client.setDUID("01:02:03:04");
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ // This time, the client should obtain options from all three classes.
+ ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+ "2001:db8:1::50",
+ "2001:db8:1::100"));
+
+ // This should also work for Request case.
+ ASSERT_NO_THROW(client.doSARR());
+ ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+ "2001:db8:1::50",
+ "2001:db8:1::100"));
+
+ // Renew case.
+ ASSERT_NO_THROW(client.doRenew());
+ ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+ "2001:db8:1::50",
+ "2001:db8:1::100"));
+
+ // Rebind case.
+ ASSERT_NO_THROW(client.doRebind());
+ ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+ "2001:db8:1::50",
+ "2001:db8:1::100"));
+
+ // Confirm case. This must be before Information-request because the
+ // client must have an address to confirm from one of the transactions
+ // involving address assignment, i.e. Request, Renew or Rebind.
+ ASSERT_NO_THROW(client.doConfirm());
+ ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+ "2001:db8:1::50",
+ "2001:db8:1::100"));
+
+ // Information-request case.
+ ASSERT_NO_THROW(client.doInfRequest());
+ ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+ "2001:db8:1::50",
+ "2001:db8:1::100"));
+}
+
+// Check classification using membership expressions.
+TEST_F(ClassifyTest, member) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"not-foo\", "
+ " \"test\": \"not (option[host-name].text == 'foo')\""
+ "},"
+ "{ \"name\": \"foo\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"not member('not-foo')\""
+ "},"
+ "{ \"name\": \"bar\", "
+ " \"test\": \"option[host-name].text == 'bar'\""
+ "},"
+ "{ \"name\": \"baz\", "
+ " \"test\": \"option[host-name].text == 'baz'\""
+ "},"
+ "{ \"name\": \"barz\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\" } ], "
+ " \"test\": \"member('bar') or member('baz')\" } ] }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->setIndex(ETH1_INDEX);
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->setIndex(ETH1_INDEX);
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->setIndex(ETH1_INDEX);
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+ query3->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname1(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname1);
+ query1->addOption(hostname1);
+ OptionStringPtr hostname3(new OptionString(Option::V6, 1234, "baz"));
+ ASSERT_TRUE(hostname3);
+ query3->addOption(hostname3);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // Check classes
+ EXPECT_FALSE(query1->inClass("not-foo"));
+ EXPECT_TRUE(query1->inClass("foo"));
+ EXPECT_FALSE(query1->inClass("bar"));
+ EXPECT_FALSE(query1->inClass("baz"));
+ EXPECT_FALSE(query1->inClass("barz"));
+
+ EXPECT_TRUE(query2->inClass("not-foo"));
+ EXPECT_FALSE(query2->inClass("foo"));
+ EXPECT_FALSE(query2->inClass("bar"));
+ EXPECT_FALSE(query2->inClass("baz"));
+ EXPECT_FALSE(query2->inClass("barz"));
+
+ EXPECT_TRUE(query3->inClass("not-foo"));
+ EXPECT_FALSE(query3->inClass("foo"));
+ EXPECT_FALSE(query3->inClass("bar"));
+ EXPECT_TRUE(query3->inClass("baz"));
+ EXPECT_TRUE(query3->inClass("barz"));
+
+ // Process queries
+ AllocEngine::ClientContext6 ctx1;
+ bool drop = !srv.earlyGHRLookup(query1, ctx1);
+ ASSERT_FALSE(drop);
+ srv.initContext(query1, ctx1, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response1 = srv.processSolicit(ctx1);
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv.earlyGHRLookup(query2, ctx2);
+ ASSERT_FALSE(drop);
+ srv.initContext(query2, ctx2, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response2 = srv.processSolicit(ctx2);
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv.earlyGHRLookup(query3, ctx3);
+ ASSERT_FALSE(drop);
+ srv.initContext(query3, ctx3, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response3 = srv.processSolicit(ctx3);
+
+ // Classification processing should add an ip-forwarding option
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_TRUE(opt1);
+ OptionCustomPtr ipf1 =
+ boost::dynamic_pointer_cast<OptionCustom>(opt1);
+ ASSERT_TRUE(ipf1);
+ EXPECT_TRUE(ipf1->readBoolean());
+
+ // But not the second query which was not classified
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+
+ // The third has the option but with another value
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_TRUE(opt3);
+ OptionCustomPtr ipf3 =
+ boost::dynamic_pointer_cast<OptionCustom>(opt3);
+ ASSERT_TRUE(ipf3);
+ EXPECT_FALSE(ipf3->readBoolean());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNone) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ EXPECT_FALSE(opt);
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedencePool) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::1", addrs[0].toText());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceSubnet) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNetwork) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"require-client-classes\": [ \"for-network\" ],"
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::3", addrs[0].toText());
+}
+
+// This test checks the complex membership from HA with server1 telephone.
+TEST_F(ClassifyTest, server1Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the first pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server1 computer.
+TEST_F(ClassifyTest, server1Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the second pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:2::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 telephone.
+TEST_F(ClassifyTest, server2Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the third pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:3::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 computer.
+TEST_F(ClassifyTest, server2Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the forth pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:4::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server1 telephone
+// with prefixes.
+TEST_F(ClassifyTest, pDserver1Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the first pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server1 computer
+// with prefix.
+TEST_F(ClassifyTest, pDserver1Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the second pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:2::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 telephone
+// with prefixes.
+TEST_F(ClassifyTest, pDserver2Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the third pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:3::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 computer
+// with prefix.
+TEST_F(ClassifyTest, pDserver2Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the forth pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:4::", lease_client.addr_.toText());
+}
+
+// This test checks the handling for the DROP special class.
+TEST_F(ClassifyTest, dropClass) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:05");
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(CONFIGS[3], *client.getServer()));
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ // No option: no drop.
+ EXPECT_TRUE(client.getContext().response_);
+
+ // Retry with an option matching the DROP class.
+ Dhcp6Client client2;
+
+ // Add the host-name option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ client2.addExtraOption(hostname);
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client2.doSolicit(true));
+
+ // Option, dropped.
+ EXPECT_FALSE(client2.getContext().response_);
+
+ // There should also be pkt6-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt6-receive-drop");
+
+ // This statistic must be present and must be set to 1.
+ ASSERT_TRUE(drop_stat);
+ EXPECT_EQ(1, drop_stat->getInteger().first);
+}
+
+// This test checks the handling for the DROP special class at the host
+// reservation classification point with KNOWN / UNKNOWN.
+TEST_F(ClassifyTest, dropClassUnknown) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:04");
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(CONFIGS[4], *client.getServer()));
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ // Reservation match: no drop.
+ EXPECT_TRUE(client.getContext().response_);
+
+ // Retry with an option matching the DROP class.
+ Dhcp6Client client2;
+
+ // Retry with another DUID.
+ client2.setDUID("01:02:03:05");
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client2.doSolicit(true));
+
+ // No reservation, dropped.
+ EXPECT_FALSE(client2.getContext().response_);
+
+ // There should also be pkt6-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt6-receive-drop");
+
+ // This statistic must be present and must be set to 1.
+ ASSERT_TRUE(drop_stat);
+ EXPECT_EQ(1, drop_stat->getInteger().first);
+}
+
+// This test checks the handling for the DROP special class at the host
+// reservation classification point with a reserved class.
+TEST_F(ClassifyTest, dropClassReservedClass) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:04");
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(CONFIGS[5], *client.getServer()));
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ // Reservation match: no drop.
+ EXPECT_TRUE(client.getContext().response_);
+
+ // Retry with an option matching the DROP class.
+ Dhcp6Client client2;
+
+ // Retry with another DUID.
+ client2.setDUID("01:02:03:05");
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client2.doSolicit(true));
+
+ // No reservation, dropped.
+ EXPECT_FALSE(client2.getContext().response_);
+
+ // There should also be pkt6-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt6-receive-drop");
+
+ // This statistic must be present and must be set to 1.
+ ASSERT_TRUE(drop_stat);
+ EXPECT_EQ(1, drop_stat->getInteger().first);
+}
+
+// This test checks the early global reservations lookup for selecting
+// a guarded subnet.
+TEST_F(ClassifyTest, earlySubnet) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:04");
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(CONFIGS[6], *client.getServer()));
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ // Check response.
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ OptionPtr ia_na = resp->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na);
+ EXPECT_FALSE(ia_na->getOption(D6O_STATUS_CODE));
+ OptionPtr iaaddr = ia_na->getOption(D6O_IAADDR);
+ ASSERT_TRUE(iaaddr);
+ boost::shared_ptr<Option6IAAddr> addr =
+ boost::dynamic_pointer_cast<Option6IAAddr>(iaaddr);
+ ASSERT_TRUE(addr);
+ EXPECT_EQ("2001:db8:1::", addr->getAddress().toText());
+
+ // Retry with another DUID.
+ Dhcp6Client client2;
+ client2.setDUID("01:02:03:05");
+ client2.setInterface("eth1");
+ client2.requestAddress();
+
+ ASSERT_NO_THROW(client2.doSolicit(true));
+
+ // Check response.
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ia_na = resp->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na);
+ EXPECT_FALSE(ia_na->getOption(D6O_STATUS_CODE));
+ iaaddr = ia_na->getOption(D6O_IAADDR);
+ ASSERT_TRUE(iaaddr);
+ addr = boost::dynamic_pointer_cast<Option6IAAddr>(iaaddr);
+ ASSERT_TRUE(addr);
+ EXPECT_EQ("2001:db8:2::", addr->getAddress().toText());
+}
+
+// This test checks the early global reservations lookup for dropping.
+TEST_F(ClassifyTest, earlyDrop) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:04");
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(CONFIGS[7], *client.getServer()));
+
+ // Send a message to the server.
+ ASSERT_NO_THROW(client.doSolicit(true));
+
+ // Match the reservation so dropped.
+ EXPECT_FALSE(client.getContext().response_);
+
+ // There should also be pkt6-receive-drop stat bumped up.
+ stats::StatsMgr& mgr = stats::StatsMgr::instance();
+ stats::ObservationPtr drop_stat = mgr.getObservation("pkt6-receive-drop");
+
+ // This statistic must be present and must be set to 1.
+ ASSERT_TRUE(drop_stat);
+ EXPECT_EQ(1, drop_stat->getInteger().first);
+
+ // Retry with another DUID.
+ Dhcp6Client client2;
+ client2.setDUID("01:02:03:05");
+ client2.setInterface("eth1");
+ client2.requestAddress();
+
+ ASSERT_NO_THROW(client2.doSolicit(true));
+
+ // Not matching so not dropped.
+ EXPECT_TRUE(client2.getContext().response_);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/client_handler_unittest.cc b/src/bin/dhcp6/tests/client_handler_unittest.cc
new file mode 100644
index 0000000..6eed4c4
--- /dev/null
+++ b/src/bin/dhcp6/tests/client_handler_unittest.cc
@@ -0,0 +1,495 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/option.h>
+#include <dhcp6/client_handler.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <stats/stats_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for testing client handler.
+class ClientHandleTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates the pkt6-receive-drop statistic.
+ ClientHandleTest() : called1_(false), called2_(false), called3_(false) {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ StatsMgr::instance().setValue("pkt6-receive-drop", static_cast<int64_t>(0));
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes statistics.
+ ~ClientHandleTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Generates a client-id option.
+ ///
+ /// (from dhcp6_test_utils.h)
+ ///
+ /// @return A random client-id option.
+ OptionPtr generateClientId(uint8_t base = 100) {
+ const size_t len = 32;
+ OptionBuffer duid(len);
+ for (size_t i = 0; i < len; ++i) {
+ duid[i] = base + i;
+ }
+ return (OptionPtr(new Option(Option::V6, D6O_CLIENTID, duid)));
+ }
+
+ /// @brief Check statistics.
+ ///
+ /// @param bumped True if pkt6-receive-drop should have been bumped by one,
+ /// false otherwise.
+ void checkStat(bool bumped) {
+ ObservationPtr obs = StatsMgr::instance().getObservation("pkt6-receive-drop");
+ ASSERT_TRUE(obs);
+ if (bumped) {
+ EXPECT_EQ(1, obs->getInteger().first);
+ } else {
+ EXPECT_EQ(0, obs->getInteger().first);
+ }
+ }
+
+ /// @brief Waits for pending continuations.
+ void waitForThreads() {
+ MultiThreadingMgr::instance().getThreadPool().wait(3);
+ }
+
+ /// @brief Set called1_ to true.
+ void setCalled1() {
+ called1_ = true;
+ }
+
+ /// @brief Set called2_ to true.
+ void setCalled2() {
+ called2_ = true;
+ }
+
+ /// @brief Set called3_ to true.
+ void setCalled3() {
+ called3_ = true;
+ }
+
+ /// @brief The called flag number 1.
+ bool called1_;
+
+ /// @brief The called flag number 2.
+ bool called2_;
+
+ /// @brief The called flag number 3.
+ bool called3_;
+};
+
+// Verifies behavior with empty block.
+TEST_F(ClientHandleTest, empty) {
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior with one query.
+TEST_F(ClientHandleTest, oneQuery) {
+ // Get a query.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->addOption(generateClientId());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior with two queries for the same client.
+TEST_F(ClientHandleTest, sharedQueries) {
+ // Get two queries.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ sol->addOption(client_id);
+ req->addOption(client_id);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the solicit.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(true);
+}
+
+// Verifies behavior with a sequence of two queries.
+TEST_F(ClientHandleTest, sequence) {
+ // Get two queries.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ sol->addOption(client_id);
+ req->addOption(client_id);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the solicit.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // As it is a different block the lock was released.
+
+ try {
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior with different clients.
+TEST_F(ClientHandleTest, notSharedQueries) {
+ // Get two queries.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ OptionPtr client_id2 = generateClientId(111);
+ // Different client ID: different client.
+ sol->addOption(client_id);
+ req->addOption(client_id2);
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the solicit.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies behavior without client ID.
+TEST_F(ClientHandleTest, noClientId) {
+ // Get two queries.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345));
+ // No client id: nothing to recognize the client.
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the solicit.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+ checkStat(false);
+}
+
+// Verifies the query is required.
+TEST_F(ClientHandleTest, noQuery) {
+ Pkt6Ptr no_pkt;
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ EXPECT_THROW(client_handler.tryLock(no_pkt), InvalidParameter);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies that double tryLock call fails.
+TEST_F(ClientHandleTest, doubleTryLock) {
+ // Get a query.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->addOption(generateClientId());
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Try to lock a second time.
+ EXPECT_THROW(client_handler.tryLock(sol), Unexpected);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Cannot verifies that empty client ID fails because getClientId() handles
+// this condition and replaces it by no client ID.
+
+// Verifies behavior with two queries for the same client and multi-threading.
+TEST_F(ClientHandleTest, serializeTwoQueries) {
+ // Get two queries.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ sol->addOption(client_id);
+ req->addOption(client_id);
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Create a continuation.
+ ContinuationPtr cont1 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled1, this));
+
+ // Try to lock it with the solicit.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol, cont1));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Create a continuation.
+ ContinuationPtr cont2 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled2, this));
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req, cont2));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance.
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(false);
+ EXPECT_FALSE(called1_);
+ EXPECT_TRUE(called2_);
+}
+
+// Verifies behavior with two queries for the same client and multi-threading.
+// Continuations are required for serialization.
+TEST_F(ClientHandleTest, serializeNoCont) {
+ // Get two queries.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ sol->addOption(client_id);
+ req->addOption(client_id);
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Try to lock it with the solicit.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance even there is none...
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(true);
+}
+
+// Verifies behavior with three queries for the same client and
+// multi-threading: currently we accept only two queries,
+// a third one replaces second so we get the first (oldest) query and
+// the last (newest) query when the client is busy.
+TEST_F(ClientHandleTest, serializeThreeQueries) {
+ // Get two queries.
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr req(new Pkt6(DHCPV6_REQUEST, 2345));
+ Pkt6Ptr ren(new Pkt6(DHCPV6_RENEW, 3456));
+ OptionPtr client_id = generateClientId();
+ // Same client ID: same client.
+ sol->addOption(client_id);
+ req->addOption(client_id);
+ ren->addOption(client_id);
+
+ // Start multi-threading.
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 1, 0));
+
+ try {
+ // Get a client handler.
+ ClientHandler client_handler;
+
+ // Create a continuation.
+ ContinuationPtr cont1 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled1, this));
+
+ // Try to lock it with the solicit.
+ bool duplicate = false;
+ EXPECT_NO_THROW(duplicate = !client_handler.tryLock(sol, cont1));
+
+ // Should return false (no duplicate).
+ EXPECT_FALSE(duplicate);
+
+ // Get a second client handler.
+ ClientHandler client_handler2;
+
+ // Create a continuation.
+ ContinuationPtr cont2 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled2, this));
+
+ // Try to lock it with a request.
+ EXPECT_NO_THROW(duplicate = !client_handler2.tryLock(req, cont2));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+
+ // Get a third client handler.
+ ClientHandler client_handler3;
+
+ // Create a continuation.
+ ContinuationPtr cont3 =
+ makeContinuation(std::bind(&ClientHandleTest::setCalled3, this));
+
+ // Try to lock it with a renew.
+ EXPECT_NO_THROW(duplicate = !client_handler3.tryLock(ren, cont3));
+
+ // Should return true (race with the duplicate).
+ EXPECT_TRUE(duplicate);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ // Give the second continuation a chance.
+ waitForThreads();
+
+ // Force multi-threading to stop;
+ MultiThreadingCriticalSection cs;
+
+ checkStat(true);
+ EXPECT_FALSE(called1_);
+ EXPECT_FALSE(called2_);
+ EXPECT_TRUE(called3_);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/config_backend_unittest.cc b/src/bin/dhcp6/tests/config_backend_unittest.cc
new file mode 100644
index 0000000..e2d8776
--- /dev/null
+++ b/src/bin/dhcp6/tests/config_backend_unittest.cc
@@ -0,0 +1,547 @@
+// Copyright (C) 2019-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <database/backend_selector.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp6.h>
+
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/get_config_unittest.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <limits.h>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::db;
+using namespace std;
+
+namespace {
+
+/// @brief Test fixture for testing external configuration merging
+class Dhcp6CBTest : public GenericBackendTest {
+protected:
+ /// @brief Pre test set up
+ /// Called prior to each test. It creates two configuration backends
+ /// that differ by host name ("db1" and "db2"). It then registers
+ /// a backend factory that will return them rather than create
+ /// new instances. The backends need to pre-exist so they can be
+ /// populated prior to calling server configure. It uses
+ /// TestConfigBackend instances but with a type of "memfile" to pass
+ /// parsing. Doing it all here allows us to use ASSERTs if we feel like
+ /// it.
+ virtual void SetUp() {
+ DatabaseConnection::ParameterMap params;
+ params[std::string("type")] = std::string("memfile");
+ params[std::string("host")] = std::string("db1");
+ db1_.reset(new TestConfigBackendDHCPv6(params));
+
+ params[std::string("host")] = std::string("db2");
+ db2_.reset(new TestConfigBackendDHCPv6(params));
+
+ ConfigBackendDHCPv6Mgr::instance().registerBackendFactory("memfile",
+ [this](const DatabaseConnection::ParameterMap& params)
+ -> ConfigBackendDHCPv6Ptr {
+ auto host = params.find("host");
+ if (host != params.end()) {
+ if (host->second == "db1") {
+ return (db1_);
+ } else if (host->second == "db2") {
+ return (db2_);
+ }
+ }
+
+ // Apparently we're looking for a new one.
+ return (TestConfigBackendDHCPv6Ptr(new TestConfigBackendDHCPv6(params)));
+ });
+ }
+
+ /// @brief Clean up after each test
+ virtual void TearDown() {
+ // Unregister the factory to be tidy.
+ ConfigBackendDHCPv6Mgr::instance().unregisterBackendFactory("memfile");
+ }
+
+public:
+
+ /// Constructor
+ Dhcp6CBTest()
+ : rcode_(-1), db1_selector("db1"), db2_selector("db1") {
+ // Open port 0 means to not do anything at all. We don't want to
+ // deal with sockets here, just check if configuration handling
+ // is sane.
+ srv_.reset(new ControlledDhcpv6Srv(0));
+
+ // Create fresh context.
+ resetConfiguration();
+ CfgMgr::instance().setFamily(AF_INET6);
+ }
+
+ /// Destructor
+ virtual ~Dhcp6CBTest() {
+ resetConfiguration();
+ };
+
+ /// @brief Reset configuration singletons.
+ void resetConfiguration() {
+ CfgMgr::instance().clear();
+ ConfigBackendDHCPv6Mgr::destroy();
+ }
+
+ /// @brief Convenience method for running configuration
+ ///
+ /// This method does not throw, but signals errors using gtest macros.
+ ///
+ /// @param config text to be parsed as JSON
+ /// @param expected_code expected code (see cc/command_interpreter.h)
+ /// @param exp_error expected text error (check skipped if empty)
+ void configure(std::string config, int expected_code,
+ std::string exp_error = "") {
+ ConstElementPtr json;
+ try {
+ json = parseDHCP6(config, true);
+ } catch(const std::exception& ex) {
+ ADD_FAILURE() << "parseDHCP6 failed: " << ex.what();
+ }
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ ASSERT_TRUE(status);
+
+ int rcode;
+ ConstElementPtr comment = parseAnswerText(rcode, status);
+ ASSERT_EQ(expected_code, rcode) << " comment: "
+ << comment->stringValue();
+
+ string text;
+ ASSERT_NO_THROW(text = comment->stringValue());
+
+ if (expected_code != rcode) {
+ std::cout << "Reported status: " << text << std::endl;
+ }
+
+ if ((rcode != 0)) {
+ if (!exp_error.empty()) {
+ ASSERT_EQ(exp_error, text);
+ }
+ }
+ }
+
+ boost::scoped_ptr<Dhcpv6Srv> srv_; ///< DHCP6 server under test
+ int rcode_; ///< Return code from element parsing
+ ConstElementPtr comment_; ///< Reason for parse fail
+
+ BackendSelector db1_selector; ///< BackendSelector by host for first config backend
+ BackendSelector db2_selector; ///< BackendSelector by host for second config backend
+
+ TestConfigBackendDHCPv6Ptr db1_; ///< First configuration backend instance
+ TestConfigBackendDHCPv6Ptr db2_; ///< Second configuration backend instance
+};
+
+// This test verifies that externally configured globals are
+// merged correctly into staging configuration.
+TEST_F(Dhcp6CBTest, mergeGlobals) {
+ string base_config =
+ "{ \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\" ] \n"
+ " }, \n"
+ " \"decline-probation-period\": 7000, \n"
+ " \"valid-lifetime\": 1000, \n"
+ " \"rebind-timer\": 800, \n"
+ " \"server-tag\": \"first-server\", \n"
+ " \"config-control\": { \n"
+ " \"config-databases\": [ { \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db1\" \n"
+ " },{ \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db2\" \n"
+ " } \n"
+ " ] \n"
+ " } \n"
+ "} \n";
+
+ extractConfig(base_config);
+
+ // Make some globals:
+ StampedValuePtr server_tag(new StampedValue("server-tag", "second-server"));
+ StampedValuePtr decline_period(new StampedValue("decline-probation-period", Element::create(86400)));
+ StampedValuePtr renew_timer(new StampedValue("renew-timer", Element::create(500)));
+
+ // Let's add all of the globals to the second backend. This will verify
+ // we find them there.
+ db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), server_tag);
+ db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), decline_period);
+ db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), renew_timer);
+
+ // Should parse and merge without error.
+ ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
+
+ // Verify the composite staging is correct. (Remember that
+ // CfgMgr::instance().commit() hasn't been called)
+ SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
+
+ // decline-probation-period is an explicit member that should come
+ // from the backend.
+ EXPECT_EQ(86400, staging_cfg->getDeclinePeriod());
+
+ // Verify that the implicit globals from JSON are there.
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, "valid-lifetime",
+ Element::create(1000)));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, "rebind-timer",
+ Element::create(800)));
+
+ // Verify that the implicit globals from the backend are there.
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, server_tag));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, renew_timer));
+}
+
+// This test verifies that externally configured option definitions
+// merged correctly into staging configuration.
+TEST_F(Dhcp6CBTest, mergeOptionDefs) {
+ string base_config =
+ "{ \n"
+ " \"option-def\": [ { \n"
+ " \"name\": \"one\", \n"
+ " \"code\": 1, \n"
+ " \"type\": \"ipv6-address\", \n"
+ " \"space\": \"isc\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"code\": 2, \n"
+ " \"type\": \"string\", \n"
+ " \"space\": \"isc\" \n"
+ " } \n"
+ " ], \n"
+ " \"config-control\": { \n"
+ " \"config-databases\": [ { \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db1\" \n"
+ " },{ \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db2\" \n"
+ " } \n"
+ " ] \n"
+ " } \n"
+ "} \n";
+
+ extractConfig(base_config);
+
+ // Create option one replacement and add it to first backend.
+ OptionDefinitionPtr def;
+ def.reset(new OptionDefinition("one", 101, "isc", "uint16"));
+ db1_->createUpdateOptionDef6(ServerSelector::ALL(), def);
+
+ // Create option three and add it to first backend.
+ def.reset(new OptionDefinition("three", 3, "isc", "string"));
+ db1_->createUpdateOptionDef6(ServerSelector::ALL(), def);
+
+ // Create option four and add it to second backend.
+ def.reset(new OptionDefinition("four", 4, "isc", "string"));
+ db2_->createUpdateOptionDef6(ServerSelector::ALL(), def);
+
+ // Should parse and merge without error.
+ ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
+
+ // Verify the composite staging is correct.
+ SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
+ ConstCfgOptionDefPtr option_defs = staging_cfg->getCfgOptionDef();
+
+ // Definition "one" from first backend should be there.
+ OptionDefinitionPtr found_def = option_defs->get("isc", "one");
+ ASSERT_TRUE(found_def);
+ EXPECT_EQ(101, found_def->getCode());
+ EXPECT_EQ(OptionDataType::OPT_UINT16_TYPE, found_def->getType());
+
+ // Definition "two" from JSON config should be there.
+ found_def = option_defs->get("isc", "two");
+ ASSERT_TRUE(found_def);
+ EXPECT_EQ(2, found_def->getCode());
+
+ // Definition "three" from first backend should be there.
+ found_def = option_defs->get("isc", "three");
+ ASSERT_TRUE(found_def);
+ EXPECT_EQ(3, found_def->getCode());
+
+ // Definition "four" from first backend should not be there.
+ found_def = option_defs->get("isc", "four");
+ ASSERT_FALSE(found_def);
+}
+
+// This test verifies that externally configured options
+// merged correctly into staging configuration.
+TEST_F(Dhcp6CBTest, mergeOptions) {
+ string base_config =
+ "{ \n"
+ " \"option-data\": [ { \n"
+ " \"name\": \"solmax-rt\", \n"
+ " \"data\": \"500\" \n"
+ " },{ \n"
+ " \"name\": \"bootfile-url\", \n"
+ " \"data\": \"orig-boot-file\" \n"
+ " } \n"
+ " ], \n"
+ " \"config-control\": { \n"
+ " \"config-databases\": [ { \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db1\" \n"
+ " },{ \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db2\" \n"
+ " } \n"
+ " ] \n"
+ " } \n"
+ "} \n";
+
+
+ OptionDescriptorPtr opt;
+ // Add solmax-rt to the first backend.
+ opt.reset(new OptionDescriptor(
+ createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+ true, false, false,
+ "updated-boot-file")));
+ opt->space_name_ = DHCP6_OPTION_SPACE;
+ db1_->createUpdateOption6(ServerSelector::ALL(), opt);
+
+ // Add solmax-rt to the second backend.
+ opt.reset(new OptionDescriptor(
+ createOption<OptionUint32>(Option::V6, D6O_SOL_MAX_RT,
+ false, false, true, 700)));
+ opt->space_name_ = DHCP6_OPTION_SPACE;
+ db2_->createUpdateOption6(ServerSelector::ALL(), opt);
+
+ // Should parse and merge without error.
+ ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
+
+ // Now let's verify that composite staging options are correct.
+ SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
+ CfgOptionPtr options = staging_cfg->getCfgOption();
+
+ // bootfile-url should come from the first config back end.
+ // (overwriting the original).
+ OptionDescriptor found_opt =
+ options->get(DHCP6_OPTION_SPACE, D6O_BOOTFILE_URL);
+ ASSERT_TRUE(found_opt.option_);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(found_opt.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("updated-boot-file", opstr->getValue());
+
+ // sol-maxt-rt should come from the original config
+ found_opt = options->get(DHCP6_OPTION_SPACE, D6O_SOL_MAX_RT);
+ ASSERT_TRUE(found_opt.option_);
+ OptionUint32Ptr opint = boost::dynamic_pointer_cast<OptionUint32>(found_opt.option_);
+ ASSERT_TRUE(opint);
+ EXPECT_EQ(500, opint->getValue());
+}
+
+// This test verifies that DHCP options fetched from the config backend
+// encapsulate their suboptions.
+TEST_F(Dhcp6CBTest, mergeOptionsWithSuboptions) {
+ string base_config =
+ "{ \n"
+ " \"option-def\": [ { \n"
+ " \"name\": \"option-1024\", \n"
+ " \"code\": 1024, \n"
+ " \"type\": \"empty\", \n"
+ " \"space\": \"dhcp6\", \n"
+ " \"encapsulate\": \"option-1024-space\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"option-1025\", \n"
+ " \"code\": 1025, \n"
+ " \"type\": \"string\", \n"
+ " \"space\": \"option-1024-space\" \n"
+ " } ], \n"
+ " \"config-control\": { \n"
+ " \"config-databases\": [ { \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db1\" \n"
+ " },{ \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db2\" \n"
+ " } \n"
+ " ] \n"
+ " } \n"
+ "} \n";
+
+ extractConfig(base_config);
+
+ // Create option 1024 instance and store it in the database.
+ OptionDescriptorPtr opt;
+ opt.reset(new OptionDescriptor(
+ createEmptyOption(Option::V6, 1024, true, false)));
+ opt->space_name_ = DHCP6_OPTION_SPACE;
+ db1_->createUpdateOption6(ServerSelector::ALL(), opt);
+
+ // Create option 1024 suboption and store it in the database.
+ opt.reset(new OptionDescriptor(
+ createOption<OptionString>(Option::V6, 1025, true, false, false,
+ "http://server:8080")
+ )
+ );
+ opt->space_name_ = "option-1024-space";
+ db1_->createUpdateOption6(ServerSelector::ALL(), opt);
+
+ // Fetch the configuration from the config backend.
+ ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
+
+ auto staging_cfg = CfgMgr::instance().getStagingCfg();
+
+ // Make sure that option 1024 has been fetched.
+ auto found_opt_desc = staging_cfg->getCfgOption()->get(DHCP6_OPTION_SPACE, 1024);
+ ASSERT_TRUE(found_opt_desc.option_);
+
+ // Make sure that the option 1024 contains its suboption.
+ auto found_subopt = found_opt_desc.option_->getOption(1025);
+ EXPECT_TRUE(found_subopt);
+}
+
+// This test verifies that externally configured shared-networks are
+// merged correctly into staging configuration.
+TEST_F(Dhcp6CBTest, mergeSharedNetworks) {
+ string base_config =
+ "{ \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\" ] \n"
+ " }, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"config-control\": { \n"
+ " \"config-databases\": [ { \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db1\" \n"
+ " },{ \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db2\" \n"
+ " } \n"
+ " ] \n"
+ " }, \n"
+ " \"shared-networks\": [ { \n"
+ " \"name\": \"two\" \n"
+ " }] \n"
+ "} \n";
+
+ extractConfig(base_config);
+
+ // Make a few networks
+ SharedNetwork6Ptr network1(new SharedNetwork6("one"));
+ SharedNetwork6Ptr network3(new SharedNetwork6("three"));
+
+ // Add network1 to db1 and network3 to db2
+ db1_->createUpdateSharedNetwork6(ServerSelector::ALL(), network1);
+ db2_->createUpdateSharedNetwork6(ServerSelector::ALL(), network3);
+
+ // Should parse and merge without error.
+ ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
+
+ // Verify the composite staging is correct. (Remember that
+ // CfgMgr::instance().commit() hasn't been called)
+ SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
+
+ CfgSharedNetworks6Ptr networks = staging_cfg->getCfgSharedNetworks6();
+ SharedNetwork6Ptr staged_network;
+
+ // SharedNetwork One should have been added from db1 config
+ staged_network = networks->getByName("one");
+ ASSERT_TRUE(staged_network);
+
+ // Subnet2 should have come from the json config
+ staged_network = networks->getByName("two");
+ ASSERT_TRUE(staged_network);
+
+ // Subnet3, which is in db2 should not have been merged.
+ // We queried db1 first and the query returned data. In
+ // other words, we iterate over the backends, asking for
+ // data. We use the first data, we find.
+ staged_network = networks->getByName("three");
+ ASSERT_FALSE(staged_network);
+}
+
+// This test verifies that externally configured subnets are
+// merged correctly into staging configuration.
+TEST_F(Dhcp6CBTest, mergeSubnets) {
+ string base_config =
+ "{ \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\" ] \n"
+ " }, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"config-control\": { \n"
+ " \"config-databases\": [ { \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db1\" \n"
+ " },{ \n"
+ " \"type\": \"memfile\", \n"
+ " \"host\": \"db2\" \n"
+ " } \n"
+ " ] \n"
+ " }, \n"
+ " \"subnet6\": [ \n"
+ " { \n"
+ " \"id\": 2,\n"
+ " \"subnet\": \"2001:2::/64\" \n"
+ " } ]\n"
+ "} \n";
+
+ extractConfig(base_config);
+
+ // Make a few subnets
+ auto subnet1 = Subnet6::create(IOAddress("2001:1::"), 64, 1, 2, 100, 100, SubnetID(1));
+ auto subnet3 = Subnet6::create(IOAddress("2001:3::"), 64, 1, 2, 100, 100, SubnetID(3));
+
+ // Add subnet1 to db1 and subnet3 to db2
+ db1_->createUpdateSubnet6(ServerSelector::ALL(), subnet1);
+ db2_->createUpdateSubnet6(ServerSelector::ALL(), subnet3);
+
+ // Should parse and merge without error.
+ configure(base_config, CONTROL_RESULT_SUCCESS, "");
+
+ // Verify the composite staging is correct. (Remember that
+ // CfgMgr::instance().commit() hasn't been called)
+
+ SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
+
+ CfgSubnets6Ptr subnets = staging_cfg->getCfgSubnets6();
+ ConstSubnet6Ptr staged_subnet;
+
+ // Subnet1 should have been added from db1 config
+ staged_subnet = subnets->getBySubnetId(1);
+ ASSERT_TRUE(staged_subnet);
+
+ // Subnet2 should have come from the json config
+ staged_subnet = subnets->getBySubnetId(2);
+ ASSERT_TRUE(staged_subnet);
+
+ // Subnet3, which is in db2 should not have been merged, since it is
+ // first found, first used?
+ staged_subnet = subnets->getBySubnetId(3);
+ ASSERT_FALSE(staged_subnet);
+}
+
+}
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
new file mode 100644
index 0000000..bd8435f
--- /dev/null
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -0,0 +1,8416 @@
+// Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <asiolink/addr_utilities.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp6.h>
+#include <hooks/hooks_manager.h>
+#include <process/config_ctl_info.h>
+#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/log_utils.h>
+#include <testutils/test_to_element.h>
+#include <util/chrono_time_utils.h>
+
+#include "test_data_files_config.h"
+#include "test_libraries.h"
+#include "marker_file.h"
+#include "dhcp6_test_utils.h"
+#include "get_config_unittest.h"
+
+#include <boost/foreach.hpp>
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <arpa/inet.h>
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+const char* PARSER_CONFIGS[] = {
+ // CONFIGURATION 0: one subnet with one pool, no user contexts
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8::/64\" }"
+ " ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8::/32\""
+ " } ]"
+ "}",
+
+ // Configuration 1: one pool with empty user context
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8::/64\","
+ " \"user-context\": {"
+ " }"
+ " }"
+ " ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8::/32\""
+ " } ]"
+ "}",
+
+ // Configuration 2: one pool with user context containing lw4over6 parameters
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8::/64\","
+ " \"user-context\": {"
+ " \"lw4over6-sharing-ratio\": 64,"
+ " \"lw4over6-v4-pool\": \"192.0.2.0/24\","
+ " \"lw4over6-sysports-exclude\": true,"
+ " \"lw4over6-bind-prefix-len\": 56"
+ " }"
+ " }"
+ " ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8::/32\""
+ " } ]"
+ "}",
+
+ // Configuration 3: one min-max pool with user context containing lw4over6 parameters
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ {"
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8:: - 2001:db8::ffff:ffff:ffff:ffff\","
+ " \"user-context\": {"
+ " \"lw4over6-sharing-ratio\": 64,"
+ " \"lw4over6-v4-pool\": \"192.0.2.0/24\","
+ " \"lw4over6-sysports-exclude\": true,"
+ " \"lw4over6-bind-prefix-len\": 56"
+ " }"
+ " }"
+ " ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8::/32\""
+ " } ]"
+ "}",
+
+ // Configuration 4: pd-pool without any user-context
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ {"
+ " \"pd-pools\": [ "
+ " { \"prefix\": \"2001:db8::\","
+ " \"prefix-len\": 56,"
+ " \"delegated-len\": 64 }"
+ " ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8::/32\""
+ " } ]"
+ "}",
+
+ // Configuration 5: pd-pool with empty user-context
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ {"
+ " \"pd-pools\": [ "
+ " { \"prefix\": \"2001:db8::\","
+ " \"prefix-len\": 56,"
+ " \"delegated-len\": 64,"
+ " \"user-context\": { }"
+ " }"
+ " ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8::/32\""
+ " } ]"
+ "}",
+
+ // Configuration 6: pd-pool with user-context with lw4over6 parameters
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ {"
+ " \"pd-pools\": [ "
+ " { \"prefix\": \"2001:db8::\","
+ " \"prefix-len\": 56,"
+ " \"delegated-len\": 64,"
+ " \"user-context\": {"
+ " \"lw4over6-sharing-ratio\": 64,"
+ " \"lw4over6-v4-pool\": \"192.0.2.0/24\","
+ " \"lw4over6-sysports-exclude\": true,"
+ " \"lw4over6-bind-prefix-len\": 56"
+ " }"
+ " }"
+ " ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8::/32\""
+ " } ]"
+ "}",
+
+ // Configuration 7: two host databases
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [\"*\" ]"
+ " },"
+ " \"valid-lifetime\": 4000,"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"hosts-databases\": [ {"
+ " \"type\": \"mysql\","
+ " \"name\": \"keatest1\","
+ " \"user\": \"keatest\","
+ " \"password\": \"keatest\""
+ " },{"
+ " \"type\": \"mysql\","
+ " \"name\": \"keatest2\","
+ " \"user\": \"keatest\","
+ " \"password\": \"keatest\""
+ " }"
+ " ]"
+ "}",
+
+ // Configuration 8: config control
+ "{ \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\" ] \n"
+ " }, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"renew-timer\": 1000, \n"
+ " \"config-control\": { \n"
+ " \"config-fetch-wait-time\": 10, \n"
+ " \"config-databases\": [ { \n"
+ " \"type\": \"mysql\", \n"
+ " \"name\": \"keatest1\", \n"
+ " \"user\": \"keatest\", \n"
+ " \"password\": \"keatest\" \n"
+ " },{ \n"
+ " \"type\": \"mysql\", \n"
+ " \"name\": \"keatest2\", \n"
+ " \"user\": \"keatest\", \n"
+ " \"password\": \"keatest\" \n"
+ " } \n"
+ " ] \n"
+ " } \n"
+ "} \n",
+
+ // Configuration 9 for comments
+ "{"
+ " \"comment\": \"A DHCPv6 server\","
+ " \"server-id\": {"
+ " \"comment\": \"DHCPv6 specific\","
+ " \"type\": \"LL\""
+ " },"
+ " \"interfaces-config\": {"
+ " \"comment\": \"Use wildcard\","
+ " \"interfaces\": [ \"*\" ] },"
+ " \"option-def\": [ {"
+ " \"comment\": \"An option definition\","
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv6-address\","
+ " \"space\": \"isc\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"comment\": \"Set option value\","
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " } ],"
+ " \"client-classes\": ["
+ " {"
+ " \"comment\": \"match all\","
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"none\""
+ " },"
+ " {"
+ " \"comment\": \"a comment\","
+ " \"name\": \"both\","
+ " \"user-context\": {"
+ " \"version\": 1"
+ " }"
+ " }"
+ " ],"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"/tmp/kea6-ctrl-socket\","
+ " \"user-context\": { \"comment\": \"Indirect comment\" }"
+ " },"
+ " \"shared-networks\": [ {"
+ " \"comment\": \"A shared network\","
+ " \"name\": \"foo\","
+ " \"subnet6\": ["
+ " { "
+ " \"comment\": \"A subnet\","
+ " \"subnet\": \"2001:db1::/48\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"comment\": \"A pool\","
+ " \"pool\": \"2001:db1::/64\""
+ " }"
+ " ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"comment\": \"A prefix pool\","
+ " \"prefix\": \"2001:db2::\","
+ " \"prefix-len\": 48,"
+ " \"delegated-len\": 64"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"comment\": \"A host reservation\","
+ " \"hw-address\": \"AA:BB:CC:DD:EE:FF\","
+ " \"hostname\": \"foo.example.com\","
+ " \"option-data\": [ {"
+ " \"comment\": \"An option in a reservation\","
+ " \"name\": \"domain-search\","
+ " \"data\": \"example.com\""
+ " } ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " } ],"
+ " \"dhcp-ddns\": {"
+ " \"comment\": \"No dynamic DNS\","
+ " \"enable-updates\": false"
+ " }"
+ "}"
+};
+
+class Dhcp6ParserTest : public LogContentTest {
+protected:
+ // Check that no hooks libraries are loaded. This is a pre-condition for
+ // a number of tests, so is checked in one place. As this uses an
+ // ASSERT call - and it is not clear from the documentation that Gtest
+ // predicates can be used in a constructor - the check is placed in SetUp.
+ virtual void SetUp() {
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ ASSERT_TRUE(libraries.empty());
+ }
+
+public:
+ Dhcp6ParserTest() :rcode_(-1), srv_(0) {
+ // srv_(0) means to not open any sockets. We don't want to
+ // deal with sockets here, just check if configuration handling
+ // is sane.
+
+ const IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // There must be some interface detected
+ if (ifaces.empty()) {
+ // We can't use ASSERT in constructor
+ ADD_FAILURE() << "No interfaces detected.";
+ }
+
+ valid_iface_ = (*ifaces.begin())->getName();
+ bogus_iface_ = "nonexisting0";
+
+ if (IfaceMgr::instance().getIface(bogus_iface_)) {
+ ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
+ << " while the test assumes that it doesn't, to execute"
+ << " some negative scenarios. Can't continue this test.";
+ }
+
+ // Reset configuration for each test.
+ resetConfiguration();
+ }
+
+ ~Dhcp6ParserTest() {
+ // Reset configuration database after each test.
+ resetConfiguration();
+
+ // ... and delete the hooks library marker files if present
+ static_cast<void>(remove(LOAD_MARKER_FILE));
+ static_cast<void>(remove(UNLOAD_MARKER_FILE));
+ };
+
+ // Checks if config_result (result of DHCP server configuration) has
+ // expected code (0 for success, other for failures).
+ // Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswerText(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_);
+ if (expected_code != rcode_) {
+ cout << "The comment returned was: [" << comment_->stringValue() << "]" << endl;
+ }
+ }
+
+ // Checks if the result of DHCP server configuration has
+ // expected code (0 for success, other for failures) and
+ // the text part. Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code,
+ string expected_txt) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswerText(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_) << "error text:" << comment_->stringValue();
+ ASSERT_TRUE(comment_);
+ ASSERT_EQ(Element::string, comment_->getType());
+ EXPECT_EQ(expected_txt, comment_->stringValue());
+ }
+
+ /// @brief Convenience method for running configuration
+ ///
+ /// This method does not throw, but signals errors using gtest macros.
+ ///
+ /// @param config text to be parsed as JSON
+ /// @param expected_code expected code (see cc/command_interpreter.h)
+ /// @param exp_error expected text error (check skipped if empty)
+ void configure(std::string config, int expected_code,
+ std::string exp_error = "") {
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+
+ int rcode;
+ ConstElementPtr comment = parseAnswerText(rcode, status);
+ EXPECT_EQ(expected_code, rcode);
+
+ string text;
+ ASSERT_TRUE(comment);
+ ASSERT_NO_THROW(text = comment->stringValue());
+
+ if (expected_code != rcode) {
+ std::cout << "Reported status: " << text << std::endl;
+ }
+
+ if ((rcode != 0)) {
+ if (!exp_error.empty()) {
+ EXPECT_EQ(exp_error, text);
+ }
+ }
+ }
+
+ /// @brief Checks if specified subnet is part of the collection
+ ///
+ /// @tparam CollectionType type of subnet6 collections i.e.
+ /// either Subnet6SimpleCollection or Subnet6Collection
+ /// @param col collection of subnets to be inspected
+ /// @param subnet text notation (e.g. 192.0.2.0/24)
+ /// @param t1 expected renew-timer value
+ /// @param t2 expected rebind-timer value
+ /// @param preferred expected preferred-lifetime value
+ /// @param valid expected valid-lifetime value
+ /// @param min_preferred expected min-preferred-lifetime value
+ /// (0 (default) means same as preferred)
+ /// @param max_preferred expected max-preferred-lifetime value
+ /// (0 (default) means same as preferred)
+ /// @param min_valid expected min-valid-lifetime value
+ /// (0 (default) means same as valid)
+ /// @param max_valid expected max-valid-lifetime value
+ /// (0 (default) means same as valid)
+ /// @return the subnet that was examined
+ template <typename CollectionType>
+ Subnet6Ptr
+ checkSubnet(const CollectionType& col, std::string subnet,
+ uint32_t t1, uint32_t t2, uint32_t pref, uint32_t valid,
+ uint32_t min_pref = 0, uint32_t max_pref = 0,
+ uint32_t min_valid = 0, uint32_t max_valid = 0) {
+ const auto& index = col.template get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet);
+ if (subnet_it == index.cend()) {
+ ADD_FAILURE() << "Unable to find expected subnet " << subnet;
+ return (Subnet6Ptr());
+ }
+ Subnet6Ptr s = *subnet_it;
+
+ EXPECT_EQ(t1, s->getT1().get());
+ EXPECT_EQ(t2, s->getT2().get());
+ EXPECT_EQ(pref, s->getPreferred().get());
+ EXPECT_EQ(valid, s->getValid().get());
+ EXPECT_EQ(min_pref ? min_pref : pref, s->getPreferred().getMin());
+ EXPECT_EQ(max_pref ? max_pref : pref, s->getPreferred().getMax());
+ EXPECT_EQ(min_valid ? min_valid : valid, s->getValid().getMin());
+ EXPECT_EQ(max_valid ? max_valid : valid, s->getValid().getMax());
+
+ return (s);
+ }
+
+ /// @brief Returns an interface configuration used by the most of the
+ /// unit tests.
+ std::string genIfaceConfig() const {
+ return ("\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "}");
+ }
+
+ /// @brief Create the simple configuration with single option.
+ ///
+ /// This function allows to set one of the parameters that configure
+ /// option value. These parameters are: "name", "code", "data",
+ /// "csv-format" and "space".
+ ///
+ /// @param param_value string holding option parameter value to be
+ /// injected into the configuration string.
+ /// @param parameter name of the parameter to be configured with
+ /// param value.
+ std::string createConfigWithOption(const std::string& param_value,
+ const std::string& parameter) {
+ std::map<std::string, std::string> params;
+ if (parameter == "name") {
+ params["name"] = param_value;
+ params["space"] = DHCP6_OPTION_SPACE;
+ params["code"] = "38";
+ params["data"] = "ABCDEF0105";
+ params["csv-format"] = "false";
+ } else if (parameter == "space") {
+ params["name"] = "subscriber-id";
+ params["space"] = param_value;
+ params["code"] = "38";
+ params["data"] = "ABCDEF0105";
+ params["csv-format"] = "false";
+ } else if (parameter == "code") {
+ params["name"] = "subscriber-id";
+ params["space"] = DHCP6_OPTION_SPACE;
+ params["code"] = param_value;
+ params["data"] = "ABCDEF0105";
+ params["csv-format"] = "false";
+ } else if (parameter == "data") {
+ params["name"] = "subscriber-id";
+ params["space"] = DHCP6_OPTION_SPACE;
+ params["code"] = "38";
+ params["data"] = param_value;
+ params["csv-format"] = "false";
+ } else if (parameter == "csv-format") {
+ params["name"] = "subscriber-id";
+ params["space"] = DHCP6_OPTION_SPACE;
+ params["code"] = "38";
+ params["data"] = "ABCDEF0105";
+ params["csv-format"] = param_value;
+ }
+ return (createConfigWithOption(params));
+ }
+
+ /// @brief Create simple configuration with single option.
+ ///
+ /// This function creates a configuration for a single option with
+ /// custom values for all parameters that describe the option.
+ ///
+ /// @params params map holding parameters and their values.
+ /// @return configuration string containing custom values of parameters
+ /// describing an option.
+ std::string createConfigWithOption(const std::map<std::string,
+ std::string>& params)
+ {
+ std::ostringstream stream;
+ stream << "{ " << genIfaceConfig() << ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ {"
+ " \"name\": \"bool-option\","
+ " \"code\": 1000,"
+ " \"type\": \"boolean\","
+ " \"space\": \"dhcp6\""
+ "} ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"option-data\": [ {";
+ bool first = true;
+ typedef std::pair<std::string, std::string> ParamPair;
+ BOOST_FOREACH(ParamPair param, params) {
+ if (!first) {
+ stream << ", ";
+ } else {
+ // cppcheck-suppress unreadVariable
+ first = false;
+ }
+ if (param.first == "name") {
+ stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
+ } else if (param.first == "code") {
+ stream << "\"code\": " << param.second;;
+ } else if (param.first == "data") {
+ stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
+ }
+ }
+ stream <<
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+ return (stream.str());
+ }
+
+ /// @brief Returns an option from the subnet.
+ ///
+ /// This function returns an option from a subnet to which the
+ /// specified subnet address belongs. The option is identified
+ /// by its code.
+ ///
+ /// @param subnet_address Address which belongs to the subnet from
+ /// which the option is to be returned.
+ /// @param option_code Code of the option to be returned.
+ /// @param expected_options_count Expected number of options in
+ /// the particular subnet.
+ ///
+ /// @return Descriptor of the option. If the descriptor holds a
+ /// NULL option pointer, it means that there was no such option
+ /// in the subnet.
+ OptionDescriptor
+ getOptionFromSubnet(const IOAddress& subnet_address,
+ const uint16_t option_code,
+ const uint16_t expected_options_count = 1) {
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(subnet_address, classify_);
+ if (!subnet) {
+ ADD_FAILURE() << "A subnet for the specified address "
+ << subnet_address
+ << " does not exist in Config Manager";
+ return (OptionDescriptor(false, false));
+ }
+ OptionContainerPtr options =
+ subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ if (expected_options_count != options->size()) {
+ ADD_FAILURE() << "The number of options in the subnet '"
+ << subnet_address.toText() << "' is different "
+ " than expected number of options '"
+ << expected_options_count << "'";
+ }
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 1) {
+ ADD_FAILURE() << "There is more than one option having the"
+ " option code '" << option_code << "' in a subnet '"
+ << subnet_address.toText() << "'. Expected "
+ " at most one option";
+ } else if (std::distance(range.first, range.second) == 0) {
+ return (OptionDescriptor(OptionPtr(), false, false));
+ }
+
+ return (*range.first);
+ }
+
+ /// @brief Parse and Execute configuration
+ ///
+ /// Parses a configuration and executes a configuration of the server.
+ /// If the operation fails, the current test will register a failure.
+ ///
+ /// @param config Configuration to parse
+ /// @param operation Operation being performed. In the case of an error,
+ /// the error text will include the string "unable to <operation>.".
+ ///
+ /// @return true if the configuration succeeded, false if not. In the
+ /// latter case, a failure will have been added to the current test.
+ bool
+ executeConfiguration(const std::string& config, const char* operation) {
+ ConstElementPtr json;
+ ConstElementPtr status;
+ try {
+ json = parseJSON(config);
+ status = Dhcpv6SrvTest::configure(srv_, json);
+
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The following configuration was used: " << std::endl
+ << config << std::endl
+ << " and the following error message was returned:"
+ << ex.what() << std::endl;
+ return (false);
+ }
+
+ // The status object must not be NULL
+ if (!status) {
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned a null pointer.";
+ return (false);
+ }
+
+ // Store the answer if we need it.
+
+ // Returned value should be 0 (configuration success)
+ comment_ = parseAnswerText(rcode_, status);
+ if (rcode_ != 0) {
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned error code "
+ << rcode_ << reason;
+ return (false);
+ }
+
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by removing all subnets
+ /// option-data, and hooks libraries. The reset must be performed after each
+ /// test to make sure that contents of the database do not affect the
+ /// results of subsequent tests.
+ void resetConfiguration() {
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},"
+ "\"hooks-libraries\": [ ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet6\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ CfgMgr::instance().rollback();
+ static_cast<void>(executeConfiguration(config,
+ "reset configuration database"));
+ // The default setting is to listen on all interfaces. In order to
+ // properly test interface configuration we disable listening on
+ // all interfaces before each test and later check that this setting
+ // has been overridden by the configuration used in the test.
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Retrieve an option associated with a host.
+ ///
+ /// The option is retrieved from the "dhcp6" option space.
+ ///
+ /// @param host Reference to a host for which an option should be retrieved.
+ /// @param option_code Option code.
+ /// @tparam ReturnType Type of the pointer object returned.
+ ///
+ /// @return Pointer to an option or NULL pointer if not found.
+ template<typename ReturnType>
+ ReturnType
+ retrieveOption(const Host& host, const uint16_t option_code) const {
+ return (retrieveOption<ReturnType>(host, DHCP6_OPTION_SPACE, option_code));
+ }
+
+ /// @brief Retrieve an option associated with a host.
+ ///
+ /// @param host Reference to a host for which an option should be retrieved.
+ /// @param space Option space from which option should be retrieved.
+ /// @param option_code Option code.
+ /// @tparam ReturnType Type of the pointer object returned.
+ ///
+ /// @return Pointer to an option or NULL pointer if not found.
+ template<typename ReturnType>
+ ReturnType
+ retrieveOption(const Host& host, const std::string& space,
+ const uint16_t option_code) const {
+ ConstCfgOptionPtr cfg_option = host.getCfgOption6();
+ if (cfg_option) {
+ OptionDescriptor opt_desc = cfg_option->get(space, option_code);
+ if (opt_desc.option_) {
+ return (boost::dynamic_pointer_cast<
+ typename ReturnType::element_type>(opt_desc.option_));
+ }
+ }
+ return (ReturnType());
+ }
+
+ /// @brief Test invalid option parameter value.
+ ///
+ /// This test function constructs the simple configuration
+ /// string and injects invalid option configuration into it.
+ /// It expects that parser will fail with provided option code.
+ ///
+ /// @param param_value string holding invalid option parameter value
+ /// to be injected into configuration string.
+ /// @param parameter name of the parameter to be configured with
+ /// param_value (can be any of "name", "code", "data")
+ void testInvalidOptionParam(const std::string& param_value,
+ const std::string& parameter) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption(param_value, parameter);
+ ConstElementPtr json = parseDHCP6(config);
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 1);
+ EXPECT_TRUE(errorContainsPosition(x, "<string>"));
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Test invalid option parameter value.
+ ///
+ /// This test function constructs the simple configuration
+ /// string and injects invalid option configuration into it.
+ /// It expects that parser will fail with provided option code.
+ ///
+ /// @param params Map of parameters defining an option.
+ void
+ testInvalidOptionParam(const std::map<std::string, std::string>& params) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption(params);
+ ConstElementPtr json = parseDHCP6(config);
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 1);
+ EXPECT_TRUE(errorContainsPosition(x, "<string>"));
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Test option against given code and data.
+ ///
+ /// @param option_desc option descriptor that carries the option to
+ /// be tested.
+ /// @param expected_code expected code of the option.
+ /// @param expected_data expected data in the option.
+ /// @param expected_data_len length of the reference data.
+ /// @param extra_data if true extra data is allowed in an option
+ /// after tested data.
+ void testOption(const OptionDescriptor& option_desc,
+ uint16_t expected_code, const uint8_t* expected_data,
+ size_t expected_data_len,
+ bool extra_data = false) {
+ // Check if option descriptor contains valid option pointer.
+ ASSERT_TRUE(option_desc.option_);
+ // Verify option type.
+ EXPECT_EQ(expected_code, option_desc.option_->getType());
+ // We may have many different option types being created. Some of them
+ // have dedicated classes derived from Option class. In such case if
+ // we want to verify the option contents against expected_data we have
+ // to prepare raw buffer with the contents of the option. The easiest
+ // way is to call pack() which will prepare on-wire data.
+ util::OutputBuffer buf(option_desc.option_->getData().size());
+ option_desc.option_->pack(buf);
+ if (extra_data) {
+ // The length of the buffer must be at least equal to size of the
+ // reference data but it can sometimes be greater than that. This is
+ // because some options carry suboptions that increase the overall
+ // length.
+ ASSERT_GE(buf.getLength() - option_desc.option_->getHeaderLen(),
+ expected_data_len);
+ } else {
+ ASSERT_EQ(buf.getLength() - option_desc.option_->getHeaderLen(),
+ expected_data_len);
+ }
+ // Verify that the data is correct. Do not verify suboptions and a header.
+ const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+ EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option_->getHeaderLen(),
+ expected_data_len));
+ }
+
+ /// @brief Test option configuration.
+ ///
+ /// This function creates a configuration for a specified option using
+ /// a map of parameters specified as the argument. The map holds
+ /// name/value pairs which identifies option's configuration parameters:
+ /// - name
+ /// - space
+ /// - code
+ /// - data
+ /// - csv-format.
+ /// This function applies a new server configuration and checks that the
+ /// option being configured is inserted into CfgMgr. The raw contents of
+ /// this option are compared with the binary data specified as expected
+ /// data passed to this function.
+ ///
+ /// @param params Map of parameters defining an option.
+ /// @param option_code Option code.
+ /// @param expected_data Array containing binary data expected to be stored
+ /// in the configured option.
+ /// @param expected_data_len Length of the array holding reference data.
+ void testConfiguration(const std::map<std::string, std::string>& params,
+ const uint16_t option_code,
+ const uint8_t* expected_data,
+ const size_t expected_data_len) {
+ CfgMgr::instance().clear();
+
+ std::string config = createConfigWithOption(params);
+ ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
+
+ // The subnet should now hold one option with the specified code.
+ OptionDescriptor desc =
+ getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code);
+ ASSERT_TRUE(desc.option_);
+ testOption(desc, option_code, expected_data, expected_data_len);
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Tests the Rapid Commit configuration for a subnet.
+ ///
+ /// This test configures the server with a given configuration and
+ /// verifies if the Rapid Commit has been configured successfully
+ /// for a subnet.
+ ///
+ /// @param config Server configuration, possibly including the
+ /// 'rapid-commit' parameter.
+ /// @param exp_rapid_commit Expected value of the Rapid Commit flag
+ /// within a subnet.
+ void testRapidCommit(const std::string& config,
+ const bool exp_rapid_commit) {
+ // Clear any existing configuration.
+ CfgMgr::instance().clear();
+
+ // Configure the server.
+ ConstElementPtr json = parseDHCP6(config);
+
+ // Make sure that the configuration was successful.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ // Get the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+
+ // Check the Rapid Commit flag for the subnet.
+ EXPECT_EQ(exp_rapid_commit, subnet->getRapidCommit());
+
+ // Clear any existing configuration.
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief This utility method attempts to configure using specified
+ /// config and then returns requested pool from requested subnet
+ ///
+ /// @param config configuration to be applied
+ /// @param subnet_index index of the subnet to be returned (0 - the first subnet)
+ /// @param pool_index index of the pool within a subnet (0 - the first pool)
+ /// @param type Pool type (TYPE_NA or TYPE_PD)
+ /// @param pool [out] Pool pointer will be stored here (if found)
+ void getPool(const std::string& config, size_t subnet_index,
+ size_t pool_index, Lease::Type type, PoolPtr& pool) {
+ ConstElementPtr status;
+ ConstElementPtr json;
+
+ EXPECT_NO_THROW(json = parseDHCP6(config, true));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ ConstCfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ ASSERT_TRUE(subnets6);
+
+ const Subnet6Collection* subnets = subnets6->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_GE(subnets->size(), subnet_index + 1);
+
+ auto subnet = subnets->begin();
+ // std::advance is not available for subnets iterators.
+ for (size_t i = 0; i < subnet_index; ++i) {
+ subnet = std::next(subnet);
+ }
+ const PoolCollection pools = (*subnet)->getPools(type);
+ ASSERT_GE(pools.size(), pool_index + 1);
+
+ pool = pools.at(pool_index);
+ EXPECT_TRUE(pool);
+ }
+
+ /// @brief Tests if the current config has a given global parameter value
+ /// @param name name of the global parameter expected to exist
+ /// @param value expected value of the global parameter
+ template <typename ValueType>
+ void checkGlobal(const std::string name, ValueType value) {
+ ConstElementPtr param;
+ ConstElementPtr exp_value;
+ param = CfgMgr::instance().getStagingCfg()->getConfiguredGlobal(name);
+ ASSERT_TRUE(param) << "global: " << name << ", expected but not found";
+ ASSERT_NO_THROW(exp_value = Element::create(value));
+ EXPECT_TRUE(param->equals(*exp_value)) << "global: " << name
+ << isc::data::prettyPrint(param)
+ << " does not match expected: "
+ << isc::data::prettyPrint(exp_value);
+ }
+
+ int rcode_; ///< Return code (see @ref isc::config::parseAnswer)
+ ControlledDhcpv6Srv srv_; ///< Instance of the ControlledDhcp6Srv used during tests
+ ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
+ string valid_iface_; ///< Valid network interface name (present in system)
+ string bogus_iface_; ///< invalid network interface name (not in system)
+ isc::dhcp::ClientClasses classify_; ///< used in client classification
+};
+
+/// The goal of this test is to verify that the code accepts only
+/// valid commands and malformed or unsupported parameters are rejected.
+TEST_F(Dhcp6ParserTest, bogusCommand) {
+
+ ConstElementPtr x;
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_,
+ parseJSON("{\"bogus\": 5}")));
+
+ // returned value must be 1 (configuration parse error)
+ checkResult(x, 1);
+
+ // it should be refused by syntax too
+ EXPECT_THROW(parseDHCP6("{\"bogus\": 5}"), Dhcp6ParseError);
+}
+
+/// The goal of this test is to verify empty interface-config is accepted.
+TEST_F(Dhcp6ParserTest, emptyInterfaceConfig) {
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP6("{ \"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }"));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+}
+
+/// Check that valid-lifetime must be between min-valid-lifetime and
+/// max-valid-lifetime when a bound is specified, *AND* a subnet is
+/// specified (boundary check is done when lifetimes are applied).
+TEST_F(Dhcp6ParserTest, outBoundValidLifetime) {
+
+ string too_small = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 2000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(too_small));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ string expected = "subnet configuration failed: "
+ "the value of min-valid-lifetime (2000) is not "
+ "less than (default) valid-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string too_large = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"valid-lifetime\": 2000, \"max-valid-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(too_large));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of (default) valid-lifetime (2000) is not "
+ "less than max-valid-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string before = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 2000, "
+ "\"max-valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(before));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of (default) valid-lifetime (1000) is not "
+ "between min-valid-lifetime (2000) and max-valid-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string after = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"valid-lifetime\": 5000, \"min-valid-lifetime\": 1000, "
+ "\"max-valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(after));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of (default) valid-lifetime (5000) is not "
+ "between min-valid-lifetime (1000) and max-valid-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string crossed = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"valid-lifetime\": 1500, \"min-valid-lifetime\": 2000, "
+ "\"max-valid-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(crossed));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of min-valid-lifetime (2000) is not "
+ "less than max-valid-lifetime (1000)";
+ checkResult(status, 1, expected);
+}
+
+/// Check that valid-lifetime must be between min-valid-lifetime and
+/// max-valid-lifetime when a bound is specified. Check on global
+/// parameters only.
+TEST_F(Dhcp6ParserTest, outBoundGlobalValidLifetime) {
+
+ string too_small = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 2000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(too_small));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ string expected =
+ "the value of min-valid-lifetime (2000) is not "
+ "less than (default) valid-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string too_large = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 2000, \"max-valid-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(too_large));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of (default) valid-lifetime (2000) is not "
+ "less than max-valid-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string before = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 1000, \"min-valid-lifetime\": 2000, "
+ "\"max-valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(before));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of (default) valid-lifetime (1000) is not "
+ "between min-valid-lifetime (2000) and max-valid-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string after = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 5000, \"min-valid-lifetime\": 1000, "
+ "\"max-valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(after));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of (default) valid-lifetime (5000) is not "
+ "between min-valid-lifetime (1000) and max-valid-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string crossed = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 1500, \"min-valid-lifetime\": 2000, "
+ "\"max-valid-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(crossed));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of min-valid-lifetime (2000) is not "
+ "less than max-valid-lifetime (1000)";
+ checkResult(status, 1, expected);
+}
+
+/// Check that preferred-lifetime must be between min-preferred-lifetime and
+/// max-preferred-lifetime when a bound is specified, *AND* a subnet is
+/// specified (boundary check is done when lifetimes are applied).
+TEST_F(Dhcp6ParserTest, outBoundPreferredLifetime) {
+
+ string too_small = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"preferred-lifetime\": 1000, \"min-preferred-lifetime\": 2000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(too_small));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ string expected = "subnet configuration failed: "
+ "the value of min-preferred-lifetime (2000) is not "
+ "less than (default) preferred-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string too_large = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"preferred-lifetime\": 2000, \"max-preferred-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(too_large));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of (default) preferred-lifetime (2000) is not "
+ "less than max-preferred-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string before = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"preferred-lifetime\": 1000, \"min-preferred-lifetime\": 2000, "
+ "\"max-preferred-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(before));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of (default) preferred-lifetime (1000) is not between "
+ "min-preferred-lifetime (2000) and max-preferred-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string after = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"preferred-lifetime\": 5000, \"min-preferred-lifetime\": 1000, "
+ "\"max-preferred-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(after));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of (default) preferred-lifetime (5000) is not between "
+ "min-preferred-lifetime (1000) and max-preferred-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string crossed = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/32\" } ],"
+ "\"preferred-lifetime\": 1500, \"min-preferred-lifetime\": 2000, "
+ "\"max-preferred-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(crossed));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected = "subnet configuration failed: "
+ "the value of min-preferred-lifetime (2000) is not "
+ "less than max-preferred-lifetime (1000)";
+ checkResult(status, 1, expected);
+}
+
+/// Check that preferred-lifetime must be between min-preferred-lifetime and
+/// max-preferred-lifetime when a bound is specified. Check on global
+/// parameters only.
+TEST_F(Dhcp6ParserTest, outBoundGlobalPreferredLifetime) {
+
+ string too_small = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 1000, \"min-preferred-lifetime\": 2000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(too_small));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ string expected =
+ "the value of min-preferred-lifetime (2000) is not "
+ "less than (default) preferred-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string too_large = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 2000, \"max-preferred-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(too_large));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of (default) preferred-lifetime (2000) is not "
+ "less than max-preferred-lifetime (1000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string before = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 1000, \"min-preferred-lifetime\": 2000, "
+ "\"max-preferred-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(before));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of (default) preferred-lifetime (1000) is not between "
+ "min-preferred-lifetime (2000) and max-preferred-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string after = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 5000, \"min-preferred-lifetime\": 1000, "
+ "\"max-preferred-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(after));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of (default) preferred-lifetime (5000) is not between "
+ "min-preferred-lifetime (1000) and max-preferred-lifetime (4000)";
+ checkResult(status, 1, expected);
+ resetConfiguration();
+
+ string crossed = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 1500, \"min-preferred-lifetime\": 2000, "
+ "\"max-preferred-lifetime\": 1000 }";
+
+ ASSERT_NO_THROW(json = parseDHCP6(crossed));
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ expected =
+ "the value of min-preferred-lifetime (2000) is not "
+ "less than max-preferred-lifetime (1000)";
+ checkResult(status, 1, expected);
+}
+
+/// The goal of this test is to verify if configuration without any
+/// subnets defined can be accepted.
+TEST_F(Dhcp6ParserTest, emptySubnet) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+}
+
+/// The goal of this test is to verify if defined subnet uses global
+/// parameter timer definitions.
+TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"min-preferred-lifetime\": 2000,"
+ "\"max-preferred-lifetime\": 4000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000,"
+ "\"min-valid-lifetime\": 3000,"
+ "\"max-valid-lifetime\": 5000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Now check if the configuration was indeed handled and we have
+ // expected pool configured.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1().get());
+ EXPECT_EQ(2000, subnet->getT2().get());
+ EXPECT_EQ(3000, subnet->getPreferred().get());
+ EXPECT_EQ(2000, subnet->getPreferred().getMin());
+ EXPECT_EQ(4000, subnet->getPreferred().getMax());
+ EXPECT_EQ(4000, subnet->getValid().get());
+ EXPECT_EQ(3000, subnet->getValid().getMin());
+ EXPECT_EQ(5000, subnet->getValid().getMax());
+
+ // Check that subnet-id is 1
+ EXPECT_EQ(1, subnet->getID());
+}
+
+// This test checks that multiple subnets can be defined and handled properly.
+TEST_F(Dhcp6ParserTest, multipleSubnets) {
+ ConstElementPtr x;
+ // Collection of four subnets for which ids should be autogenerated
+ // - ids are unspecified or set to 0.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"id\": 0"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\" "
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
+ " \"subnet\": \"2001:db8:4::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ int cnt = 0; // Number of reconfigurations
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ do {
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Check subnet-ids of each subnet (it should be monotonously increasing)
+ auto subnet = subnets->begin();
+ EXPECT_EQ(1, (*subnet)->getID());
+ EXPECT_EQ(2, (*++subnet)->getID());
+ EXPECT_EQ(3, (*++subnet)->getID());
+ EXPECT_EQ(4, (*++subnet)->getID());
+
+ // Repeat reconfiguration process 10 times and check that the subnet-id
+ // is set to the same value. Technically, just two iterations would be
+ // sufficient, but it's nice to have a test that exercises reconfiguration
+ // a bit.
+ } while (++cnt < 10);
+}
+
+// This test checks that it is possible to assign arbitrary ids for subnets.
+TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) {
+ ConstElementPtr x;
+ // Four subnets with arbitrary subnet ids.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"id\": 1024"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"id\": 100"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 1"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
+ " \"subnet\": \"2001:db8:4::/64\", "
+ " \"id\": 34"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ int cnt = 0; // Number of reconfigurations
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ do {
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Check that subnet ids are as expected.
+ // Now the subnet order is the subnet id one.
+ auto subnet = subnets->begin();
+ EXPECT_EQ(1, (*subnet)->getID());
+ EXPECT_EQ(34, (*++subnet)->getID());
+ EXPECT_EQ(100, (*++subnet)->getID());
+ EXPECT_EQ(1024, (*++subnet)->getID());
+
+ // Repeat reconfiguration process 10 times and check that the subnet-id
+ // is set to the same value.
+ } while (++cnt < 3);
+}
+
+// Check that the configuration with two subnets having the same ID is rejected.
+TEST_F(Dhcp6ParserTest, multipleSubnetsOverlappingIDs) {
+ ConstElementPtr x;
+ // Four subnets, two of them have the same id.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"id\": 1024"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"id\": 100"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 1024"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
+ " \"subnet\": \"2001:db8:4::/64\", "
+ " \"id\": 34"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 1);
+ EXPECT_TRUE(errorContainsPosition(x, "<string>"));
+}
+
+// Goal of this test is to verify that a previously configured subnet can be
+// deleted in subsequent reconfiguration.
+TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
+ ConstElementPtr x;
+
+ // All four subnets
+ string config4 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"id\": 1"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"id\": 2"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 3"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
+ " \"subnet\": \"2001:db8:4::/64\", "
+ " \"id\": 4"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Three subnets (the last one removed)
+ string config_first3 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"id\": 1"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"id\": 2"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 3"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Second subnet removed
+ string config_second_removed = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"id\": 1"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 3"
+ " },"
+ " {"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
+ " \"subnet\": \"2001:db8:4::/64\", "
+ " \"id\": 4"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // CASE 1: Configure 4 subnets, then reconfigure and remove the
+ // last one.
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config4));
+ extractConfig(config4);
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Do the reconfiguration (the last subnet is removed)
+ ASSERT_NO_THROW(json = parseDHCP6(config_first3));
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)
+
+ auto subnet = subnets->begin();
+ EXPECT_EQ(1, (*subnet)->getID());
+ EXPECT_EQ(2, (*++subnet)->getID());
+ EXPECT_EQ(3, (*++subnet)->getID());
+
+ /// CASE 2: Configure 4 subnets, then reconfigure and remove one
+ /// from in between (not first, not last)
+
+ ASSERT_NO_THROW(json = parseDHCP6(config4));
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ // Do reconfiguration
+ ASSERT_NO_THROW(json = parseDHCP6(config_second_removed));
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ CfgMgr::instance().commit();
+
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 4 subnets
+
+ subnet = subnets->begin();
+ EXPECT_EQ(1, (*subnet)->getID());
+ // The second subnet (with subnet-id = 2) is no longer there
+ EXPECT_EQ(3, (*++subnet)->getID());
+ EXPECT_EQ(4, (*++subnet)->getID());
+}
+
+// Check whether it is possible to configure compatibility flags.
+TEST_F(Dhcp6ParserTest, compatibility) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"compatibility\": { "
+ " \"lenient-option-parsing\": true"
+ "},"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config)) << "bad config: " << config;
+ extractConfig(config);
+
+ // Check defaults: they should be false.
+ EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getLenientOptionParsing());
+
+ // Check the configuration was really applied.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getLenientOptionParsing());
+}
+
+// Check that unknown compatibility flag raises error.
+TEST_F(Dhcp6ParserTest, compatibilityUnknown) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"compatibility\": { "
+ " \"foo-bar\": true"
+ "},"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Syntax is incorrect.
+ EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseJSON(config));
+
+ // Unknown keyword is detected.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ string expected = "unsupported compatibility parameter: ";
+ expected += "foo-bar (<string>:1:154)";
+ checkResult(status, 1, expected);
+}
+
+// Check that not boolean compatibility flag value raises error.
+TEST_F(Dhcp6ParserTest, compatibilityNotBool) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"compatibility\": { "
+ " \"lenient-option-parsing\": 1"
+ "},"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Syntax is incorrect.
+ EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseJSON(config));
+
+ // Bad value type is detected.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ string expected = "compatibility parameter values must be boolean ";
+ expected += "(lenient-option-parsing at <string>:1:169)";
+ checkResult(status, 1, expected);
+}
+
+// This test checks if it is possible to override global values
+// on a per subnet basis.
+TEST_F(Dhcp6ParserTest, subnetLocal) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"min-preferred-lifetime\": 2000,"
+ "\"max-preferred-lifetime\": 4000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"renew-timer\": 1, "
+ " \"rebind-timer\": 2, "
+ " \"preferred-lifetime\": 3,"
+ " \"min-preferred-lifetime\": 2,"
+ " \"max-preferred-lifetime\": 4,"
+ " \"valid-lifetime\": 4,"
+ " \"min-valid-lifetime\": 3,"
+ " \"max-valid-lifetime\": 5,"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000,"
+ "\"min-valid-lifetime\": 3000,"
+ "\"max-valid-lifetime\": 5000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1, subnet->getT1().get());
+ EXPECT_EQ(2, subnet->getT2().get());
+ EXPECT_EQ(3, subnet->getPreferred().get());
+ EXPECT_EQ(2, subnet->getPreferred().getMin());
+ EXPECT_EQ(4, subnet->getPreferred().getMax());
+ EXPECT_EQ(4, subnet->getValid().get());
+ EXPECT_EQ(3, subnet->getValid().getMin());
+ EXPECT_EQ(5, subnet->getValid().getMax());
+}
+
+// This test checks if it is possible to define a subnet with an
+// interface defined.
+TEST_F(Dhcp6ParserTest, subnetInterface) {
+
+ // There should be at least one interface
+ // As far as I can tell, this is the first lambda in Kea code. Cool.
+ auto config = [this](string iface) {
+ return ("{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"interface\": \"" + iface + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }"); };
+ cout << config(valid_iface_) << endl;
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config(valid_iface_)));
+ extractConfig(config("eth0"));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(valid_iface_, subnet->getIface().get());
+}
+
+// This test checks if invalid interface name will be rejected in
+// Subnet6 definition.
+TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
+
+ // There should be at least one interface
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"interface\": \"" + bogus_iface_ + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 1 (configuration error)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ EXPECT_FALSE(subnet);
+}
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceGlobal) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"interface\": \"" + valid_iface_ + "\"," // Not valid. Can be defined in subnet only
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ConstElementPtr json = parseJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 1 (parse error)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
+}
+
+// This test checks if it is possible to define a subnet with an
+// interface-id option defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
+
+ const string valid_interface_id = "foobar";
+ const string bogus_interface_id = "blah";
+
+ // There should be at least one interface
+
+ const string config = "{ "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"interface-id\": \"" + valid_interface_id + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // Returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ // Try to get a subnet based on bogus interface-id option
+ OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
+ SubnetSelector selector;
+ selector.first_relay_linkaddr_ = IOAddress("5000::1");
+ selector.interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(selector);
+ EXPECT_FALSE(subnet);
+
+ // Now try to get subnet for valid interface-id value
+ tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
+ selector.interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(selector);
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(selector.interface_id_->equals(subnet->getInterfaceId()));
+}
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
+
+ const string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"interface-id\": \"foobar\"," // Not valid. Can be defined in subnet only
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json = parseJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // Returned value should be 1 (parse error)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
+}
+
+// This test checks if it is not possible to define a subnet with an
+// interface (i.e. local subnet) and interface-id (remote subnet) defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
+
+ const string config = "{ \"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"interface\": \"" + valid_iface_ + "\","
+ " \"interface-id\": \"foobar\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // Returned value should be 1 (configuration error)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// Goal of this test is to verify that invalid subnet fails to be parsed.
+TEST_F(Dhcp6ParserTest, badSubnetValues) {
+
+ // Contains parts needed for a single test scenario.
+ struct Scenario {
+ std::string description_;
+ std::string config_json_;
+ std::string exp_error_msg_;
+ };
+
+ // Vector of scenarios.
+ std::vector<Scenario> scenarios = {
+ {
+ "IP is not an address",
+ "{ \"subnet6\": [ { "
+ " \"subnet\": \"not an address/64\" } ]}",
+ "subnet configuration failed: "
+ "Failed to convert string to address 'notanaddress': Invalid argument"
+ },
+ {
+ "IP is Invalid",
+ "{ \"subnet6\": [ { "
+ " \"subnet\": \"200175:db8::/64\" } ]}",
+ "subnet configuration failed: "
+ "Failed to convert string to address '200175:db8::': Invalid argument"
+ },
+ {
+ "Missing prefix",
+ "{ \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8::\" } ]}",
+ "subnet configuration failed: "
+ "Invalid subnet syntax (prefix/len expected):2001:db8:: (<string>:1:30)"
+ },
+ {
+ "Prefix not an integer (2 slashes)",
+ "{ \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:://64\" } ]}",
+ "subnet configuration failed: "
+ "prefix length: '/64' is not an integer (<string>:1:30)"
+ },
+ {
+ "Prefix value is insane",
+ "{ \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8::/43225\" } ]}",
+ "subnet configuration failed: "
+ "Invalid prefix length specified for subnet: 43225 (<string>:1:30)"
+ }
+ };
+
+ // Iterate over the list of scenarios. Each should fail to parse with
+ // a specific error message.
+ for (auto scenario = scenarios.begin(); scenario != scenarios.end(); ++scenario) {
+ {
+ SCOPED_TRACE((*scenario).description_);
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6((*scenario).config_json_))
+ << "invalid json, broken test";
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+ checkResult(status, 1);
+ EXPECT_EQ(comment_->stringValue(), (*scenario).exp_error_msg_);
+ }
+ }
+}
+
+// This test checks the configuration of the Rapid Commit option
+// support for the subnet.
+TEST_F(Dhcp6ParserTest, subnetRapidCommit) {
+ {
+ // rapid-commit implicitly set to false.
+ SCOPED_TRACE("Default Rapid Commit setting");
+ testRapidCommit("{ \"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - "
+ "2001:db8:1::ffff\" } ],"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ false);
+ }
+
+ {
+ // rapid-commit explicitly set to true.
+ SCOPED_TRACE("Enable Rapid Commit");
+ testRapidCommit("{ \"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - "
+ "2001:db8:1::ffff\" } ],"
+ " \"rapid-commit\": true,"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ true);
+ }
+
+ {
+ // rapid-commit explicitly set to false.
+ SCOPED_TRACE("Disable Rapid Commit");
+ testRapidCommit("{ \"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - "
+ "2001:db8:1::ffff\" } ],"
+ " \"rapid-commit\": false,"
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }",
+ false);
+ }
+}
+
+// This test checks that multiple pools can be defined and handled properly.
+// The test defines 2 subnets, each with 2 pools.
+TEST_F(Dhcp6ParserTest, multiplePools) {
+ // Collection with two subnets, each with 2 pools.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8:1::/96\" },"
+ " { \"pool\": \"2001:db8:1:0:abcd::/112\" }"
+ " ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " },"
+ " {"
+ " \"id\": 2,"
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8:2::1 - 2001:db8:2::ff\" },"
+ " { \"pool\": \"2001:db8:2::300 - 2001:db8:2::3ff\" }"
+ " ],"
+ " \"subnet\": \"2001:db8:2::/64\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(2, subnets->size()); // We expect 2 subnets
+
+ // Check the first subnet
+ auto subnet = subnets->begin();
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(2, pools1.size());
+ EXPECT_EQ("type=IA_NA, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=128",
+ pools1[0]->toText());
+ EXPECT_EQ("type=IA_NA, 2001:db8:1:0:abcd::-2001:db8:1:0:abcd::ffff, delegated_len=128",
+ pools1[1]->toText());
+ // There shouldn't be any TA or PD pools
+ EXPECT_TRUE((*subnet)->getPools(Lease::TYPE_TA).empty());
+ EXPECT_TRUE((*subnet)->getPools(Lease::TYPE_PD).empty());
+
+ // Check the second subnet
+ ++subnet;
+ const PoolCollection& pools2 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(2, pools2.size());
+ EXPECT_EQ("type=IA_NA, 2001:db8:2::1-2001:db8:2::ff, delegated_len=128",
+ pools2[0]->toText());
+ EXPECT_EQ("type=IA_NA, 2001:db8:2::300-2001:db8:2::3ff, delegated_len=128",
+ pools2[1]->toText());
+ // There shouldn't be any TA or PD pools
+ EXPECT_TRUE((*subnet)->getPools(Lease::TYPE_TA).empty());
+ EXPECT_TRUE((*subnet)->getPools(Lease::TYPE_PD).empty());
+}
+
+// Test verifies that a subnet with pool values that do not belong to that
+// pool are rejected.
+TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"4001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value must be 1 (values error)
+ // as the pool does not belong to that subnet
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// Goal of this test is to verify if pools can be defined
+// using prefix/length notation. There is no separate test for min-max
+// notation as it was tested in several previous tests.
+// Note this test also verifies that subnets can be configured without
+// prefix delegation pools.
+TEST_F(Dhcp6ParserTest, poolPrefixLen) {
+
+ ConstElementPtr x;
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value must be 1 (configuration parse error)
+ checkResult(x, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1().get());
+ EXPECT_EQ(2000, subnet->getT2().get());
+ EXPECT_EQ(3000, subnet->getPreferred().get());
+ EXPECT_EQ(4000, subnet->getValid().get());
+}
+
+// Goal of this test is to verify if invalid pool definitions
+// return a location in the error message.
+TEST_F(Dhcp6ParserTest, badPools) {
+
+ // not a prefix
+ string config_bogus1 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"foo/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // not a length
+ string config_bogus2 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/foo\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // invalid prefix length
+ string config_bogus3 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/200\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // not a prefix nor a min-max
+ string config_bogus4 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"foo\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // not an address
+ string config_bogus5 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"foo - bar\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // min > max
+ string config_bogus6 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::ffff - 2001:db8:1::\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // out of range prefix length (new check)
+ string config_bogus7 = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/1104\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json1;
+ ASSERT_NO_THROW(json1 = parseDHCP6(config_bogus1));
+ ConstElementPtr json2;
+ ASSERT_NO_THROW(json2 = parseDHCP6(config_bogus2));
+ ConstElementPtr json3;
+ ASSERT_NO_THROW(json3 = parseDHCP6(config_bogus3));
+ ConstElementPtr json4;
+ ASSERT_NO_THROW(json4 = parseDHCP6(config_bogus4));
+ ConstElementPtr json5;
+ ASSERT_NO_THROW(json5 = parseDHCP6(config_bogus5));
+ ConstElementPtr json6;
+ ASSERT_NO_THROW(json6 = parseDHCP6(config_bogus6));
+ ConstElementPtr json7;
+ ASSERT_NO_THROW(json7 = parseDHCP6(config_bogus7));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json1));
+
+ // check if returned status is always a failure
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json2));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json3));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json4));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json5));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json6));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json7));
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// Goal of this test is to verify the basic parsing of a prefix delegation
+// pool. It uses a single, valid pd pool.
+TEST_F(Dhcp6ParserTest, pdPoolBasics) {
+
+ ConstElementPtr x;
+
+ // Define a single valid pd pool.
+ string config =
+ "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 64, "
+ " \"delegated-len\": 128"
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }";
+
+ // Convert the JSON string into Elements.
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ // Verify that DHCP6 configuration processing succeeds.
+ // Returned value must be non-empty ConstElementPtr to config result.
+ // rcode should be 0 which indicates successful configuration processing.
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // Test that we can retrieve the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+
+ // Fetch the collection of PD pools. It should have 1 entry.
+ PoolCollection pc;
+ ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+ EXPECT_EQ(1, pc.size());
+
+ // Get a pointer to the pd pool instance, and verify its contents.
+ Pool6Ptr p6;
+ ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[0]));
+ ASSERT_TRUE(p6);
+ EXPECT_EQ("2001:db8:1::", p6->getFirstAddress().toText());
+ EXPECT_EQ(128, p6->getLength());
+
+ // prefix-len is not directly accessible after pool construction, so
+ // verify that it was interpreted correctly by checking the last address
+ // value.
+ isc::asiolink::IOAddress prefixAddress("2001:db8:1::");
+ EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
+}
+
+// This test verifies that it is possible to specify a prefix pool with an
+// excluded prefix (see RFC6603).
+TEST_F(Dhcp6ParserTest, pdPoolPrefixExclude) {
+
+ ConstElementPtr x;
+
+ // Define a single valid pd pool.
+ string config =
+ "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64,"
+ " \"excluded-prefix\": \"3000:0:0:0:1000::\","
+ " \"excluded-prefix-len\": 72"
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }";
+
+ // Convert the JSON string into Elements.
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ // Verify that DHCP6 configuration processing succeeds.
+ // Returned value must be non-empty ConstElementPtr to config result.
+ // rcode should be 0 which indicates successful configuration processing.
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // Test that we can retrieve the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+
+ // Fetch the collection of PD pools. It should have 1 entry.
+ PoolCollection pc;
+ ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+ EXPECT_EQ(1, pc.size());
+
+ // Get a pointer to the pd pool instance, and verify its contents.
+ Pool6Ptr p6;
+ ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[0]));
+ ASSERT_TRUE(p6);
+ EXPECT_EQ("3000::", p6->getFirstAddress().toText());
+ EXPECT_EQ(64, p6->getLength());
+
+ // This pool should have Prefix Exclude option associated.
+ Option6PDExcludePtr pd_exclude_option = p6->getPrefixExcludeOption();
+ ASSERT_TRUE(pd_exclude_option);
+
+ // Pick a delegated prefix of 3000:0:0:3:1000::/64 which belongs to our
+ // pool of 3000::/48. For this prefix obtain a Prefix Exclude option and
+ // verify that it is correct.
+ EXPECT_EQ("3000:0:0:3:1000::",
+ pd_exclude_option->getExcludedPrefix(IOAddress("3000:0:0:3::"), 64).toText());
+ EXPECT_EQ(72, static_cast<unsigned>(pd_exclude_option->getExcludedPrefixLength()));
+}
+
+// Goal of this test is verify that a list of PD pools can be configured.
+// It also verifies that a subnet may be configured with both regular pools
+// and pd pools.
+TEST_F(Dhcp6ParserTest, pdPoolList) {
+
+ ConstElementPtr x;
+
+ // We will configure three pools of prefixes for the subnet. Note that
+ // the 3rd prefix is out of the subnet prefix (the prefix doesn't match
+ // the subnet prefix).
+ const char* prefixes[] = {
+ "2001:db8:1:1::",
+ "2001:db8:1:2::",
+ "3000:1:3::"
+ };
+
+ string config =
+ "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1:04::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/40\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:1:01::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " },"
+ " { \"prefix\": \"2001:db8:1:02::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 88"
+ " },"
+ " { \"prefix\": \"3000:1:03::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 96"
+ " }"
+ "],"
+ "\"valid-lifetime\": 4000 }"
+ "] }";
+
+ // Convert the JSON string into Elements.
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ // Verify that DHCP6 configuration processing succeeds.
+ // Returned value must be non-empty ConstElementPtr to config result.
+ // rcode should be 0 which indicates successful configuration processing.
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // Test that we can retrieve the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+
+ // Fetch the collection of NA pools. It should have 1 entry.
+ PoolCollection pc;
+ ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_NA));
+ EXPECT_EQ(1, pc.size());
+
+ // Fetch the collection of PD pools. It should have 3 entries.
+ ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+ EXPECT_EQ(3, pc.size());
+
+ // Loop through the pools and verify their contents.
+ for (unsigned int i = 0; i < 3; i++) {
+ Pool6Ptr p6;
+ ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[i]));
+ ASSERT_TRUE(p6);
+ EXPECT_EQ(prefixes[i], p6->getFirstAddress().toText());
+ EXPECT_EQ((80 + (i * 8)), p6->getLength());
+ }
+}
+
+// Goal of this test is to verify that a whole prefix can be delegated and that
+// a whole subnet can be delegated.
+TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
+
+ ConstElementPtr x;
+
+ // Define a single valid pd pool.
+ string config =
+ "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 64, "
+ " \"delegated-len\": 64"
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }";
+
+ // Convert the JSON string into Elements.
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ // Verify that DHCP6 configuration processing succeeds.
+ // Returned value must be non-empty ConstElementPtr to config result.
+ // rcode should be 0 which indicates successful configuration processing.
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // Test that we can retrieve the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+
+ ASSERT_TRUE(subnet);
+
+ // Fetch the collection of PD pools. It should have 1 entry.
+ PoolCollection pc;
+ ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
+ EXPECT_EQ(1, pc.size());
+
+ // Get a pointer to the pd pool instance, and verify its contents.
+ Pool6Ptr p6;
+ ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[0]));
+ ASSERT_TRUE(p6);
+ EXPECT_EQ("2001:db8:1::", p6->getFirstAddress().toText());
+ EXPECT_EQ(64, p6->getLength());
+
+ // prefix-len is not directly accessible after pool construction, so
+ // verify that it was interpreted correctly by checking the last address
+ // value.
+ isc::asiolink::IOAddress prefixAddress("2001:db8:1::");
+ EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
+}
+
+// Goal of this test is check for proper handling of invalid prefix delegation
+// pool configuration. It uses an array of invalid configurations to check
+// a variety of configuration errors.
+TEST_F(Dhcp6ParserTest, invalidPdPools) {
+
+ ConstElementPtr x;
+
+ const char *config[] = {
+ // No prefix.
+ "{ \"interfaces-config\": { \"interfaces\": [ ] },"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { "
+ " \"prefix-len\": 64, "
+ " \"delegated-len\": 128"
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }",
+ // No prefix-len.
+ "{ \"interfaces-config\": { \"interfaces\": [ ] },"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:1::\", "
+ " \"delegated-len\": 128"
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }",
+ // No delegated-len.
+ "{ \"interfaces-config\": { \"interfaces\": [ ] },"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 64 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }",
+ // Delegated length is too short.
+ "{ \"interfaces-config\": { \"interfaces\": [ ] },"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 128, "
+ " \"delegated-len\": 64"
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "] }"
+ };
+
+ ConstElementPtr json;
+ int num_msgs = sizeof(config)/sizeof(char*);
+ for (unsigned int i = 0; i < num_msgs; i++) {
+ // Convert JSON string to Elements.
+ // The 3 first configs should fail to parse.
+ if (i < 3) {
+ EXPECT_THROW(parseDHCP6(config[i]), Dhcp6ParseError);
+ json = parseJSON(config[i]);
+ } else {
+ ASSERT_NO_THROW(json = parseDHCP6(config[i]));
+ }
+
+ // Configuration processing should fail without a throw.
+ ASSERT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+
+ // Returned value must be non-empty ConstElementPtr to config result.
+ // rcode should be 1 which indicates configuration error.
+ checkResult(x, 1);
+ EXPECT_TRUE(errorContainsPosition(x, "<string>"));
+ }
+}
+
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv6 address can be created.
+TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv6-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+ extractConfig(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // We need to commit option definitions because later in this test we
+ // will be checking if they get removed when "option-def" parameter
+ // is removed from a configuration.
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+
+ // The copy of the option definition should be available in the libdhcp++.
+ OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100);
+ ASSERT_TRUE(def_libdhcp);
+
+ // Both definitions should be held in distinct pointers but they should
+ // be equal.
+ EXPECT_TRUE(def_libdhcp != def);
+ EXPECT_TRUE(*def_libdhcp == *def);
+
+ // Let's apply empty configuration. This removes the option definitions
+ // configuration and should result in removal of the option 100 from the
+ // libdhcp++. Note DHCP6 or OPTION_DEFS parsers do not accept empty maps.
+ json.reset(new MapElement());
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100));
+}
+
+// The goal of this test is to check whether an option definition
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp6ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+ extractConfig(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp6ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+ extractConfig(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
+ // Preconfigure libdhcp++ with option definitions. The new configuration
+ // should override it, but when the new configuration fails, it should
+ // revert to this original configuration.
+ OptionDefSpaceContainer defs;
+ OptionDefinitionPtr def(new OptionDefinition("bar", 233, "isc", "string"));
+ defs.addItem(def);
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ // Specific check for incorrect report using default config pair
+ // as option-def is parsed first.
+ string expected = "failed to create or run parser for configuration ";
+ expected += "element option-def: option definition with code '100' ";
+ expected += "already exists in option space 'isc'";
+ EXPECT_EQ(1, countFile(expected));
+
+ // The new configuration should have inserted option 100, but
+ // once configuration failed (on the duplicate option definition)
+ // the original configuration in libdhcp++ should be reverted.
+ EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100));
+ def = LibDHCP::getRuntimeOptionDef("isc", 233);
+ ASSERT_TRUE(def);
+ EXPECT_EQ("bar", def->getName());
+ EXPECT_EQ(233, def->getCode());
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp6ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": true,"
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+ extractConfig(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+// The purpose of this test to verify that encapsulated option
+// space name may be specified.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulate) {
+
+ // Configuration string. Included the encapsulated
+ // option space name.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"sub-opts-space\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+ extractConfig(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ("sub-opts-space", def->getEncapsulatedSpace());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+/// The purpose of this test is to verify that various integer types
+/// are supported.
+TEST_F(Dhcp6ParserTest, optionIntegerTypes) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"record-types\": \"uint8,uint16,uint32,int8,int16,int32\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 0);
+}
+
+/// The goal of this test is to verify that the invalid encapsulated
+/// option space name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidEncapsulatedSpace) {
+ // Configuration string. The encapsulated option space
+ // name is invalid (% character is not allowed).
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"invalid%space%name\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+/// The goal of this test is to verify that the encapsulated
+/// option space name can't be specified for the option that
+/// comprises an array of data fields.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulatedSpaceAndArray) {
+ // Configuration string. The encapsulated option space
+ // name is set to non-empty value and the array flag
+ // is set.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": true,"
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"valid-space-name\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+/// The goal of this test is to verify that the option may not
+/// encapsulate option space it belongs to.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) {
+ // Configuration string. Option is set to encapsulate
+ // option space it belongs to.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"isc\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp6 option
+/// space and has its definition) and that it is allowed to define
+/// option in the dhcp6 option space that has a code which is not
+/// used by any of the standard options.
+TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 100 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp6 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseOPTION_DEFS(config));
+
+ OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is invalid. The 'dhcp6'
+ // option space groups standard options and the code 3 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 3,"
+ " \"type\": \"string\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ json = parseOPTION_DEFS(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ /// @todo The option 63 is a standard DHCPv6 option. However, at this point
+ /// there is no definition for this option in libdhcp++, so it should be
+ /// allowed to define it from the configuration interface. This test will
+ /// have to be removed once definitions for remaining standard options are
+ /// created.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"geolocation\","
+ " \"code\": 63,"
+ " \"type\": \"string\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ json = parseOPTION_DEFS(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting success.
+ checkResult(status, 0);
+
+ def = CfgMgr::instance().getStagingCfg()->
+ getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 63);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("geolocation", def->getName());
+ EXPECT_EQ(63, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+}
+
+// Goal of this test is to verify that global option data is configured
+TEST_F(Dhcp6ParserTest, optionDataDefaultsGlobal) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"preference\","
+ " \"data\": \"01\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // These options are global
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(0, options->size());
+
+ options = CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(2, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t subid_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
+
+ range = idx.equal_range(D6O_PREFERENCE);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t pref_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
+
+ // Check that options with other option codes are not returned.
+ for (uint16_t code = 47; code < 57; ++code) {
+ range = idx.equal_range(code);
+ EXPECT_EQ(0, std::distance(range.first, range.second));
+ }
+}
+
+// Goal of this test is to verify that subnet option data is configured
+TEST_F(Dhcp6ParserTest, optionDataDefaultsSubnet) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"preference\","
+ " \"data\": \"01\""
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // These options are subnet options
+ OptionContainerPtr options =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(0, options->size());
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(2, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t subid_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
+
+ range = idx.equal_range(D6O_PREFERENCE);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t pref_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
+
+ // Check that options with other option codes are not returned.
+ for (uint16_t code = 47; code < 57; ++code) {
+ range = idx.equal_range(code);
+ EXPECT_EQ(0, std::distance(range.first, range.second));
+ }
+}
+
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp6' option space as it is the
+ // standard option.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"data\": \"1234\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 38,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now available
+ // Try to get the option from the space dhcp6.
+ OptionDescriptor desc1 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP6_OPTION_SPACE, 38);
+ ASSERT_TRUE(desc1.option_);
+ EXPECT_EQ(38, desc1.option_->getType());
+ // Try to get the option from the space isc.
+ OptionDescriptor desc2 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get("isc", 38);
+ ASSERT_TRUE(desc2.option_);
+ EXPECT_EQ(38, desc1.option_->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ OptionDescriptor desc3 = CfgMgr::instance().getStagingCfg()->
+ getCfgOption()->get("non-existing", 38);
+ ASSERT_FALSE(desc3.option_);
+}
+
+// The goal of this test is to verify that it is possible to
+// encapsulate option space containing some options with
+// another option. In this test we create base option that
+// encapsulates option space 'isc' that comprises two other
+// options. Also, for all options their definitions are
+// created.
+TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
+
+ // @todo DHCP configurations has many dependencies between
+ // parameters. First of all, configuration for subnet is
+ // inherited from the global values. Thus subnet has to be
+ // configured when all global values have been configured.
+ // Also, an option can encapsulate another option only
+ // if the latter has been configured. For this reason in this
+ // test we created two-stage configuration where first we
+ // created options that belong to encapsulated option space.
+ // In the second stage we add the base option. Also, the Subnet
+ // object is configured in the second stage so it is created
+ // at the very end (when all other parameters are configured).
+
+ // Starting stage 1. Configure sub-options and their definitions.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"isc\","
+ " \"data\": \"192.168.2.1\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ CfgMgr::instance().clear();
+
+ // Stage 2. Configure base option and a subnet. Please note that
+ // the configuration from the stage 2 is repeated because BIND
+ // configuration manager sends whole configuration for the lists
+ // where at least one element is being modified or added.
+ config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"base-option\","
+ " \"data\": \"11\""
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"isc\","
+ " \"data\": \"192.168.2.1\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"base-option\","
+ " \"code\": 100,"
+ " \"type\": \"uint8\","
+ " \"space\": \"dhcp6\","
+ " \"encapsulate\": \"isc\""
+ "},"
+ "{"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // We should have one option available.
+ OptionContainerPtr options =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the option.
+ OptionDescriptor desc =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(DHCP6_OPTION_SPACE, 100);
+ EXPECT_TRUE(desc.option_);
+ EXPECT_EQ(100, desc.option_->getType());
+
+ // This option should comprise two sub-options.
+ // Onf of them is 'foo' with code 110.
+ OptionPtr option_foo = desc.option_->getOption(110);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(110, option_foo->getType());
+
+ // ...another one 'foo2' with code 111.
+ OptionPtr option_foo2 = desc.option_->getOption(111);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(111, option_foo2->getType());
+}
+
+// Goal of this test is to verify options configuration
+// for multiple subnets.
+TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": false"
+ " } ]"
+ " },"
+ " {"
+ " \"id\": 2,"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"option-data\": [ {"
+ " \"name\": \"user-class\","
+ " \"data\": \"FFFEFDFCFB\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ Subnet6Ptr subnet1 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet1);
+ OptionContainerPtr options1 = subnet1->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options1->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx1 = options1->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range1 =
+ idx1.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
+ ASSERT_EQ(1, std::distance(range1.first, range1.second));
+ const uint8_t subid_expected[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0A
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range1.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
+
+ // Test another subnet in the same way.
+ Subnet6Ptr subnet2 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:2::4"), classify_);
+ ASSERT_TRUE(subnet2);
+ OptionContainerPtr options2 = subnet2->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options2->size());
+
+ const OptionContainerTypeIndex& idx2 = options2->get<1>();
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range2 =
+ idx2.equal_range(D6O_USER_CLASS);
+ ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+ const uint8_t user_class_expected[] = {
+ 0xFF, 0xFE, 0xFD, 0xFC, 0xFB
+ };
+ testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+ sizeof(user_class_expected));
+}
+
+// This test verifies that it is possible to specify options on
+// pool levels.
+TEST_F(Dhcp6ParserTest, optionDataMultiplePools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::10 - 2001:db8:1::100\","
+ " \"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": false"
+ " } ]"
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::300 - 2001:db8:1::400\","
+ " \"option-data\": [ {"
+ " \"name\": \"user-class\","
+ " \"data\": \"FFFEFDFCFB\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ " \"pd-pools\": [ { "
+ " \"prefix\": \"3000::\","
+ " \"prefix-len\": 48,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"112233445566\","
+ " \"csv-format\": false"
+ " } ]"
+ " },"
+ " {"
+ " \"prefix\": \"3001::\","
+ " \"prefix-len\": 48,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"user-class\","
+ " \"data\": \"aabbccddee\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+
+ PoolPtr pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3000::"), false);
+ ASSERT_TRUE(pool);
+ Pool6Ptr pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ ASSERT_TRUE(pool6);
+
+ OptionContainerPtr options1 =
+ pool6->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options1->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx1 = options1->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range1 =
+ idx1.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect a single Subscriber ID option instance.
+ ASSERT_EQ(1, std::distance(range1.first, range1.second));
+ const uint8_t subscriber_id_expected[] = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range1.first, D6O_SUBSCRIBER_ID, subscriber_id_expected,
+ sizeof(subscriber_id_expected));
+
+ // Test another pool in the same way.
+ pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3001::"), false);
+ ASSERT_TRUE(pool);
+ pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ ASSERT_TRUE(pool6);
+
+ OptionContainerPtr options2 =
+ pool6->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options2->size());
+
+ const OptionContainerTypeIndex& idx2 = options2->get<1>();
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range2 =
+ idx2.equal_range(D6O_USER_CLASS);
+ ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+ const uint8_t user_class_expected[] = {
+ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE
+ };
+ testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+ sizeof(user_class_expected));
+
+ // Test options in NA pools.
+ pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::10"));
+ ASSERT_TRUE(pool);
+ pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ ASSERT_TRUE(pool6);
+
+ OptionContainerPtr options3 =
+ pool6->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options3->size());
+
+ const OptionContainerTypeIndex& idx3 = options3->get<1>();
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range3 =
+ idx3.equal_range(D6O_SUBSCRIBER_ID);
+ ASSERT_EQ(1, std::distance(range3.first, range3.second));
+
+ const uint8_t subscriber_id_expected2[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A
+ };
+ testOption(*range3.first, D6O_SUBSCRIBER_ID, subscriber_id_expected2,
+ sizeof(subscriber_id_expected2));
+
+ pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::300"));
+ ASSERT_TRUE(pool);
+ pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ ASSERT_TRUE(pool6);
+
+ OptionContainerPtr options4 =
+ pool6->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options4->size());
+
+ const OptionContainerTypeIndex& idx4 = options4->get<1>();
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range4 =
+ idx4.equal_range(D6O_USER_CLASS);
+ ASSERT_EQ(1, std::distance(range4.first, range4.second));
+
+ const uint8_t user_class_expected2[] = {
+ 0xFF, 0xFE, 0xFD, 0xFC, 0xFB
+ };
+ testOption(*range4.first, D6O_USER_CLASS, user_class_expected2,
+ sizeof(user_class_expected2));
+}
+
+// The goal of this test is to check that the option carrying a boolean
+// value can be configured using one of the values: "true", "false", "0"
+// or "1".
+TEST_F(Dhcp6ParserTest, optionDataBoolean) {
+ // Create configuration. Use standard option 1000.
+ std::map<std::string, std::string> params;
+ params["name"] = "bool-option";
+ params["space"] = DHCP6_OPTION_SPACE;
+ params["code"] = "1000";
+ params["data"] = "true";
+ params["csv-format"] = "true";
+
+ std::string config = createConfigWithOption(params);
+ ASSERT_TRUE(executeConfiguration(config, "parse configuration with a"
+ " boolean value"));
+
+ // The subnet should now hold one option with the code 1000.
+ OptionDescriptor desc =
+ getOptionFromSubnet(IOAddress("2001:db8:1::5"), 1000);
+ ASSERT_TRUE(desc.option_);
+
+ // This option should be set to "true", represented as 0x1 in the option
+ // buffer.
+ uint8_t expected_option_data[] = {
+ 0x1
+ };
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Configure the option with the "1" value. This should have the same
+ // effect as if "true" was specified.
+ params["data"] = "1";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The value of "1" with a few leading zeros should work too.
+ params["data"] = "00001";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Configure the option with the "false" value.
+ params["data"] = "false";
+ // The option buffer should now hold the value of 0.
+ expected_option_data[0] = 0;
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Specifying "0" should have the same effect as "false".
+ params["data"] = "0";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The same effect should be for multiple 0 chars.
+ params["data"] = "00000";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Bogus values should not be accepted.
+ params["data"] = "bogus";
+ testInvalidOptionParam(params);
+
+ params["data"] = "2";
+ testInvalidOptionParam(params);
+
+ // Now let's test that it is possible to use binary format.
+ params["data"] = "0";
+ params["csv-format"] = "false";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The binary 1 should work as well.
+ params["data"] = "1";
+ expected_option_data[0] = 1;
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // As well as an even number of digits.
+ params["data"] = "01";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+}
+
+// Verify that empty option name is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionNameEmpty) {
+ // Empty option names not allowed.
+ testInvalidOptionParam("", "name");
+}
+
+// Verify that empty option name with spaces is rejected
+// in the configuration.
+TEST_F(Dhcp6ParserTest, optionNameSpaces) {
+ // Spaces in option names not allowed.
+ testInvalidOptionParam("option foo", "name");
+}
+
+// Verify that negative option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeNegative) {
+ // Check negative option code -4. This should fail too.
+ testInvalidOptionParam("-4", "code");
+}
+
+// Verify that out of bounds option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeNonUint16) {
+ // The valid option codes are uint16_t values so passing
+ // uint16_t maximum value incremented by 1 should result
+ // in failure.
+ testInvalidOptionParam("65536", "code");
+}
+
+// Verify that out of bounds option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeHighNonUint16) {
+ // Another check for uint16_t overflow but this time
+ // let's pass even greater option code value.
+ testInvalidOptionParam("70000", "code");
+}
+
+// Verify that zero option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeZero) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("0", "code");
+}
+
+// Verify that invalid hex literals for option data are detected.
+TEST_F(Dhcp6ParserTest, optionDataInvalidHexLiterals) {
+ testInvalidOptionParam("01020R", "data"); // non hex digit
+ testInvalidOptionParam("0x01:02", "data"); // 0x prefix with colon separator
+ testInvalidOptionParam("0x01 02", "data"); // 0x prefix with space separator
+ testInvalidOptionParam("0X0102", "data"); // 0X upper case X in prefix
+ testInvalidOptionParam("01.02", "data"); // invalid separator
+}
+
+// Verify the valid forms hex literals in option data are supported.
+TEST_F(Dhcp6ParserTest, optionDataValidHexLiterals) {
+
+ std::vector<std::string> valid_hexes =
+ {
+ "0a0b0C0D", // upper and lower case
+ "0A:0B:0C:0D", // colon seperator
+ "0A 0B 0C 0D", // space seperator
+ "A0B0C0D", // odd number of digits
+ "0xA0B0C0D" // 0x prefix
+ };
+
+ for (auto valid_hex : valid_hexes) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption(valid_hex, "data");
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+
+ // Expect single option with the code equal to 38.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t subid_expected[] = { 0x0A, 0x0B, 0x0C, 0x0D };
+
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected, sizeof(subid_expected));
+
+ // Clear configuration for the next pass.
+ resetConfiguration();
+ }
+}
+
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp6ParserTest, stdOptionData) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "ia-na";
+ params["space"] = DHCP6_OPTION_SPACE;
+ // Option code 3 means OPTION_IA_NA.
+ params["code"] = "3";
+ params["data"] = "12345, 6789, 1516";
+ params["csv-format"] = "true";
+
+ std::string config = createConfigWithOption(params);
+ ConstElementPtr json = parseDHCP6(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(D6O_IA_NA);
+ // Expect single option with the code equal to IA_NA option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option_;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be Option6IA
+ // which is derived from Option. This class is dedicated to
+ // represent standard option IA_NA.
+ boost::shared_ptr<Option6IA> optionIA =
+ boost::dynamic_pointer_cast<Option6IA>(option);
+ // If cast is unsuccessful than option returned was of a
+ // different type than Option6IA. This is wrong.
+ ASSERT_TRUE(optionIA);
+ // If cast was successful we may use accessors exposed by
+ // Option6IA to validate that the content of this option
+ // has been set correctly.
+ EXPECT_EQ(12345, optionIA->getIAID());
+ EXPECT_EQ(6789, optionIA->getT1());
+ EXPECT_EQ(1516, optionIA->getT2());
+}
+
+// Verify that specific option object is returned for standard
+// option with trailing domain list.
+TEST_F(Dhcp6ParserTest, rdnssOption) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "rdnss-selection";
+ params["space"] = DHCP6_OPTION_SPACE;
+ // Option code 74 is D6O_RDNSS_SELECTION
+ params["code"] = "74";
+ params["data"] = "2001::1, 3, isc.org, example.org, example.com";
+ params["csv-format"] = "true";
+
+ std::string config = createConfigWithOption(params);
+ ConstElementPtr json = parseDHCP6(config, true);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(D6O_RDNSS_SELECTION);
+ // Expect single option with the code equal to rndnss-selection option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option_;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be OptionCustom
+ // which is derived from Option. This class is dedicated to
+ // represent standard option D6O_RDNSS_SELECTION.
+ boost::shared_ptr<OptionCustom> optionCustom =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ // If cast is unsuccessful than option returned was of a
+ // different type than optionCustom. This is wrong.
+ ASSERT_TRUE(optionCustom);
+ // If cast was successful we may use accessors exposed by
+ // optionCustom to validate that the content of this option
+ // has been set correctly.
+ ASSERT_EQ(5, optionCustom->getDataFieldsNum());
+ EXPECT_EQ("2001::1", optionCustom->readAddress(0).toText());
+ EXPECT_EQ(3, optionCustom->readInteger<uint8_t>(1));
+ EXPECT_EQ("isc.org.", optionCustom->readFqdn(2));
+ EXPECT_EQ("example.org.", optionCustom->readFqdn(3));
+ EXPECT_EQ("example.com.", optionCustom->readFqdn(4));
+}
+
+// This test checks if vendor options can be specified in the config file
+// (in hex format), and later retrieved from configured subnet
+TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
+
+ // This configuration string is to configure two options
+ // sharing the code 1 and belonging to the different vendor spaces.
+ // (different vendor-id values).
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"option-one\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 100,"
+ " \"data\": \"ABCDEF0105\","
+ " \"csv-format\": false"
+ " },"
+ " {"
+ " \"name\": \"option-two\","
+ " \"space\": \"vendor-1234\","
+ " \"code\": 100,"
+ " \"data\": \"1234\","
+ " \"csv-format\": false"
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now available
+ // Try to get the option from the vendor space 4491
+ OptionDescriptor desc1 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100);
+ ASSERT_TRUE(desc1.option_);
+ EXPECT_EQ(100, desc1.option_->getType());
+ // Try to get the option from the vendor space 1234
+ OptionDescriptor desc2 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(1234, 100);
+ ASSERT_TRUE(desc2.option_);
+ EXPECT_EQ(100, desc1.option_->getType());
+
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ OptionDescriptor desc3 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(5678, 38);
+ ASSERT_FALSE(desc3.option_);
+}
+
+// This test checks if vendor options can be specified in the config file,
+// (in csv format), and later retrieved from configured subnet
+TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
+
+ // This configuration string is to configure two options
+ // sharing the code 1 and belonging to the different vendor spaces.
+ // (different vendor-id values).
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 100,"
+ " \"data\": \"this is a string vendor-opt\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"space\": \"vendor-4491\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now available.
+ // Try to get the option from the vendor space 4491
+ OptionDescriptor desc1 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(VENDOR_ID_CABLE_LABS, 100);
+ ASSERT_TRUE(desc1.option_);
+ EXPECT_EQ(100, desc1.option_->getType());
+
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ OptionDescriptor desc2 =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->get(5678, 100);
+ ASSERT_FALSE(desc2.option_);
+}
+
+/// @todo add tests similar to vendorOptionsCsv and vendorOptionsHex, but for
+/// vendor options defined in a subnet.
+
+// The goal of this test is to verify that the standard option can
+// be configured to encapsulate multiple other options.
+/// @todo This test is currently disabled because it relies on the option
+/// 17 which is treated differently than all other options. There are no
+/// other standard options used by Kea which would encapsulate other
+/// options and for which values could be configured here.
+TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
+
+ // The configuration is two stage process in this test.
+ // In the first stage we create definitions of suboptions
+ // that we will add to the base option.
+ // Let's create some dummy options: foo and foo2.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"a-vendor-space\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"a-vendor-space\","
+ " \"data\": \"192.168.2.1\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"space\": \"a-vendor-space\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"a-vendor-space\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ CfgMgr::instance().clear();
+
+ // Once the definitions have been added we can configure the
+ // standard option #17. This option comprises an enterprise
+ // number and sub options. By convention (introduced in
+ // std_option_defs.h) option named 'vendor-opts'
+ // encapsulates the option space named 'vendor-<vendor-id>'.
+ // We add our dummy options to this option space and thus
+ // they should be included as sub-options in the 'vendor-opts'
+ // option.
+ config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"vendor-opts\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-1234\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-1234\","
+ " \"data\": \"192.168.2.1\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"space\": \"vendor-1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"vendor-1234\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Get the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::5"), classify_);
+ ASSERT_TRUE(subnet);
+
+ // We should have one option available.
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the option.
+ OptionDescriptor desc = subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_VENDOR_OPTS);
+ EXPECT_TRUE(desc.option_);
+ EXPECT_EQ(D6O_VENDOR_OPTS, desc.option_->getType());
+
+ // Option with the code 110 should be added as a sub-option.
+ OptionPtr option_foo = desc.option_->getOption(110);
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(110, option_foo->getType());
+ // This option comprises a single uint32_t value thus it is
+ // represented by OptionInt<uint32_t> class. Let's get the
+ // object of this type.
+ boost::shared_ptr<OptionInt<uint32_t> > option_foo_uint32 =
+ boost::dynamic_pointer_cast<OptionInt<uint32_t> >(option_foo);
+ ASSERT_TRUE(option_foo_uint32);
+ // Validate the value according to the configuration.
+ EXPECT_EQ(1234, option_foo_uint32->getValue());
+
+ // Option with the code 111 should be added as a sub-option.
+ OptionPtr option_foo2 = desc.option_->getOption(111);
+ ASSERT_TRUE(option_foo2);
+ EXPECT_EQ(111, option_foo2->getType());
+ // This option comprises the IPV4 address. Such option is
+ // represented by OptionCustom object.
+ OptionCustomPtr option_foo2_v4 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_foo2);
+ ASSERT_TRUE(option_foo2_v4);
+ // Get the IP address carried by this option and validate it.
+ EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
+
+ // Option with the code 112 should not be added.
+ EXPECT_FALSE(desc.option_->getOption(112));
+}
+
+// Tests of the hooks libraries configuration. All tests have the pre-
+// condition (checked in the test fixture's SetUp() method) that no hooks
+// libraries are loaded at the start of the tests.
+
+// Helper function to return a configuration containing an arbitrary number
+// of hooks libraries.
+std::string
+buildHooksLibrariesConfig(const std::vector<std::string>& libraries = {},
+ bool multi_threading = true) {
+ const string lbrace("{");
+ const string rbrace("}");
+ const string liblabel("\"library\": ");
+ const string quote("\"");
+
+ // Create the first part of the configuration string.
+ string config =
+ "{ \"interfaces-config\": { \"interfaces\": [] },"
+ "\"hooks-libraries\": [";
+
+ // Append the libraries (separated by commas if needed)
+ for (unsigned int i = 0; i < libraries.size(); ++i) {
+ if (i > 0) {
+ config += string(", ");
+ }
+ config += (lbrace + liblabel + quote + libraries[i] + quote + rbrace);
+ }
+
+ // Append the remainder of the configuration.
+ config += string(
+ "],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"a-vendor-space\","
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"a-vendor-space\","
+ " \"data\": \"192.168.2.1\""
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"space\": \"a-vendor-space\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"a-vendor-space\""
+ " } ]");
+
+ config += R"(,
+ "multi-threading": {
+ "enable-multi-threading": )" +
+ string(multi_threading ? "true" : "false") + R"(
+ })";
+
+ config += string("}");
+
+ return (config);
+}
+
+// The goal of this test is to verify the configuration of hooks libraries if
+// none are specified.
+TEST_F(Dhcp6ParserTest, NoHooksLibraries) {
+ // Parse a configuration containing no names.
+ string config = buildHooksLibrariesConfig();
+ if (!executeConfiguration(config,
+ "set configuration with no hooks libraries")) {
+ FAIL() << "Unable to execute configuration";
+
+ } else {
+ // No libraries should be loaded at the end of the test.
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+ }
+}
+
+// Verify parsing fails with one library that will fail validation.
+TEST_F(Dhcp6ParserTest, InvalidLibrary) {
+ // Parse a configuration containing a failing library.
+ string config = buildHooksLibrariesConfig({NOT_PRESENT_LIBRARY});
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // The status object must not be NULL
+ ASSERT_TRUE(status);
+
+ // Returned value should not be 0
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_NE(0, rcode_);
+}
+
+// Verify the configuration of hooks libraries with two being specified.
+TEST_F(Dhcp6ParserTest, LibrariesSpecified) {
+ // Marker files should not be present.
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Set up the configuration with two libraries and load them.
+ string config = buildHooksLibrariesConfig({CALLOUT_LIBRARY_1, CALLOUT_LIBRARY_2},
+ /* multi_threading = */ false);
+ ASSERT_TRUE(executeConfiguration(config,
+ "load two valid libraries"));
+
+ // Expect two libraries to be loaded in the correct order (load marker file
+ // is present, no unload marker file).
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(2, libraries.size());
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ CfgMgr::instance().commit();
+
+ // Unload the libraries. The load file should not have changed, but
+ // the unload one should indicate the unload() functions have been run.
+ config = buildHooksLibrariesConfig();
+ ASSERT_TRUE(executeConfiguration(config, "unloading libraries"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+
+ // Expect the hooks system to say that none are loaded.
+ libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+}
+
+// Verify the configuration of hooks libraries which are not compatible with
+// multi threading is rejected.
+TEST_F(Dhcp6ParserTest, IncompatibleLibrary2Specified) {
+ // Marker files should not be present.
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ std::vector<std::string> libraries;
+ libraries.push_back(string(CALLOUT_LIBRARY_2));
+
+ // Set up the configuration with two libraries and load them.
+ string config = buildHooksLibrariesConfig(libraries, true);
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // The status object must not be NULL
+ ASSERT_TRUE(status);
+
+ // Returned value should not be 0
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_NE(0, rcode_);
+
+ // Expect the library to be rejected by the server (no load marker file, no
+ // unload marker file).
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Expect the hooks system to say that none are loaded.
+ libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+}
+
+// Verify the configuration of hooks libraries which are not compatible with
+// multi threading is rejected.
+TEST_F(Dhcp6ParserTest, IncompatibleLibrary3Specified) {
+ // Marker files should not be present.
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ std::vector<std::string> libraries;
+ libraries.push_back(string(CALLOUT_LIBRARY_3));
+
+ // Set up the configuration with two libraries and load them.
+ string config = buildHooksLibrariesConfig(libraries, true);
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // The status object must not be NULL
+ ASSERT_TRUE(status);
+
+ // Returned value should not be 0
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_NE(0, rcode_);
+
+ // Expect the library to be rejected by the server (no load marker file, no
+ // unload marker file).
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Expect the hooks system to say that none are loaded.
+ libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+}
+// This test verifies that it is possible to select subset of interfaces on
+// which server should listen.
+TEST_F(Dhcp6ParserTest, selectedInterfaces) {
+ IfaceMgrTestConfig test_config(true);
+
+ // Make sure there is no garbage interface configuration in the CfgMgr.
+ ASSERT_FALSE(test_config.socketOpen("eth0", AF_INET6));
+ ASSERT_FALSE(test_config.socketOpen("eth1", AF_INET6));
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"eth0\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value must be 1 (values error)
+ // as the pool does not belong to that subnet
+ checkResult(status, 0);
+
+ CfgMgr::instance().getStagingCfg()->getCfgIface()->openSockets(AF_INET6, 10000);
+
+ // eth0 and eth1 were explicitly selected. eth2 was not.
+ EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));
+ EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET6));
+}
+
+// This test verifies that it is possible to configure the server to listen on
+// all interfaces.
+TEST_F(Dhcp6ParserTest, allInterfaces) {
+ IfaceMgrTestConfig test_config(true);
+
+ ASSERT_FALSE(test_config.socketOpen("eth0", AF_INET6));
+ ASSERT_FALSE(test_config.socketOpen("eth1", AF_INET6));
+
+ // This configuration specifies two interfaces on which server should listen
+ // but also includes '*'. This keyword switches server into the
+ // mode when it listens on all interfaces regardless of what interface names
+ // were specified in the "interfaces" parameter.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"eth0\", \"eth1\", \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ CfgMgr::instance().getStagingCfg()->getCfgIface()->openSockets(AF_INET6, 10000);
+
+ // All interfaces should be now active.
+ EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET6));
+ EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET6));
+}
+
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp6ParserTest, subnetRelayInfo) {
+
+ // A config with relay information.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"relay\": { "
+ " \"ip-address\": \"2001:db8:1::abcd\""
+ " },"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"preferred-lifetime\": 3000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db8:1::1"), classify_);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_TRUE(subnet->hasRelays());
+ EXPECT_TRUE(subnet->hasRelayAddress(IOAddress("2001:db8:1::abcd")));
+}
+
+// This test checks if it is possible to specify a list of relays
+TEST_F(Dhcp6ParserTest, subnetRelayInfoList) {
+ // A config with relay information.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"relay\": { "
+ " \"ip-addresses\": [ \"2001:db9::abcd\", \"2001:db9::abce\" ]"
+ " },"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"preferred-lifetime\": 3000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+ selectSubnet(IOAddress("2001:db9::abcd"), classify_, true);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_TRUE(subnet->hasRelays());
+ EXPECT_TRUE(subnet->hasRelayAddress(IOAddress("2001:db9::abcd")));
+ EXPECT_TRUE(subnet->hasRelayAddress(IOAddress("2001:db9::abce")));
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifySubnets) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"id\": 2,"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"id\": 3,"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"id\": 4,"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
+ " \"subnet\": \"2001:db8:4::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Let's check if client belonging to alpha class is supported in subnet[0]
+ // and not supported in any other subnet (except subnet[3], which allows
+ // everyone).
+ ClientClasses classes;
+ classes.insert("alpha");
+ auto subnet0 = subnets->begin();
+ auto subnet1 = std::next(subnet0);
+ auto subnet2 = std::next(subnet1);
+ auto subnet3 = std::next(subnet2);
+ EXPECT_TRUE ((*subnet0)->clientSupported(classes));
+ EXPECT_FALSE((*subnet1)->clientSupported(classes));
+ EXPECT_FALSE((*subnet2)->clientSupported(classes));
+ EXPECT_TRUE ((*subnet3)->clientSupported(classes));
+
+ // Let's check if client belonging to beta class is supported in subnet[1]
+ // and not supported in any other subnet (except subnet[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("beta");
+ EXPECT_FALSE((*subnet0)->clientSupported(classes));
+ EXPECT_TRUE ((*subnet1)->clientSupported(classes));
+ EXPECT_FALSE((*subnet2)->clientSupported(classes));
+ EXPECT_TRUE ((*subnet3)->clientSupported(classes));
+
+ // Let's check if client belonging to gamma class is supported in subnet[2]
+ // and not supported in any other subnet (except subnet[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("gamma");
+ EXPECT_FALSE((*subnet0)->clientSupported(classes));
+ EXPECT_FALSE((*subnet1)->clientSupported(classes));
+ EXPECT_TRUE ((*subnet2)->clientSupported(classes));
+ EXPECT_TRUE ((*subnet3)->clientSupported(classes));
+
+ // Let's check if client belonging to some other class (not mentioned in
+ // the config) is supported only in subnet[3], which allows everyone.
+ classes.clear();
+ classes.insert("delta");
+ EXPECT_FALSE((*subnet0)->clientSupported(classes));
+ EXPECT_FALSE((*subnet1)->clientSupported(classes));
+ EXPECT_FALSE((*subnet2)->clientSupported(classes));
+ EXPECT_TRUE ((*subnet3)->clientSupported(classes));
+
+ // Finally, let's check class-less client. He should be allowed only in
+ // the last subnet, which does not have any class restrictions.
+ classes.clear();
+ EXPECT_FALSE((*subnet0)->clientSupported(classes));
+ EXPECT_FALSE((*subnet1)->clientSupported(classes));
+ EXPECT_FALSE((*subnet2)->clientSupported(classes));
+ EXPECT_TRUE ((*subnet3)->clientSupported(classes));
+}
+
+// Goal of this test is to verify that multiple pools can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifyPools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::/80\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:2::/80\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:3::/80\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:4::/80\" "
+ " } ],"
+ " \"subnet\": \"2001:db8:0::/40\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+ const PoolCollection& pools = (*subnets->begin())->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+ // Let's check if client belonging to alpha class is supported in pool[0]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ ClientClasses classes;
+ classes.insert("alpha");
+ EXPECT_TRUE (pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to beta class is supported in pool[1]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("beta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to gamma class is supported in pool[2]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("gamma");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to some other class (not mentioned in
+ // the config) is supported only in pool[3], which allows everyone.
+ classes.clear();
+ classes.insert("delta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Finally, let's check class-less client. He should be allowed only in
+ // the last pool, which does not have any class restrictions.
+ classes.clear();
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+}
+
+// Goal of this test is to verify that multiple pdpools can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifyPdPools) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pd-pools\": [ { "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"client-class\": \"alpha\" "
+ " },"
+ " {"
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:2::\", "
+ " \"client-class\": \"beta\" "
+ " },"
+ " {"
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:3::\", "
+ " \"client-class\": \"gamma\" "
+ " },"
+ " {"
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64, "
+ " \"prefix\": \"2001:db8:4::\" "
+ " } ],"
+ " \"subnet\": \"2001:db8::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config, true));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+ const PoolCollection& pools = (*subnets->begin())->getPools(Lease::TYPE_PD);
+ ASSERT_EQ(4, pools.size()); // We expect 4 pools
+
+ // Let's check if client belonging to alpha class is supported in pool[0]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ ClientClasses classes;
+ classes.insert("alpha");
+ EXPECT_TRUE (pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to beta class is supported in pool[1]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("beta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to gamma class is supported in pool[2]
+ // and not supported in any other pool (except pool[3], which allows
+ // everyone).
+ classes.clear();
+ classes.insert("gamma");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Let's check if client belonging to some other class (not mentioned in
+ // the config) is supported only in pool[3], which allows everyone.
+ classes.clear();
+ classes.insert("delta");
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+
+ // Finally, let's check class-less client. He should be allowed only in
+ // the last pool, which does not have any class restrictions.
+ classes.clear();
+ EXPECT_FALSE(pools.at(0)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(1)->clientSupported(classes));
+ EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE (pools.at(3)->clientSupported(classes));
+}
+
+// This test verifies that valid d2CliengConfig works correctly.
+TEST_F(Dhcp6ParserTest, d2ClientConfigValid) {
+ // Verify that the D2 configuration can be fetched and is set to disabled.
+ D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ // Verify that the convenience method agrees.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ string config_str = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ], "
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"3001::1\", "
+ " \"server-port\" : 777, "
+ " \"sender-ip\" : \"3001::2\", "
+ " \"sender-port\" : 778, "
+ " \"max-queue-size\" : 2048, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\"}, "
+ "\"valid-lifetime\": 4000 }";
+
+ // Convert the JSON string to configuration elements.
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_str));
+ extractConfig(config_str);
+
+ // Pass the configuration in for parsing.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Verify that DHCP-DDNS updating is enabled.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Verify that the D2 configuration can be retrieved.
+ d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are correct.
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("3001::1", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(777, d2_client_config->getServerPort());
+ EXPECT_EQ("3001::2", d2_client_config->getSenderIp().toText());
+ EXPECT_EQ(778, d2_client_config->getSenderPort());
+ EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+
+ // ddns-send-updates should be global default
+ checkGlobal("ddns-send-updates", true);
+
+ // The following, deprecated dhcp-ddns parameters,
+ // should all have global default values.
+ checkGlobal("ddns-override-no-update", false);
+ checkGlobal("ddns-override-client-update", false);
+ checkGlobal("ddns-replace-client-name", "never");
+ checkGlobal("ddns-generated-prefix", "myhost");
+ checkGlobal("ddns-qualifying-suffix", "");
+}
+
+// This test verifies that valid but deprecated dhcp-ddns parameters
+// get moved to the global scope when they do not already exist there.
+TEST_F(Dhcp6ParserTest, d2ClientConfigMoveToGlobal) {
+ // Verify that the D2 configuration can be fetched and is set to disabled.
+ D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ // Verify that the convenience method agrees.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ string config_str = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ], "
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"3001::1\", "
+ " \"server-port\" : 777, "
+ " \"sender-ip\" : \"3001::2\", "
+ " \"sender-port\" : 778, "
+ " \"max-queue-size\" : 2048, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : \"when-present\", "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\", "
+ " \"hostname-char-set\" : \"[^A-Z]\", "
+ " \"hostname-char-replacement\" : \"x\"}, "
+ "\"valid-lifetime\": 4000 }";
+
+ // Convert the JSON string to configuration elements.
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_str));
+ extractConfig(config_str);
+
+ // Pass the configuration in for parsing.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Verify that DHCP-DDNS updating is enabled.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Verify that the D2 configuration can be retrieved.
+ d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are correct.
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("3001::1", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(777, d2_client_config->getServerPort());
+ EXPECT_EQ("3001::2", d2_client_config->getSenderIp().toText());
+ EXPECT_EQ(778, d2_client_config->getSenderPort());
+ EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+
+ // ddns-send-updates should be global default
+ checkGlobal("ddns-send-updates", true);
+
+ // The following should all have been moved from dhcp-ddns.
+ checkGlobal("ddns-override-no-update", true);
+ checkGlobal("ddns-override-client-update", true);
+ checkGlobal("ddns-replace-client-name", "when-present");
+ checkGlobal("ddns-generated-prefix", "test.prefix");
+ checkGlobal("ddns-qualifying-suffix", "test.suffix.");
+ checkGlobal("hostname-char-set", "[^A-Z]");
+ checkGlobal("hostname-char-replacement", "x");
+}
+
+// This test verifies that explicit global values override deprecated
+// dhcp-ddns parameters (i.e. global scope wins)
+TEST_F(Dhcp6ParserTest, d2ClientConfigBoth) {
+ // Verify that the D2 configuration can be fetched and is set to disabled.
+ D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ // Verify that the convenience method agrees.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ string config_str = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ], "
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"3001::1\", "
+ " \"server-port\" : 777, "
+ " \"sender-ip\" : \"3001::2\", "
+ " \"sender-port\" : 778, "
+ " \"max-queue-size\" : 2048, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"override-no-update\" : false, "
+ " \"override-client-update\" : false, "
+ " \"replace-client-name\" : \"when-present\", "
+ " \"generated-prefix\" : \"d2.prefix\", "
+ " \"qualifying-suffix\" : \"d2.suffix.\", "
+ " \"hostname-char-set\" : \"[^0-9]\", "
+ " \"hostname-char-replacement\" : \"z\" }, "
+ " \"ddns-send-updates\" : false, "
+ " \"ddns-override-no-update\" : true, "
+ " \"ddns-override-client-update\" : true, "
+ " \"ddns-replace-client-name\" : \"always\", "
+ " \"ddns-generated-prefix\" : \"global.prefix\", "
+ " \"ddns-qualifying-suffix\" : \"global.suffix.\", "
+ " \"hostname-char-set\" : \"[^A-Z]\", "
+ " \"hostname-char-replacement\" : \"x\", "
+ "\"valid-lifetime\": 4000 }";
+
+ // Convert the JSON string to configuration elements.
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_str));
+ extractConfig(config_str);
+
+ // Pass the configuration in for parsing.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Verify that DHCP-DDNS updating is enabled.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Verify that the D2 configuration can be retrieved.
+ d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are correct.
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("3001::1", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(777, d2_client_config->getServerPort());
+ EXPECT_EQ("3001::2", d2_client_config->getSenderIp().toText());
+ EXPECT_EQ(778, d2_client_config->getSenderPort());
+ EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+
+ // Verify all global values won.
+ checkGlobal("ddns-send-updates", false);
+ checkGlobal("ddns-override-no-update", true);
+ checkGlobal("ddns-override-client-update", true);
+ checkGlobal("ddns-replace-client-name", "always");
+ checkGlobal("ddns-generated-prefix", "global.prefix");
+ checkGlobal("ddns-qualifying-suffix", "global.suffix.");
+ checkGlobal("hostname-char-set", "[^A-Z]");
+ checkGlobal("hostname-char-replacement", "x");
+}
+
+// This test checks the ability of the server to handle a configuration
+// containing an invalid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp6ParserTest, invalidD2ClientConfig) {
+ // Configuration string with an invalid D2 client config,
+ // "server-ip" is invalid.
+ string config_str = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ], "
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"bogus-value\", "
+ " \"server-port\" : 5301, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : \"when-present\", "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" },"
+ "\"valid-lifetime\": 4000 }";
+
+ // Convert the JSON string to configuration elements.
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_str));
+
+ // Configuration should not throw, but should fail.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // check if returned status is failed.
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ // Verify that the D2 configuration can be fetched and is set to disabled.
+ D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ // Verify that the convenience method agrees.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+}
+
+/// @brief Checks if the reservation is in the range of reservations.
+///
+/// @param resrv Reservation to be searched for.
+/// @param range Range of reservations returned by the @c Host object
+/// in which the reservation will be searched.
+bool reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
+ for (IPv6ResrvIterator it = range.first; it != range.second;
+ ++it) {
+ if (resrv == it->second) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+// This test verifies that the host reservations can be specified for
+// respective IPv6 subnets.
+TEST_F(Dhcp6ParserTest, reservations) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\", "
+ " \"id\": 123,"
+ " \"reservations\": ["
+ " ]"
+ " },"
+ " {"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ " \"ip-addresses\": [ \"2001:db8:2::1234\" ],"
+ " \"hostname\": \"\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:2::1111\""
+ " },"
+ " {"
+ " \"name\": \"preference\","
+ " \"data\": \"11\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"hw-address\": \"01:02:03:04:05:06\","
+ " \"ip-addresses\": [ \"2001:db8:2::abcd\" ],"
+ " \"hostname\": \"\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:2::abbc\""
+ " },"
+ " {"
+ " \"name\": \"preference\","
+ " \"data\": \"25\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"pools\": [ ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"id\": 234"
+ " },"
+ " {"
+ " \"pools\": [ ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"0A:09:08:07:06:05:04:03:02:01\","
+ " \"prefixes\": [ \"2001:db8:3:2::/96\" ],"
+ " \"hostname\": \"\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:3::3333\""
+ " },"
+ " {"
+ " \"name\": \"preference\","
+ " \"data\": \"33\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"hw-address\": \"06:05:04:03:02:01\","
+ " \"prefixes\": [ \"2001:db8:3:1::/96\" ],"
+ " \"hostname\": \"\""
+ " }"
+ " ]"
+ " } "
+ "], "
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // Make sure all subnets have been successfully configured. There is no
+ // need to sanity check the subnet properties because it should have
+ // been already tested by other tests.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size());
+
+ // Hosts configuration must be available.
+ CfgHostsPtr hosts_cfg = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(hosts_cfg);
+
+ // Let's create an object holding hardware address of the host having
+ // a reservation in the subnet having id of 234. For simplicity the
+ // address is a collection of numbers from 1 to 6.
+ std::vector<uint8_t> hwaddr;
+ for (unsigned int i = 1; i < 7; ++i) {
+ hwaddr.push_back(static_cast<uint8_t>(i));
+ }
+ // Retrieve the reservation and sanity check the address reserved.
+ ConstHostPtr host = hosts_cfg->get6(234, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size());
+ ASSERT_TRUE(host);
+ IPv6ResrvRange resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:2::abcd")),
+ resrv));
+ // This reservation should be solely assigned to the subnet 234,
+ // and not to other two.
+ EXPECT_FALSE(hosts_cfg->get6(123, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+ EXPECT_FALSE(hosts_cfg->get6(542, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+ // Check that options are assigned correctly.
+ Option6AddrLstPtr opt_dns =
+ retrieveOption<Option6AddrLstPtr>(*host, D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ Option6AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("2001:db8:2::abbc", dns_addrs[0].toText());
+ OptionUint8Ptr opt_prf =
+ retrieveOption<OptionUint8Ptr>(*host, D6O_PREFERENCE);
+ ASSERT_TRUE(opt_prf);
+ EXPECT_EQ(25, static_cast<int>(opt_prf->getValue()));
+
+ // Do the same test for the DUID based reservation.
+ std::vector<uint8_t> duid;
+ for (unsigned int i = 1; i < 0xb; ++i) {
+ duid.push_back(static_cast<uint8_t>(i));
+ }
+ host = hosts_cfg->get6(234, Host::IDENT_DUID, &duid[0], duid.size());
+ ASSERT_TRUE(host);
+ resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:2::1234")),
+ resrv));
+ EXPECT_FALSE(hosts_cfg->get6(123, Host::IDENT_DUID, &duid[0], duid.size()));
+ EXPECT_FALSE(hosts_cfg->get6(542, Host::IDENT_DUID, &duid[0], duid.size()));
+ // Check that options are assigned correctly.
+ opt_dns = retrieveOption<Option6AddrLstPtr>(*host, D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("2001:db8:2::1111", dns_addrs[0].toText());
+ opt_prf = retrieveOption<OptionUint8Ptr>(*host, D6O_PREFERENCE);
+ ASSERT_TRUE(opt_prf);
+ EXPECT_EQ(11, static_cast<int>(opt_prf->getValue()));
+
+ // The HW address used for one of the reservations in the subnet 542
+ // consists of numbers from 6 to 1. So, let's just reverse the order
+ // of the address from the previous test.
+ std::vector<uint8_t> hwaddr_r(hwaddr.rbegin(), hwaddr.rend());
+ host = hosts_cfg->get6(542, Host::IDENT_HWADDR,
+ &hwaddr_r[0], hwaddr_r.size());
+ ASSERT_TRUE(host);
+ resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:3:1::"),
+ 96), resrv));
+
+ // This reservation must not belong to other subnets.
+ EXPECT_FALSE(hosts_cfg->get6(123, Host::IDENT_HWADDR,
+ &hwaddr_r[0], hwaddr_r.size()));
+ EXPECT_FALSE(hosts_cfg->get6(234, Host::IDENT_HWADDR,
+ &hwaddr_r[0], hwaddr_r.size()));
+
+ // Repeat the test for the DUID based reservation in this subnet.
+ std::vector<uint8_t> duid_r(duid.rbegin(), duid.rend());
+ host = hosts_cfg->get6(542, Host::IDENT_DUID, &duid_r[0], duid_r.size());
+ ASSERT_TRUE(host);
+ resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:3:2::"),
+ 96), resrv));
+
+ EXPECT_FALSE(hosts_cfg->get6(123, Host::IDENT_DUID,
+ &duid_r[0], duid_r.size()));
+ EXPECT_FALSE(hosts_cfg->get6(234, Host::IDENT_DUID,
+ &duid_r[0], duid_r.size()));
+ // Check that options are assigned correctly.
+ opt_dns = retrieveOption<Option6AddrLstPtr>(*host, D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("2001:db8:3::3333", dns_addrs[0].toText());
+ opt_prf = retrieveOption<OptionUint8Ptr>(*host, D6O_PREFERENCE);
+ ASSERT_TRUE(opt_prf);
+ EXPECT_EQ(33, static_cast<int>(opt_prf->getValue()));
+}
+
+// This test checks that it is possible to configure option data for a
+// host using a user defined option format.
+TEST_F(Dhcp6ParserTest, reservationWithOptionDefinition) {
+ ConstElementPtr x;
+ // The following configuration contains host declaration in which
+ // a non-standard option is used. This option has option definition
+ // specified in the configuration.
+ string config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"space\": \"isc\""
+ "} ],"
+ "\"subnet6\": [ "
+ " {"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ " \"ip-addresses\": [ \"2001:db8:2::1234\" ],"
+ " \"hostname\": \"\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"foo\","
+ " \"data\": \"11\","
+ " \"space\": \"isc\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"pools\": [ ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " \"id\": 234"
+ " }"
+ "],"
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // Hosts configuration must be available.
+ CfgHostsPtr hosts_cfg = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(hosts_cfg);
+
+ // Let's create an object holding DUID of the host. For simplicity the
+ // address is a collection of numbers from 1 to A.
+ std::vector<uint8_t> duid;
+ for (unsigned int i = 1; i < 0xB; ++i) {
+ duid.push_back(static_cast<uint8_t>(i));
+ }
+ // Retrieve the reservation and sanity check the address reserved.
+ ConstHostPtr host = hosts_cfg->get6(234, Host::IDENT_DUID,
+ &duid[0], duid.size());
+ ASSERT_TRUE(host);
+
+ // Check if the option has been parsed.
+ OptionUint32Ptr opt_foo = retrieveOption<OptionUint32Ptr>(*host, "isc",
+ 100);
+ ASSERT_TRUE(opt_foo);
+ EXPECT_EQ(100, opt_foo->getType());
+ EXPECT_EQ(11, opt_foo->getValue());
+}
+
+// This test verifies that the bogus host reservation would trigger a
+// server configuration error.
+TEST_F(Dhcp6ParserTest, reservationBogus) {
+ // Case 1: misspelled "duid" parameter.
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"pools\": [ ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"dui\": \"0A:09:08:07:06:05:04:03:02:01\","
+ " \"prefixes\": [ \"2001:db8:3:2::/96\" ],"
+ " \"hostname\": \"\""
+ " }"
+ " ]"
+ " } "
+ "], "
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json = parseJSON(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 1);
+
+ EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
+
+ // Case 2: DUID and HW Address both specified.
+ config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"pools\": [ ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"01:02:03:04:05:06\","
+ " \"duid\": \"0A:09:08:07:06:05:04:03:02:01\","
+ " \"prefixes\": [ \"2001:db8:3:2::/96\" ],"
+ " \"hostname\": \"\""
+ " }"
+ " ]"
+ " } "
+ "], "
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000 }";
+
+ json = parseDHCP6(config);
+
+ // Remove existing configuration, if any.
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 1);
+
+ // Case 3: Broken specification of option data.
+ config = "{ " + genIfaceConfig() + ","
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"pools\": [ ],"
+ " \"subnet\": \"2001:db8:3::/64\", "
+ " \"id\": 542,"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"0A:09:08:07:06:05:04:03:02:01\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"invalid-ip-address\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " } "
+ "], "
+ "\"preferred-lifetime\": 3000,"
+ "\"valid-lifetime\": 4000 }";
+
+ json = parseDHCP6(config);
+
+ // Remove existing configuration, if any.
+ CfgMgr::instance().clear();
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 1);
+}
+
+/// The goal of this test is to verify that configuration can include
+/// MAC/Hardware sources. This case uses RFC numbers to reference methods.
+/// Also checks if the aliases are handled properly (rfc6939 = client-addr-relay,
+/// rfc4649 = remote-id, rfc4580 = subscriber-id).
+TEST_F(Dhcp6ParserTest, macSources1) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"mac-sources\": [ \"rfc6939\", \"rfc4649\", \"rfc4580\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ CfgMACSources sources = CfgMgr::instance().getStagingCfg()->getMACSources().get();
+
+ ASSERT_EQ(3, sources.size());
+ // Let's check the aliases. They should be recognized to their base methods.
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, sources[0]);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, sources[1]);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, sources[2]);
+}
+
+/// The goal of this test is to verify that configuration can include
+/// MAC/Hardware sources. This uses specific method names.
+TEST_F(Dhcp6ParserTest, macSources2) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"mac-sources\": [ \"client-link-addr-option\", \"remote-id\", "
+ " \"subscriber-id\"],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ CfgMACSources sources = CfgMgr::instance().getStagingCfg()->getMACSources().get();
+
+ ASSERT_EQ(3, sources.size());
+ // Let's check the aliases. They should be recognized to their base methods.
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, sources[0]);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, sources[1]);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, sources[2]);
+}
+
+/// The goal of this test is to verify that empty MAC sources configuration
+/// is rejected. If specified, this has to have at least one value.
+TEST_F(Dhcp6ParserTest, macSourcesEmpty) {
+ ConstElementPtr status;
+
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_,
+ parseJSON("{ " + genIfaceConfig() + ","
+ "\"mac-sources\": [ ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }")));
+
+ // returned value should be 1 (failure), because the mac-sources must not
+ // be empty when specified.
+ checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that MAC sources configuration can
+/// only use valid parameters.
+TEST_F(Dhcp6ParserTest, macSourcesBogus) {
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6("{ " + genIfaceConfig() + ","
+ "\"mac-sources\": [ \"from-wire\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }"));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ // returned value should be 1 (failure)
+ checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that Host Reservation flags can be
+/// specified on a per-subnet basis.
+TEST_F(Dhcp6ParserTest, hostReservationPerSubnet) {
+
+ /// - Configuration:
+ /// - only addresses (no prefixes)
+ /// - 7 subnets with:
+ /// - 2001:db8:1::/64 (all reservations enabled)
+ /// - 2001:db8:2::/64 (out-of-pool reservations)
+ /// - 2001:db8:3::/64 (reservations disabled)
+ /// - 2001:db8:4::/64 (global reservations)
+ /// - 2001:db8:5::/64 (reservations not specified)
+ /// - 2001:db8:6::/64 (global + all enabled)
+ /// - 2001:db8:7::/64 (global + out-of-pool enabled)
+ const char* hr_config =
+ "{"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " },"
+ " {"
+ " \"id\": 2,"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true"
+ " },"
+ " {"
+ " \"id\": 3,"
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/64\" } ],"
+ " \"subnet\": \"2001:db8:3::/48\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": false"
+ " },"
+ " {"
+ " \"id\": 4,"
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/64\" } ],"
+ " \"subnet\": \"2001:db8:4::/48\", "
+ " \"reservations-global\": true,"
+ " \"reservations-in-subnet\": false"
+ " },"
+ " {"
+ " \"id\": 5,"
+ " \"pools\": [ { \"pool\": \"2001:db8:5::/64\" } ],"
+ " \"subnet\": \"2001:db8:5::/48\" "
+ " },"
+ " {"
+ " \"id\": 6,"
+ " \"pools\": [ { \"pool\": \"2001:db8:6::/64\" } ],"
+ " \"subnet\": \"2001:db8:6::/48\", "
+ " \"reservations-global\": true,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " },"
+ " {"
+ " \"id\": 7,"
+ " \"pools\": [ { \"pool\": \"2001:db8:7::/64\" } ],"
+ " \"subnet\": \"2001:db8:7::/48\", "
+ " \"reservations-global\": true,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(hr_config));
+ extractConfig(hr_config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+ CfgMgr::instance().commit();
+
+ // Let's get all subnets and check that there are 7 of them.
+ ConstCfgSubnets6Ptr subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
+ ASSERT_TRUE(subnets);
+ const Subnet6Collection* subnet_col = subnets->getAll();
+ ASSERT_EQ(7, subnet_col->size()); // We expect 7 subnets
+
+ // Let's check if the parsed subnets have correct HR modes.
+
+ // Subnet 1
+ Subnet6Ptr subnet;
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 2
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:2::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_TRUE(subnet->getReservationsOutOfPool());
+
+ // Subnet 3
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:3::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_FALSE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 4
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:4::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getReservationsGlobal());
+ EXPECT_FALSE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 5
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:5::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 6
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:6::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 7
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:7::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_TRUE(subnet->getReservationsOutOfPool());
+
+}
+
+/// The goal of this test is to verify that Host Reservation flags can be
+/// specified globally.
+TEST_F(Dhcp6ParserTest, hostReservationGlobal) {
+
+ /// - Configuration:
+ /// - only addresses (no prefixes)
+ /// - 2 subnets with:
+ /// - 2001:db8:1::/64 (all reservations enabled)
+ /// - 2001:db8:2::/64 (reservations not specified)
+ const char* hr_config =
+ "{"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"reservations-global\": false,"
+ "\"reservations-in-subnet\": true,"
+ "\"reservations-out-of-pool\": true,"
+ "\"subnet6\": [ { "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " },"
+ " {"
+ " \"id\": 2,"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(hr_config));
+ extractConfig(hr_config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+ CfgMgr::instance().commit();
+
+ // Let's get all subnets and check that there are 2 of them.
+ ConstCfgSubnets6Ptr subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
+ ASSERT_TRUE(subnets);
+ const Subnet6Collection* subnet_col = subnets->getAll();
+ ASSERT_EQ(2, subnet_col->size()); // We expect 2 subnets
+
+ // Let's check if the parsed subnets have correct HR modes.
+
+ // Subnet 1
+ Subnet6Ptr subnet;
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool());
+
+ // Subnet 2
+ subnet = subnets->selectSubnet(IOAddress("2001:db8:2::1"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getReservationsGlobal());
+ EXPECT_TRUE(subnet->getReservationsInSubnet());
+ EXPECT_TRUE(subnet->getReservationsOutOfPool());
+}
+
+/// The goal of this test is to verify that configuration can include
+/// Relay Supplied options (specified as numbers).
+TEST_F(Dhcp6ParserTest, rsooNumbers) {
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json =
+ parseDHCP6("{ " + genIfaceConfig() + ","
+ "\"relay-supplied-options\": [ \"10\", \"20\", \"30\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }"));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ // The following codes should be enabled now
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(10));
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(20));
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(30));
+
+ // This option is on the IANA list, so it should be allowed all the time
+ // (http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml)
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+ // Those options are not enabled
+ EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(25));
+ EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()->enabled(1));
+}
+
+/// The goal of this test is to verify that configuration can include
+/// Relay Supplied options (specified as names).
+TEST_F(Dhcp6ParserTest, rsooNames) {
+
+ string config = "{ " + genIfaceConfig() + ","
+ "\"relay-supplied-options\": [ \"dns-servers\", \"remote-id\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ for (uint16_t code = 0; code < D6O_NAME_SERVERS; ++code) {
+ EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(code)) << " for option code " << code;
+ }
+
+ // The following code should be enabled now
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(D6O_NAME_SERVERS));
+
+ for (uint16_t code = D6O_NAME_SERVERS + 1; code < D6O_REMOTE_ID; ++code) {
+ EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(code)) << " for option code " << code;
+ }
+
+ // Check remote-id. It should be enabled.
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(D6O_REMOTE_ID));
+ for (uint16_t code = D6O_REMOTE_ID + 1; code < D6O_ERP_LOCAL_DOMAIN_NAME; ++code) {
+ EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(code)) << " for option code " << code;
+ }
+
+ // This option is on the IANA list, so it should be allowed all the time
+ // (http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml)
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+ for (uint16_t code = D6O_ERP_LOCAL_DOMAIN_NAME + 1; code < 300; ++code) {
+ EXPECT_FALSE(CfgMgr::instance().getStagingCfg()->getCfgRSOO()
+ ->enabled(code)) << " for option code " << code;
+ }
+}
+
+TEST_F(Dhcp6ParserTest, rsooNegativeNumber) {
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json =
+ parseDHCP6("{ " + genIfaceConfig() + ","
+ "\"relay-supplied-options\": [ \"80\", \"-2\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }"));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ // returned value should be 0 (success)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+TEST_F(Dhcp6ParserTest, rsooBogusName) {
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json =
+ parseDHCP6("{ " + genIfaceConfig() + ","
+ "\"relay-supplied-options\": [ \"bogus\", \"dns-servers\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ], "
+ "\"valid-lifetime\": 4000 }"));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ // returned value should be 0 (success)
+ checkResult(status, 1);
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+/// Check that not existent data directory returns an error.
+TEST_F(Dhcp6ParserTest, notExistDataDir) {
+
+ string config_txt = "{\n"
+ "\"data-directory\": \"/does-not-exist--\"\n"
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // returned value should be 1 (error)
+ int rcode;
+ ConstElementPtr comment = parseAnswerText(rcode, status);
+ EXPECT_EQ(1, rcode);
+ string text;
+ ASSERT_NO_THROW(text = comment->stringValue());
+ EXPECT_EQ("Bad directory '/does-not-exist--': No such file or directory",
+ text);
+}
+
+/// Check that not a directory data directory returns an error.
+TEST_F(Dhcp6ParserTest, notDirDataDir) {
+
+ string config_txt = "{\n"
+ "\"data-directory\": \"/dev/null\"\n"
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // returned value should be 1 (error)
+ int rcode;
+ ConstElementPtr comment = parseAnswerText(rcode, status);
+ EXPECT_EQ(1, rcode);
+ string text;
+ ASSERT_NO_THROW(text = comment->stringValue());
+ EXPECT_EQ("'/dev/null' is not a directory", text);
+}
+
+/// Check that a valid data directory is accepted.
+TEST_F(Dhcp6ParserTest, testDataDir) {
+
+ EXPECT_TRUE(CfgMgr::instance().getDataDir().unspecified());
+ string original_datadir(CfgMgr::instance().getDataDir());
+ string datadir(TEST_DATA_BUILDDIR);
+ string config_txt = "{\n"
+ "\"data-directory\": \"" + datadir + "\"\n"
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+ // Do not export it as it will keep the current TEST_DATA_BUILDDIR...
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // returned value should be 0 (success);
+ checkResult(status, 0);
+
+ // The value of data-directory was updated.
+ EXPECT_FALSE(CfgMgr::instance().getDataDir().unspecified());
+ EXPECT_EQ(datadir, string(CfgMgr::instance().getDataDir()));
+ EXPECT_NE(original_datadir, string(CfgMgr::instance().getDataDir()));
+}
+
+/// Check that the decline-probation-period value has a default value if not
+/// specified explicitly.
+TEST_F(Dhcp6ParserTest, declineTimerDefault) {
+
+ string config_txt = "{ " + genIfaceConfig() + ","
+ "\"subnet6\": [ ] "
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+ extractConfig(config_txt);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ // The value of decline-probation-period must be equal to the
+ // default value (86400). The default value is defined in GLOBAL6_DEFAULTS in
+ // simple_parser6.cc.
+ EXPECT_EQ(86400, CfgMgr::instance().getStagingCfg()->getDeclinePeriod());
+}
+
+/// Check that the dhcp4o6-port default value has a default value if not
+/// specified explicitly.
+TEST_F(Dhcp6ParserTest, dhcp4o6portDefault) {
+
+ string config_txt = "{ " + genIfaceConfig() + ","
+ "\"subnet6\": [ ] "
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+ extractConfig(config_txt);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, config));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ // The value of decline-probation-period must be equal to the
+ // default value (0). The default value is defined in GLOBAL6_DEFAULTS in
+ // simple_parser6.cc.
+ EXPECT_EQ(0, CfgMgr::instance().getStagingCfg()->getDhcp4o6Port());
+}
+
+/// Check that the decline-probation-period value can be set properly.
+TEST_F(Dhcp6ParserTest, declineTimer) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"decline-probation-period\": 12345,"
+ "\"subnet6\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ // The value of decline-probation-period must be equal to the
+ // value specified.
+ EXPECT_EQ(12345,
+ CfgMgr::instance().getStagingCfg()->getDeclinePeriod());
+}
+
+/// Check that an incorrect decline-probation-period value will be caught.
+TEST_F(Dhcp6ParserTest, declineTimerError) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"decline-probation-period\": \"soon\","
+ "\"subnet6\": [ ]"
+ "}";
+
+ ConstElementPtr json = parseJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // returned value should be 1 (error)
+ checkResult(status, 1);
+
+ // Check that the error contains error position.
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+ // Check that the Dhcp6 parser catches the type error
+ EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
+}
+
+// Check that configuration for the expired leases processing may be
+// specified.
+TEST_F(Dhcp6ParserTest, expiredLeasesProcessing) {
+ // Create basic configuration with the expiration specific parameters.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"expired-leases-processing\": "
+ "{"
+ " \"reclaim-timer-wait-time\": 20,"
+ " \"flush-reclaimed-timer-wait-time\": 35,"
+ " \"hold-reclaimed-time\": 1800,"
+ " \"max-reclaim-leases\": 50,"
+ " \"max-reclaim-time\": 100,"
+ " \"unwarned-reclaim-cycles\": 10"
+ "},"
+ "\"subnet6\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // Returned value should be 0 (success)
+ checkResult(status, 0);
+
+ // The value of decline-probation-period must be equal to the
+ // value specified.
+ CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
+ ASSERT_TRUE(cfg);
+
+ // Verify that parameters are correct.
+ EXPECT_EQ(20, cfg->getReclaimTimerWaitTime());
+ EXPECT_EQ(35, cfg->getFlushReclaimedTimerWaitTime());
+ EXPECT_EQ(1800, cfg->getHoldReclaimedTime());
+ EXPECT_EQ(50, cfg->getMaxReclaimLeases());
+ EXPECT_EQ(100, cfg->getMaxReclaimTime());
+ EXPECT_EQ(10, cfg->getUnwarnedReclaimCycles());
+}
+
+// Check that invalid configuration for the expired leases processing is
+// causing an error.
+TEST_F(Dhcp6ParserTest, expiredLeasesProcessingError) {
+ // Create basic configuration with the expiration specific parameters.
+ // One of the parameters holds invalid value.
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"expired-leases-processing\": "
+ "{"
+ " \"reclaim-timer-wait-time\": -5,"
+ " \"flush-reclaimed-timer-wait-time\": 35,"
+ " \"hold-reclaimed-time\": 1800,"
+ " \"max-reclaim-leases\": 50,"
+ " \"max-reclaim-time\": 100,"
+ " \"unwarned-reclaim-cycles\": 10"
+ "},"
+ "\"subnet6\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+
+ // Returned value should be 0 (error)
+ checkResult(status, 1);
+
+ // Check that the error contains error position.
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+}
+
+// Verifies that simple list of valid classes parses and
+// is staged for commit.
+TEST_F(Dhcp6ParserTest, validClientClassDictionary) {
+ string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"client-classes\" : [ \n"
+ " { \n"
+ " \"name\": \"one\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"three\" \n"
+ " } \n"
+ "], \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ], \n"
+ " \"subnet\": \"2001:db8:1::/64\" } ], \n"
+ "\"valid-lifetime\": 4000 } \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // We check staging config because CfgMgr::commit hasn't been executed.
+ ClientClassDictionaryPtr dictionary;
+ dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ASSERT_TRUE(dictionary);
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+
+ // Execute the commit
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Verify that after commit, the current config has the correct dictionary
+ dictionary = CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ ASSERT_TRUE(dictionary);
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+}
+
+// Verifies that a class list containing an invalid
+// class definition causes a configuration error.
+TEST_F(Dhcp6ParserTest, invalidClientClassDictionary) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"client-classes\" : [ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"bogus\": \"bad\" \n"
+ " } \n"
+ "], \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8::1 - 2001:db8::ffff\" } ], \n"
+ " \"subnet\": \"2001:db8::/64\" \n"
+ " } ] \n"
+ "} \n";
+
+ EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
+}
+
+// Test verifies that regular configuration does not provide any user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, poolUserContextMissing) {
+ extractConfig(PARSER_CONFIGS[0]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[0]), 0, 0, Lease::TYPE_NA, pool);
+ ASSERT_TRUE(pool);
+ EXPECT_FALSE(pool->getContext());
+}
+
+// Test verifies that it's possible to specify empty user context in the
+// address pool.
+TEST_F(Dhcp6ParserTest, poolUserContextEmpty) {
+ extractConfig(PARSER_CONFIGS[1]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[1]), 0, 0, Lease::TYPE_NA, pool);
+ ASSERT_TRUE(pool);
+ ConstElementPtr ctx = pool->getContext();
+ ASSERT_TRUE(ctx);
+
+ // The context should be of type map and not contain any parameters.
+ EXPECT_EQ(Element::map, ctx->getType());
+ EXPECT_EQ(0, ctx->size());
+}
+
+// Test verifies that it's possible to specify parameters in the user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, poolUserContextlw4over6) {
+ extractConfig(PARSER_CONFIGS[2]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[2]), 0, 0, Lease::TYPE_NA, pool);
+ ASSERT_TRUE(pool);
+ ConstElementPtr ctx = pool->getContext();
+ ASSERT_TRUE(ctx);
+
+ // The context should be of type map and contain 4 parameters.
+ EXPECT_EQ(Element::map, ctx->getType());
+ EXPECT_EQ(4, ctx->size());
+ ConstElementPtr ratio = ctx->get("lw4over6-sharing-ratio");
+ ConstElementPtr v4pool = ctx->get("lw4over6-v4-pool");
+ ConstElementPtr exclude = ctx->get("lw4over6-sysports-exclude");
+ ConstElementPtr v6len = ctx->get("lw4over6-bind-prefix-len");
+
+ ASSERT_TRUE(ratio);
+ ASSERT_EQ(Element::integer, ratio->getType());
+ int64_t int_value;
+ EXPECT_NO_THROW(ratio->getValue(int_value));
+ EXPECT_EQ(64L, int_value);
+
+ ASSERT_TRUE(v4pool);
+ ASSERT_EQ(Element::string, v4pool->getType());
+ EXPECT_EQ("192.0.2.0/24", v4pool->stringValue());
+
+ ASSERT_TRUE(exclude);
+ bool bool_value;
+ ASSERT_EQ(Element::boolean, exclude->getType());
+ EXPECT_NO_THROW(exclude->getValue(bool_value));
+ EXPECT_EQ(true, bool_value);
+
+ ASSERT_TRUE(v6len);
+ ASSERT_EQ(Element::integer, v6len->getType());
+ EXPECT_NO_THROW(v6len->getValue(int_value));
+ EXPECT_EQ(56L, int_value);
+}
+
+// Test verifies that it's possible to specify parameters in the user context
+// in the min-max address pool.
+TEST_F(Dhcp6ParserTest, poolMinMaxUserContext) {
+ extractConfig(PARSER_CONFIGS[3]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[3]), 0, 0, Lease::TYPE_NA, pool);
+ ASSERT_TRUE(pool);
+ ConstElementPtr ctx = pool->getContext();
+ ASSERT_TRUE(ctx);
+
+ // The context should be of type map and contain 4 parameters.
+ EXPECT_EQ(Element::map, ctx->getType());
+ EXPECT_EQ(4, ctx->size());
+ ConstElementPtr ratio = ctx->get("lw4over6-sharing-ratio");
+ ConstElementPtr v4pool = ctx->get("lw4over6-v4-pool");
+ ConstElementPtr exclude = ctx->get("lw4over6-sysports-exclude");
+ ConstElementPtr v6len = ctx->get("lw4over6-bind-prefix-len");
+
+ ASSERT_TRUE(ratio);
+ ASSERT_EQ(Element::integer, ratio->getType());
+ int64_t int_value;
+ EXPECT_NO_THROW(ratio->getValue(int_value));
+ EXPECT_EQ(64L, int_value);
+
+ ASSERT_TRUE(v4pool);
+ ASSERT_EQ(Element::string, v4pool->getType());
+ EXPECT_EQ("192.0.2.0/24", v4pool->stringValue());
+
+ ASSERT_TRUE(exclude);
+ bool bool_value;
+ ASSERT_EQ(Element::boolean, exclude->getType());
+ EXPECT_NO_THROW(exclude->getValue(bool_value));
+ EXPECT_EQ(true, bool_value);
+
+ ASSERT_TRUE(v6len);
+ ASSERT_EQ(Element::integer, v6len->getType());
+ EXPECT_NO_THROW(v6len->getValue(int_value));
+ EXPECT_EQ(56L, int_value);
+}
+
+// Test verifies that regular configuration does not provide any user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, pdPoolUserContextMissing) {
+ extractConfig(PARSER_CONFIGS[4]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[4]), 0, 0, Lease::TYPE_PD, pool);
+ ASSERT_TRUE(pool);
+ EXPECT_FALSE(pool->getContext());
+}
+
+// Test verifies that it's possible to specify empty user context in the
+// address pool.
+TEST_F(Dhcp6ParserTest, pdPoolUserContextEmpty) {
+ extractConfig(PARSER_CONFIGS[5]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[5]), 0, 0, Lease::TYPE_PD, pool);
+ ASSERT_TRUE(pool);
+ ConstElementPtr ctx = pool->getContext();
+ ASSERT_TRUE(ctx);
+
+ // The context should be of type map and not contain any parameters.
+ EXPECT_EQ(Element::map, ctx->getType());
+ EXPECT_EQ(0, ctx->size());
+}
+
+// Test verifies that it's possible to specify parameters in the user context
+// in the address pool.
+TEST_F(Dhcp6ParserTest, pdPoolUserContextlw4over6) {
+ extractConfig(PARSER_CONFIGS[6]);
+ PoolPtr pool;
+ getPool(string(PARSER_CONFIGS[6]), 0, 0, Lease::TYPE_PD, pool);
+ ASSERT_TRUE(pool);
+ ConstElementPtr ctx = pool->getContext();
+ ASSERT_TRUE(ctx);
+
+ // The context should be of type map and contain 4 parameters.
+ EXPECT_EQ(Element::map, ctx->getType());
+ EXPECT_EQ(4, ctx->size());
+ ConstElementPtr ratio = ctx->get("lw4over6-sharing-ratio");
+ ConstElementPtr v4pool = ctx->get("lw4over6-v4-pool");
+ ConstElementPtr exclude = ctx->get("lw4over6-sysports-exclude");
+ ConstElementPtr v6len = ctx->get("lw4over6-bind-prefix-len");
+
+ ASSERT_TRUE(ratio);
+ ASSERT_EQ(Element::integer, ratio->getType());
+ int64_t int_value;
+ EXPECT_NO_THROW(ratio->getValue(int_value));
+ EXPECT_EQ(64L, int_value);
+
+ ASSERT_TRUE(v4pool);
+ ASSERT_EQ(Element::string, v4pool->getType());
+ EXPECT_EQ("192.0.2.0/24", v4pool->stringValue());
+
+ ASSERT_TRUE(exclude);
+ bool bool_value;
+ ASSERT_EQ(Element::boolean, exclude->getType());
+ EXPECT_NO_THROW(exclude->getValue(bool_value));
+ EXPECT_TRUE(bool_value);
+
+ ASSERT_TRUE(v6len);
+ ASSERT_EQ(Element::integer, v6len->getType());
+ EXPECT_NO_THROW(v6len->getValue(int_value));
+ EXPECT_EQ(56L, int_value);
+}
+
+// Test verifies the error message for an incorrect pool range
+// is what we expect.
+TEST_F(Dhcp6ParserTest, invalidPoolRange) {
+ string config = "{ " + genIfaceConfig() + ", \n" +
+ "\"valid-lifetime\": 4000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:: - 200:1db8::ffff\" } ], \n"
+ " \"subnet\": \"2001:db8::/32\" \n"
+ " } ] \n"
+ "} \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = parseAnswerText(rcode, status);
+ string text;
+ ASSERT_NO_THROW(text = comment->stringValue());
+
+ EXPECT_EQ(1, rcode);
+ string expected = "Failed to create pool defined by: "
+ "2001:db8::-200:1db8::ffff (<string>:8:26)";
+ EXPECT_EQ(expected, text);
+}
+
+// Test verifies the error message for an outside subnet pool range
+// is what we expect.
+TEST_F(Dhcp6ParserTest, outsideSubnetPool) {
+ string config = "{ " + genIfaceConfig() + ", \n" +
+ "\"valid-lifetime\": 4000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:: - 2001:db8::ffff\" } ], \n"
+ " \"subnet\": \"2001:dc8::/32\" \n"
+ " } ] \n"
+ "} \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = parseAnswerText(rcode, status);
+ string text;
+ ASSERT_NO_THROW(text = comment->stringValue());
+
+ EXPECT_EQ(1, rcode);
+ string expected = "subnet configuration failed: "
+ "a pool of type IA_NA, with the following address range: "
+ "2001:db8::-2001:db8::ffff does not match the prefix of a subnet: "
+ "2001:dc8::/32 to which it is being added (<string>:6:14)";
+ EXPECT_EQ(expected, text);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksEmpty) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8::/48\" \n"
+ " } ],\n"
+ "\"shared-networks\": [ ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+}
+
+// Test verifies that if a shared network is defined, it at least has to have
+// a name.
+TEST_F(Dhcp6ParserTest, sharedNetworksNoName) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8::/48\" \n"
+ " } ],\n"
+ "\"shared-networks\": [ { } ]\n"
+ "} \n";
+
+ EXPECT_THROW(parseDHCP6(config, true), Dhcp6ParseError);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksEmptyName) {
+ string config = "{\n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8::/48\" \n"
+ " } ],\n"
+ "\"shared-networks\": [ { \"name\": \"\" } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_ERROR,
+ "Shared-network with subnets is missing mandatory 'name' parameter");
+}
+
+// Test verifies that a degenerated shared-network (no subnets) is
+// accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksName) {
+ string config = "{\n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db8::1 - 2001:db8::ffff\" } ]\n"
+ " } ],\n"
+ "\"shared-networks\": [ { \"name\": \"foo\" } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks6();
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+ SharedNetwork6Ptr net = *(nets->begin());
+ ASSERT_TRUE(net);
+ EXPECT_EQ("foo", net->getName());
+
+ // Verify that there are no subnets in this shared-network
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(0, subs->size());
+}
+
+// Test verifies that a degenerated shared-network (just one subnet) is
+// accepted. Also tests that, unless explicitly specified, the subnet
+// gets default values.
+TEST_F(Dhcp6ParserTest, sharedNetworks1subnet) {
+ string config = "{\n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db8::1 - 2001:db8::ffff\" } ]\n"
+ " } ]\n"
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks6();
+ ASSERT_TRUE(cfg_net);
+
+ // There should be exactly one shared subnet.
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+
+ SharedNetwork6Ptr net = *(nets->begin());
+ ASSERT_TRUE(net);
+ EXPECT_EQ("foo", net->getName());
+
+ // It should have one subnet. The subnet should have default values.
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(1, subs->size());
+ checkSubnet(*subs, "2001:db8::/48", 0, 0, 0, 7200);
+
+ // Now make sure the subnet was added to global list of subnets.
+ CfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ ASSERT_TRUE(subnets6);
+
+ const Subnet6Collection* gsubs = subnets6->getAll();
+ ASSERT_TRUE(gsubs);
+ checkSubnet(*gsubs, "2001:db8::/48", 0, 0, 0, 7200);
+}
+
+// Test verifies that a proper shared-network (three subnets) is
+// accepted. It verifies several things:
+// - that more than one subnet can be added to shared subnets
+// - that each subnet being part of the shared subnets is also stored in
+// global subnets collection
+// - that a subnet can inherit global values
+// - that subnet can override global parameters
+// - that overridden parameters only affect one subnet and not others
+TEST_F(Dhcp6ParserTest, sharedNetworks3subnets) {
+ string config = "{\n"
+ "\"renew-timer\": 1000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"min-preferred-lifetime\": 2000, \n"
+ "\"max-preferred-lifetime\": 4000, \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"min-valid-lifetime\": 3000, \n"
+ "\"max-valid-lifetime\": 5000, \n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"subnet6\": [\n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db1::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db2::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+ " \"renew-timer\": 2,\n"
+ " \"rebind-timer\": 22,\n"
+ " \"preferred-lifetime\": 222,\n"
+ " \"min-preferred-lifetime\": 111,\n"
+ " \"max-preferred-lifetime\": 333,\n"
+ " \"valid-lifetime\": 2222,\n"
+ " \"min-valid-lifetime\": 1111,\n"
+ " \"max-valid-lifetime\": 3333\n"
+ " },\n"
+ " { \n"
+ " \"id\": 3, \n"
+ " \"subnet\": \"2001:db3::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+ " }\n"
+ " ]\n"
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks6();
+
+ // There is expected one shared subnet.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+
+ SharedNetwork6Ptr net = *(nets->begin());
+ ASSERT_TRUE(net);
+
+ EXPECT_EQ("foo", net->getName());
+
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(3, subs->size());
+ checkSubnet(*subs, "2001:db1::/48",
+ 1000, 2000, 3000, 4000,
+ 2000, 4000, 3000, 5000);
+ checkSubnet(*subs, "2001:db2::/48",
+ 2, 22, 222, 2222,
+ 111, 333, 1111, 3333);
+ checkSubnet(*subs, "2001:db3::/48",
+ 1000, 2000, 3000, 4000,
+ 2000, 4000, 3000, 5000);
+
+ // Now make sure the subnet was added to global list of subnets.
+ CfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ ASSERT_TRUE(subnets6);
+
+ const Subnet6Collection* gsubs = subnets6->getAll();
+ ASSERT_TRUE(gsubs);
+ checkSubnet(*gsubs, "2001:db1::/48",
+ 1000, 2000, 3000, 4000,
+ 2000, 4000, 3000, 5000);
+ checkSubnet(*gsubs, "2001:db2::/48",
+ 2, 22, 222, 2222,
+ 111, 333, 1111, 3333);
+ checkSubnet(*gsubs, "2001:db3::/48",
+ 1000, 2000, 3000, 4000,
+ 2000, 4000, 3000, 5000);
+}
+
+// This test checks if parameters are derived properly:
+// - global to shared network
+// - shared network to subnet
+// Also, it tests that more than one shared network can be defined.
+TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
+
+ // We need to fake the interfaces present, because we want to test
+ // interface names inheritance. However, there are sanity checks
+ // on subnet level that would refuse the value if the interface
+ // is not present.
+ IfaceMgrTestConfig iface_config(true);
+
+ // Build some expected interface-id values.
+ const string text1 = "oneone";
+ const string text2 = "twotwo";
+ OptionBuffer buffer1 = OptionBuffer(text1.begin(), text1.end());
+ OptionBuffer buffer2 = OptionBuffer(text2.begin(), text2.end());
+ Option iface_id1(Option::V6, D6O_INTERFACE_ID, buffer1);
+ Option iface_id2(Option::V6, D6O_INTERFACE_ID, buffer2);
+
+ string config = "{\n"
+ "\"renew-timer\": 1, \n"
+ "\"rebind-timer\": 2, \n"
+ "\"preferred-lifetime\": 3,\n"
+ "\"min-preferred-lifetime\": 2,\n"
+ "\"max-preferred-lifetime\": 4,\n"
+ "\"valid-lifetime\": 4, \n"
+ "\"min-valid-lifetime\": 3, \n"
+ "\"max-valid-lifetime\": 5, \n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"renew-timer\": 10,\n"
+ " \"rebind-timer\": 20, \n"
+ " \"preferred-lifetime\": 30,\n"
+ " \"min-preferred-lifetime\": 20,\n"
+ " \"max-preferred-lifetime\": 40,\n"
+ " \"valid-lifetime\": 40, \n"
+ " \"min-valid-lifetime\": 30, \n"
+ " \"max-valid-lifetime\": 50, \n"
+ " \"interface-id\": \"oneone\",\n"
+ " \"store-extended-info\": true,\n"
+ " \"relay\": {\n"
+ " \"ip-address\": \"1111::1\"\n"
+ " },\n"
+ " \"rapid-commit\": true,\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": false,\n"
+ " \"subnet6\": [\n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db1::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db2::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+ " \"renew-timer\": 100\n,"
+ " \"rebind-timer\": 200, \n"
+ " \"preferred-lifetime\": 300,\n"
+ " \"min-preferred-lifetime\": 200,\n"
+ " \"max-preferred-lifetime\": 400,\n"
+ " \"relay\": {\n"
+ " \"ip-address\": \"2222::2\"\n"
+ " },\n"
+ " \"valid-lifetime\": 400, \n"
+ " \"min-valid-lifetime\": 300, \n"
+ " \"max-valid-lifetime\": 500, \n"
+ " \"interface-id\": \"twotwo\",\n"
+ " \"rapid-commit\": true,\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": true\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ "{ // second shared-network starts here\n"
+ " \"name\": \"bar\",\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 3, \n"
+ " \"subnet\": \"2001:db3::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+ " }\n"
+ " ]\n"
+ "} ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks6();
+
+ // Two shared networks are expected.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(2, nets->size());
+
+ // Let's check the first one.
+ SharedNetwork6Ptr net = nets->at(0);
+ ASSERT_TRUE(net);
+
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(2, subs->size());
+
+ // For the first subnet, the renew-timer should be 10, because it was
+ // derived from shared-network level. Other parameters a derived
+ // from global scope to shared-network level and later again to
+ // subnet6 level.
+ Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48",
+ 10, 20, 30, 40, 20, 40, 30, 50);
+ ASSERT_TRUE(s);
+ ASSERT_TRUE(s->getInterfaceId());
+ EXPECT_TRUE(iface_id1.equals(s->getInterfaceId()));
+ EXPECT_TRUE(s->hasRelayAddress(IOAddress("1111::1")));
+ EXPECT_TRUE(s->getRapidCommit());
+ EXPECT_FALSE(s->getReservationsGlobal());
+ EXPECT_FALSE(s->getReservationsInSubnet());
+ EXPECT_FALSE(s->getReservationsOutOfPool());
+ EXPECT_TRUE(s->getStoreExtendedInfo());
+
+ // For the second subnet, the renew-timer should be 100, because it
+ // was specified explicitly. Other parameters a derived
+ // from global scope to shared-network level and later again to
+ // subnet6 level.
+ s = checkSubnet(*subs, "2001:db2::/48",
+ 100, 200, 300, 400, 200, 400, 300, 500);
+ ASSERT_TRUE(s->getInterfaceId());
+ EXPECT_TRUE(iface_id2.equals(s->getInterfaceId()));
+ EXPECT_TRUE(s->hasRelayAddress(IOAddress("2222::2")));
+ EXPECT_TRUE(s->getRapidCommit());
+ EXPECT_FALSE(s->getReservationsGlobal());
+ EXPECT_TRUE(s->getReservationsInSubnet());
+ EXPECT_TRUE(s->getReservationsOutOfPool());
+ EXPECT_TRUE(s->getStoreExtendedInfo());
+
+ // Ok, now check the second shared subnet.
+ net = nets->at(1);
+ ASSERT_TRUE(net);
+
+ subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(1, subs->size());
+
+ // This subnet should derive its renew-timer from global scope.
+ s = checkSubnet(*subs, "2001:db3::/48", 1, 2, 3, 4, 2, 4, 3, 5);
+ EXPECT_FALSE(s->getInterfaceId());
+ EXPECT_FALSE(s->hasRelays());
+ EXPECT_FALSE(s->getRapidCommit());
+ EXPECT_FALSE(s->getReservationsGlobal());
+ EXPECT_TRUE(s->getReservationsInSubnet());
+ EXPECT_FALSE(s->getReservationsOutOfPool());
+ EXPECT_FALSE(s->getStoreExtendedInfo());
+}
+
+// Since it is not allowed to define both interface-id and interface
+// for the same subnet, we need dedicated test that will check
+// interface separately.
+TEST_F(Dhcp6ParserTest, sharedNetworksDeriveInterfaces) {
+
+ // We need to fake the interfaces present, because we want to test
+ // interface names inheritance. However, there are sanity checks
+ // on subnet level that would refuse the value if the interface
+ // is not present.
+ IfaceMgrTestConfig iface_config(true);
+
+ string config = "{\n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"interface\": \"eth0\",\n"
+ " \"rebind-timer\": 10, \n"
+ " \"subnet6\": [\n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db1::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db2::/48\",\n"
+ " \"rebind-timer\": 100, \n"
+ " \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ "{ // second shared-network starts here\n"
+ " \"name\": \"bar\",\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 3, \n"
+ " \"subnet\": \"2001:db3::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+ " }\n"
+ " ]\n"
+ "} ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks6();
+
+ // Two shared networks are expected.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(2, nets->size());
+
+ // Let's check the first one.
+ SharedNetwork6Ptr net = nets->at(0);
+ ASSERT_TRUE(net);
+
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(2, subs->size());
+
+ // For the first subnet, the rebind-timer should be 10, because it was
+ // derived from shared-network level. Other parameters a derived
+ // from global scope to shared-network level and later again to
+ // subnet6 level.
+ Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48", 0, 10, 0, 7200);
+ ASSERT_TRUE(s);
+ EXPECT_EQ("eth0", s->getIface().get());
+
+ // For the second subnet, the rebind-timer should be 100, because it
+ // was specified explicitly. Other parameters a derived
+ // from global scope to shared-network level and later again to
+ // subnet6 level.
+ checkSubnet(*subs, "2001:db2::/48", 0, 100, 0, 7200);
+ EXPECT_EQ("eth0", s->getIface().get());
+
+ // Ok, now check the second shared subnet.
+ net = nets->at(1);
+ ASSERT_TRUE(net);
+
+ subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(1, subs->size());
+
+ // This subnet should derive its rebind-timer from global scope.
+ s = checkSubnet(*subs, "2001:db3::/48", 0, 0, 0, 7200);
+ EXPECT_EQ("", s->getIface().get());
+}
+
+// It is not allowed to have different values for interfaces names is subnets
+// in the same shared network.
+TEST_F(Dhcp6ParserTest, sharedNetworksInterfacesMixed) {
+
+ // We need to fake the interfaces present, because we want to test
+ // interface names inheritance. However, there are sanity checks
+ // on subnet level that would refuse the value if the interface
+ // is not present.
+ IfaceMgrTestConfig iface_config(true);
+
+ string config = "{\n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"subnet6\": [\n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db1::/48\",\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db2::/48\",\n"
+ " \"interface\": \"eth1\"\n"
+ " }\n"
+ " ]\n"
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_ERROR, "Subnet 2001:db2::/48 has specified "
+ "interface eth1, but earlier subnet in the same shared-network "
+ "or the shared-network itself used eth0");
+}
+
+// This test checks if client-class is derived properly.
+TEST_F(Dhcp6ParserTest, sharedNetworksDeriveClientClass) {
+
+ string config = "{\n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"foo\"\n,"
+ " \"client-class\": \"alpha\",\n"
+ " \"subnet6\": [\n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db1::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db2::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+ " \"client-class\": \"beta\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ "{ // second shared-network starts here\n"
+ " \"name\": \"bar\",\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 3, \n"
+ " \"subnet\": \"2001:db3::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+ " }\n"
+ " ]\n"
+ "} ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks6();
+
+ // Two shared networks are expected.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(2, nets->size());
+
+ // Let's check the first one.
+ SharedNetwork6Ptr net = nets->at(0);
+ ASSERT_TRUE(net);
+ EXPECT_EQ("alpha", net->getClientClass().get());
+
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(2, subs->size());
+
+ // For the first subnet, the client-class should be inherited from
+ // shared-network level.
+ Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48", 0, 0, 0, 7200);
+ ASSERT_TRUE(s);
+ EXPECT_EQ("alpha", s->getClientClass().get());
+
+ // For the second subnet, the values are overridden on subnet level.
+ // The value should not be inherited.
+ s = checkSubnet(*subs, "2001:db2::/48", 0, 0, 0, 7200);
+ ASSERT_TRUE(s);
+ EXPECT_EQ("beta", s->getClientClass().get()); // beta defined on subnet level
+
+ // Ok, now check the second shared network. It doesn't have
+ // anything defined on shared-network or subnet level, so
+ // everything should have default values.
+ net = nets->at(1);
+ ASSERT_TRUE(net);
+
+ subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(1, subs->size());
+
+ // This subnet should derive its renew-timer from global scope.
+ s = checkSubnet(*subs, "2001:db3::/48", 0, 0, 0, 7200);
+ EXPECT_TRUE(s->getClientClass().empty());
+}
+
+// Tests if rapid-commit is derived properly.
+TEST_F(Dhcp6ParserTest, sharedNetworksRapidCommit) {
+
+ string config = "{\n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"frog\"\n,"
+ " \"rapid-commit\": true,\n"
+ " \"subnet6\": [\n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db1::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db2::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+ " \"client-class\": \"beta\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ "{ // second shared-network starts here\n"
+ " \"name\": \"bar\",\n"
+ " \"rapid-commit\": false,\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 3, \n"
+ " \"subnet\": \"2001:db3::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+ " }\n"
+ " ]\n"
+ "} ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Now verify that the shared network was indeed configured.
+ CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+ ->getCfgSharedNetworks6();
+
+ // Two shared networks are expected.
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(2, nets->size());
+
+ // Let's check the first one.
+ SharedNetwork6Ptr net = nets->at(0);
+ ASSERT_TRUE(net);
+
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ ASSERT_EQ(2, subs->size());
+ auto sub = subs->begin();
+ EXPECT_TRUE((*sub)->getRapidCommit());
+ EXPECT_TRUE((*std::next(sub))->getRapidCommit());
+
+ // Ok, now check the second shared network. It doesn't have
+ // anything defined on shared-network or subnet level, so
+ // everything should have default values.
+ net = nets->at(1);
+ ASSERT_TRUE(net);
+
+ subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ EXPECT_EQ(1, subs->size());
+
+ // This subnet should derive its renew-timer from global scope.
+ sub = subs->begin();
+ EXPECT_FALSE((*sub)->getRapidCommit());
+}
+
+// Tests that non-matching rapid-commit setting for subnets belonging to a
+// shared network cause configuration error.
+TEST_F(Dhcp6ParserTest, sharedNetworksRapidCommitMix) {
+
+ string config = "{\n"
+ "\"shared-networks\": [ {\n"
+ " \"name\": \"frog\"\n,"
+ " \"subnet6\": [\n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db1::/48\",\n"
+ " \"rapid-commit\": true,\n"
+ " \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+ " },\n"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db2::/48\",\n"
+ " \"rapid-commit\": false,\n"
+ " \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+ " \"client-class\": \"beta\"\n"
+ " }\n"
+ " ]\n"
+ " } ]\n"
+ "} \n";
+
+ configure(config, CONTROL_RESULT_ERROR, "All subnets in a shared network "
+ "must have the same rapid-commit value. Subnet 2001:db2::/48 has "
+ "specified rapid-commit false, but earlier subnet in the same "
+ "shared-network or the shared-network itself used rapid-commit true");
+}
+
+// This test checks multiple host data sources.
+TEST_F(Dhcp6ParserTest, hostsDatabases) {
+
+ string config = PARSER_CONFIGS[7];
+ extractConfig(config);
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Check database config
+ ConstCfgDbAccessPtr cfgdb =
+ CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
+ ASSERT_TRUE(cfgdb);
+ const std::list<std::string>& hal = cfgdb->getHostDbAccessStringList();
+ ASSERT_EQ(2, hal.size());
+ // Keywords are in alphabetical order
+ EXPECT_EQ("name=keatest1 password=keatest type=mysql user=keatest", hal.front());
+ EXPECT_EQ("name=keatest2 password=keatest type=mysql user=keatest", hal.back());
+}
+
+// This test checks comments. Please keep it last.
+TEST_F(Dhcp6ParserTest, comments) {
+
+ string config = PARSER_CONFIGS[9];
+ extractConfig(config);
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Check global user context.
+ ConstElementPtr ctx = CfgMgr::instance().getStagingCfg()->getContext();
+ ASSERT_TRUE(ctx);
+ ASSERT_EQ(1, ctx->size());
+ ASSERT_TRUE(ctx->get("comment"));
+ EXPECT_EQ("\"A DHCPv6 server\"", ctx->get("comment")->str());
+
+ // There is a server id.
+ ConstCfgDUIDPtr duid = CfgMgr::instance().getStagingCfg()->getCfgDUID();
+ ASSERT_TRUE(duid);
+ EXPECT_EQ(DUID::DUID_LL, duid->getType());
+
+ // Check server id user context.
+ ConstElementPtr ctx_duid = duid->getContext();
+ ASSERT_TRUE(ctx_duid);
+ ASSERT_EQ(1, ctx_duid->size());
+ ASSERT_TRUE(ctx_duid->get("comment"));
+ EXPECT_EQ("\"DHCPv6 specific\"", ctx_duid->get("comment")->str());
+
+ // There is a network interface configuration.
+ ConstCfgIfacePtr iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(iface);
+
+ // Check network interface configuration user context.
+ ConstElementPtr ctx_iface = iface->getContext();
+ ASSERT_TRUE(ctx_iface);
+ ASSERT_EQ(1, ctx_iface->size());
+ ASSERT_TRUE(ctx_iface->get("comment"));
+ EXPECT_EQ("\"Use wildcard\"", ctx_iface->get("comment")->str());
+
+ // There is a global option definition.
+ const OptionDefinitionPtr& opt_def =
+ LibDHCP::getRuntimeOptionDef("isc", 100);
+ ASSERT_TRUE(opt_def);
+ EXPECT_EQ("foo", opt_def->getName());
+ EXPECT_EQ(100, opt_def->getCode());
+ EXPECT_FALSE(opt_def->getArrayType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def->getType());
+ EXPECT_TRUE(opt_def->getEncapsulatedSpace().empty());
+
+ // Check option definition user context.
+ ConstElementPtr ctx_opt_def = opt_def->getContext();
+ ASSERT_TRUE(ctx_opt_def);
+ ASSERT_EQ(1, ctx_opt_def->size());
+ ASSERT_TRUE(ctx_opt_def->get("comment"));
+ EXPECT_EQ("\"An option definition\"", ctx_opt_def->get("comment")->str());
+
+ // There is an option descriptor aka option data.
+ const OptionDescriptor& opt_desc =
+ CfgMgr::instance().getStagingCfg()->getCfgOption()->
+ get(DHCP6_OPTION_SPACE, D6O_SUBSCRIBER_ID);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(D6O_SUBSCRIBER_ID, opt_desc.option_->getType());
+
+ // Check option descriptor user context.
+ ConstElementPtr ctx_opt_desc = opt_desc.getContext();
+ ASSERT_TRUE(ctx_opt_desc);
+ ASSERT_EQ(1, ctx_opt_desc->size());
+ ASSERT_TRUE(ctx_opt_desc->get("comment"));
+ EXPECT_EQ("\"Set option value\"", ctx_opt_desc->get("comment")->str());
+
+ // And there are some client classes.
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ASSERT_TRUE(dict);
+ EXPECT_EQ(3, dict->getClasses()->size());
+ ClientClassDefPtr cclass = dict->findClass("all");
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("all", cclass->getName());
+ EXPECT_EQ("'' == ''", cclass->getTest());
+
+ // Check client class user context.
+ ConstElementPtr ctx_class = cclass->getContext();
+ ASSERT_TRUE(ctx_class);
+ ASSERT_EQ(1, ctx_class->size());
+ ASSERT_TRUE(ctx_class->get("comment"));
+ EXPECT_EQ("\"match all\"", ctx_class->get("comment")->str());
+
+ // The 'none' class has no user-context/comment.
+ cclass = dict->findClass("none");
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("none", cclass->getName());
+ EXPECT_EQ("", cclass->getTest());
+ EXPECT_FALSE(cclass->getContext());
+
+ // The 'both' class has a user context and a comment.
+ cclass = dict->findClass("both");
+ EXPECT_EQ("both", cclass->getName());
+ EXPECT_EQ("", cclass->getTest());
+ ctx_class = cclass->getContext();
+ ASSERT_TRUE(ctx_class);
+ ASSERT_EQ(2, ctx_class->size());
+ ASSERT_TRUE(ctx_class->get("comment"));
+ EXPECT_EQ("\"a comment\"", ctx_class->get("comment")->str());
+ ASSERT_TRUE(ctx_class->get("version"));
+ EXPECT_EQ("1", ctx_class->get("version")->str());
+
+ // There is a control socket.
+ ConstElementPtr socket =
+ CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
+ ASSERT_TRUE(socket);
+ ASSERT_TRUE(socket->get("socket-type"));
+ EXPECT_EQ("\"unix\"", socket->get("socket-type")->str());
+ ASSERT_TRUE(socket->get("socket-name"));
+ EXPECT_EQ("\"/tmp/kea6-ctrl-socket\"", socket->get("socket-name")->str());
+
+ // Check control socket comment and user context.
+ ConstElementPtr ctx_socket = socket->get("user-context");
+ ASSERT_EQ(1, ctx_socket->size());
+ ASSERT_TRUE(ctx_socket->get("comment"));
+ EXPECT_EQ("\"Indirect comment\"", ctx_socket->get("comment")->str());
+
+ // Now verify that the shared network was indeed configured.
+ const CfgSharedNetworks6Ptr& cfg_net =
+ CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6();
+ ASSERT_TRUE(cfg_net);
+ const SharedNetwork6Collection* nets = cfg_net->getAll();
+ ASSERT_TRUE(nets);
+ ASSERT_EQ(1, nets->size());
+ SharedNetwork6Ptr net = nets->at(0);
+ ASSERT_TRUE(net);
+ EXPECT_EQ("foo", net->getName());
+
+ // Check shared network user context.
+ ConstElementPtr ctx_net = net->getContext();
+ ASSERT_TRUE(ctx_net);
+ ASSERT_EQ(1, ctx_net->size());
+ ASSERT_TRUE(ctx_net->get("comment"));
+ EXPECT_EQ("\"A shared network\"", ctx_net->get("comment")->str());
+
+ // The shared network has a subnet.
+ const Subnet6SimpleCollection* subs = net->getAllSubnets();
+ ASSERT_TRUE(subs);
+ ASSERT_EQ(1, subs->size());
+ Subnet6Ptr sub = *subs->begin();
+ ASSERT_TRUE(sub);
+
+ // Check subnet user context.
+ ConstElementPtr ctx_sub = sub->getContext();
+ ASSERT_TRUE(ctx_sub);
+ ASSERT_EQ(1, ctx_sub->size());
+ ASSERT_TRUE(ctx_sub->get("comment"));
+ EXPECT_EQ("\"A subnet\"", ctx_sub->get("comment")->str());
+ EXPECT_EQ(100, sub->getID());
+ EXPECT_EQ("2001:db1::/48", sub->toText());
+
+ // The subnet has a pool.
+ const PoolCollection& pools = sub->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools.size());
+ PoolPtr pool = pools.at(0);
+ ASSERT_TRUE(pool);
+
+ // Check pool user context.
+ ConstElementPtr ctx_pool = pool->getContext();
+ ASSERT_TRUE(ctx_pool);
+ ASSERT_EQ(1, ctx_pool->size());
+ ASSERT_TRUE(ctx_pool->get("comment"));
+ EXPECT_EQ("\"A pool\"", ctx_pool->get("comment")->str());
+
+ // The subnet has a prefix pool.
+ const PoolCollection& pdpools = sub->getPools(Lease::TYPE_PD);
+ ASSERT_EQ(1, pdpools.size());
+ PoolPtr pdpool = pdpools.at(0);
+ ASSERT_TRUE(pdpool);
+
+ // Check prefix pool user context.
+ ConstElementPtr ctx_pdpool = pdpool->getContext();
+ ASSERT_TRUE(ctx_pdpool);
+ ASSERT_EQ(1, ctx_pdpool->size());
+ ASSERT_TRUE(ctx_pdpool->get("comment"));
+ EXPECT_EQ("\"A prefix pool\"", ctx_pdpool->get("comment")->str());
+
+ // The subnet has a host reservation.
+ uint8_t hw[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
+ ConstHostPtr host =
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->
+ get6(100, Host::IDENT_HWADDR, &hw[0], sizeof(hw));
+ ASSERT_TRUE(host);
+ EXPECT_EQ(Host::IDENT_HWADDR, host->getIdentifierType());
+ EXPECT_EQ("aa:bb:cc:dd:ee:ff", host->getHWAddress()->toText(false));
+ EXPECT_FALSE(host->getDuid());
+ EXPECT_EQ(SUBNET_ID_UNUSED, host->getIPv4SubnetID());
+ EXPECT_EQ(100, host->getIPv6SubnetID());
+ EXPECT_EQ("foo.example.com", host->getHostname());
+
+ // Check host user context.
+ ConstElementPtr ctx_host = host->getContext();
+ ASSERT_TRUE(ctx_host);
+ ASSERT_EQ(1, ctx_host->size());
+ ASSERT_TRUE(ctx_host->get("comment"));
+ EXPECT_EQ("\"A host reservation\"", ctx_host->get("comment")->str());
+
+ // The host reservation has an option data.
+ ConstCfgOptionPtr opts = host->getCfgOption6();
+ ASSERT_TRUE(opts);
+ EXPECT_FALSE(opts->empty());
+ const OptionDescriptor& host_desc =
+ opts->get(DHCP6_OPTION_SPACE, D6O_DOMAIN_SEARCH);
+ ASSERT_TRUE(host_desc.option_);
+ EXPECT_EQ(D6O_DOMAIN_SEARCH, host_desc.option_->getType());
+
+ // Check embedded option data user context.
+ ConstElementPtr ctx_host_desc = host_desc.getContext();
+ ASSERT_TRUE(ctx_host_desc);
+ ASSERT_EQ(1, ctx_host_desc->size());
+ ASSERT_TRUE(ctx_host_desc->get("comment"));
+ EXPECT_EQ("\"An option in a reservation\"",
+ ctx_host_desc->get("comment")->str());
+
+ // Finally dynamic DNS update configuration.
+ const D2ClientConfigPtr& d2 =
+ CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
+ ASSERT_TRUE(d2);
+ EXPECT_FALSE(d2->getEnableUpdates());
+
+ // Check dynamic DNS update configuration user context.
+ ConstElementPtr ctx_d2 = d2->getContext();
+ ASSERT_TRUE(ctx_d2);
+ ASSERT_EQ(1, ctx_d2->size());
+ ASSERT_TRUE(ctx_d2->get("comment"));
+ EXPECT_EQ("\"No dynamic DNS\"", ctx_d2->get("comment")->str());
+}
+
+// This test verifies that the global host reservations can be specified.
+TEST_F(Dhcp6ParserTest, globalReservations) {
+ ConstElementPtr x;
+ string config = "{ " + genIfaceConfig() + ",\n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"reservations\": [\n"
+ " {\n"
+ " \"duid\": \"01:02:03:04:05:06:07:08:09:0A\",\n"
+ " \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
+ " \"hostname\": \"\",\n"
+ " \"option-data\": [\n"
+ " {\n"
+ " \"name\": \"dns-servers\",\n"
+ " \"data\": \"2001:db8:2::1111\"\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"preference\",\n"
+ " \"data\": \"11\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"hw-address\": \"01:02:03:04:05:06\",\n"
+ " \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
+ " \"hostname\": \"\",\n"
+ " \"option-data\": [\n"
+ " {\n"
+ " \"name\": \"dns-servers\",\n"
+ " \"data\": \"2001:db8:2::abbc\"\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"preference\",\n"
+ " \"data\": \"25\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "],\n"
+ "\"subnet6\": [ \n"
+ " { \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"id\": 123,\n"
+ " \"reservations\": [\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"pools\": [ ],\n"
+ " \"subnet\": \"2001:db8:2::/64\", \n"
+ " \"id\": 234\n"
+ " },\n"
+ " {\n"
+ " \"pools\": [ ],\n"
+ " \"subnet\": \"2001:db8:3::/64\", \n"
+ " \"id\": 542\n"
+ " }\n"
+ "],\n"
+ "\"preferred-lifetime\": 3000,\n"
+ "\"valid-lifetime\": 4000 }\n";
+
+ ConstElementPtr json;
+ (json = parseDHCP6(config));
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ EXPECT_NO_THROW(x = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(x, 0);
+
+ // Make sure all subnets have been successfully configured. There is no
+ // need to sanity check the subnet properties because it should have
+ // been already tested by other tests.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size());
+
+ // Hosts configuration must be available.
+ CfgHostsPtr hosts_cfg = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(hosts_cfg);
+
+ // Let's create an object holding hardware address of the host having
+ // a reservation in the subnet having id of 234. For simplicity the
+ // address is a collection of numbers from 1 to 6.
+ std::vector<uint8_t> hwaddr;
+ for (unsigned int i = 1; i < 7; ++i) {
+ hwaddr.push_back(static_cast<uint8_t>(i));
+ }
+ // Retrieve the reservation and sanity check the address reserved.
+ ConstHostPtr host = hosts_cfg->get6(SUBNET_ID_GLOBAL, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size());
+ ASSERT_TRUE(host);
+ IPv6ResrvRange resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:2::abcd")),
+ resrv));
+ // This reservation should be solely assigned to the subnet 234,
+ // and not to other two.
+ EXPECT_FALSE(hosts_cfg->get6(123, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+ EXPECT_FALSE(hosts_cfg->get6(542, Host::IDENT_HWADDR,
+ &hwaddr[0], hwaddr.size()));
+ // Check that options are assigned correctly.
+ Option6AddrLstPtr opt_dns =
+ retrieveOption<Option6AddrLstPtr>(*host, D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ Option6AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("2001:db8:2::abbc", dns_addrs[0].toText());
+ OptionUint8Ptr opt_prf =
+ retrieveOption<OptionUint8Ptr>(*host, D6O_PREFERENCE);
+ ASSERT_TRUE(opt_prf);
+ EXPECT_EQ(25, static_cast<int>(opt_prf->getValue()));
+
+ // Do the same test for the DUID based reservation.
+ std::vector<uint8_t> duid;
+ for (unsigned int i = 1; i < 0xb; ++i) {
+ duid.push_back(static_cast<uint8_t>(i));
+ }
+ host = hosts_cfg->get6(SUBNET_ID_GLOBAL, Host::IDENT_DUID, &duid[0], duid.size());
+ ASSERT_TRUE(host);
+ resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:2::1234")),
+ resrv));
+ EXPECT_FALSE(hosts_cfg->get6(123, Host::IDENT_DUID, &duid[0], duid.size()));
+ EXPECT_FALSE(hosts_cfg->get6(542, Host::IDENT_DUID, &duid[0], duid.size()));
+ // Check that options are assigned correctly.
+ opt_dns = retrieveOption<Option6AddrLstPtr>(*host, D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns);
+ dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(1, dns_addrs.size());
+ EXPECT_EQ("2001:db8:2::1111", dns_addrs[0].toText());
+ opt_prf = retrieveOption<OptionUint8Ptr>(*host, D6O_PREFERENCE);
+ ASSERT_TRUE(opt_prf);
+ EXPECT_EQ(11, static_cast<int>(opt_prf->getValue()));
+}
+
+// This test verifies that configuration control info gets populated.
+TEST_F(Dhcp6ParserTest, configControlInfo) {
+ string config = PARSER_CONFIGS[8];
+
+ // Should be able to register a backend factory for "mysql".
+ ASSERT_TRUE(TestConfigBackendDHCPv6::
+ registerBackendType(ConfigBackendDHCPv6Mgr::instance(),
+ "mysql"));
+
+ // Should parse ok, now that the factory has been registered.
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+
+ // Make sure the config control info is there.
+ process::ConstConfigControlInfoPtr info =
+ CfgMgr::instance().getStagingCfg()->getConfigControlInfo();
+ ASSERT_TRUE(info);
+
+ // Fetch the list of config dbs. It should have two entries.
+ const process::ConfigDbInfoList& dblist = info->getConfigDatabases();
+ ASSERT_EQ(2, dblist.size());
+
+ // Make sure the entries are what we expect and in the right order.
+ // (DbAccessParser creates access strings with the keywords in
+ // alphabetical order).
+ EXPECT_EQ("name=keatest1 password=keatest type=mysql user=keatest",
+ dblist.front().getAccessString());
+ EXPECT_EQ("name=keatest2 password=keatest type=mysql user=keatest",
+ dblist.back().getAccessString());
+
+ // Verify that the config-fetch-wait-time is correct.
+ EXPECT_FALSE(info->getConfigFetchWaitTime().unspecified());
+ EXPECT_EQ(10, info->getConfigFetchWaitTime().get());
+}
+
+// Check whether it is possible to configure server-tag
+TEST_F(Dhcp6ParserTest, serverTag) {
+ // Config without server-tag
+ string config_no_tag = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ ] "
+ "}";
+
+ // Config with server-tag
+ string config_tag = "{ " + genIfaceConfig() + "," +
+ "\"server-tag\": \"boo\", "
+ "\"subnet6\": [ ] "
+ "}";
+
+ // Config with an invalid server-tag
+ string bad_tag = "{ " + genIfaceConfig() + "," +
+ "\"server-tag\": 777, "
+ "\"subnet6\": [ ] "
+ "}";
+
+ // Let's check the default. It should be empty.
+ ASSERT_TRUE(CfgMgr::instance().getStagingCfg()->getServerTag().empty());
+
+ // Configuration with no tag should default to an emtpy tag value.
+ configure(config_no_tag, CONTROL_RESULT_SUCCESS, "");
+ EXPECT_TRUE(CfgMgr::instance().getStagingCfg()->getServerTag().empty());
+
+ // Clear the config
+ CfgMgr::instance().clear();
+
+ // Configuration with the tag should have the tag value.
+ configure(config_tag, CONTROL_RESULT_SUCCESS, "");
+ EXPECT_EQ("boo", CfgMgr::instance().getStagingCfg()->getServerTag().get());
+
+ // Make sure a invalid server-tag fails to parse.
+ ASSERT_THROW(parseDHCP6(bad_tag), std::exception);
+}
+
+// Check whether it is possible to configure packet queue
+TEST_F(Dhcp6ParserTest, dhcpQueueControl) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ std::string mt_json_;
+ };
+ std::vector<Scenario> scenarios = {
+ {
+ "no entry",
+ "",
+ "",
+ },
+ {
+ "queue disabled",
+ "{ \n"
+ " \"enable-queue\": false \n"
+ "} \n",
+ R"("multi-threading": {
+ "enable-multi-threading": false
+ })",
+ },
+ {
+ "queue enabled at first, but gets forcefully disabled by MT",
+ "{ \n"
+ " \"enable-queue\": true \n"
+ "} \n",
+ R"("multi-threading": {
+ "enable-multi-threading": true
+ })",
+ },
+ {
+ "queue disabled, arbitrary content allowed",
+ "{ \n"
+ " \"enable-queue\": false, \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n",
+ R"("multi-threading": {
+ "enable-multi-threading": false
+ })",
+ },
+ {
+ "queue enabled, with queue-type",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\" \n"
+ "} \n",
+ R"("multi-threading": {
+ "enable-multi-threading": false
+ })",
+ },
+ {
+ "queue enabled with queue-type and arbitrary content",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\", \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n",
+ R"("multi-threading": {
+ "enable-multi-threading": false
+ })",
+ }
+ };
+
+ // Let's check the default. It should be empty.
+ data::ConstElementPtr staged_control;
+ staged_control = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
+ ASSERT_FALSE(staged_control);
+
+ // Iterate over the valid scenarios and verify they succeed.
+ data::ElementPtr exp_control;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ // Clear the config
+ CfgMgr::instance().clear();
+
+ // Construct the config JSON
+ std::stringstream os;
+ os << "{ " + genIfaceConfig();
+ if (!scenario.json_.empty()) {
+ os << ",\n \"dhcp-queue-control\": " << scenario.json_;
+ }
+ if (!scenario.mt_json_.empty()) {
+ os << ",\n" << scenario.mt_json_;
+ }
+ os << "\n}\n";
+
+ // Configure the server. This should succeed.
+ configure(os.str(), CONTROL_RESULT_SUCCESS, "");
+
+ // Fetch the queue control info.
+ staged_control = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
+
+ // Make sure the staged queue config exists.
+ ASSERT_TRUE(staged_control);
+
+ // Now build the expected queue control content.
+ if (scenario.json_.empty()) {
+ exp_control = Element::createMap();
+ } else {
+ try {
+ exp_control = boost::const_pointer_cast<Element>(Element::fromJSON(scenario.json_));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << " cannot convert expected JSON, test is broken:"
+ << ex.what();
+ }
+ }
+
+ // Add the defaults to expected queue control.
+ SimpleParser6::setDefaults(exp_control, SimpleParser6::DHCP_QUEUE_CONTROL6_DEFAULTS);
+
+ // This specific scenario is the only one where we expect enable-queue
+ // to be changed from what the user set it to.
+ if (scenario.description_ == "queue enabled at first, but gets forcefully disabled by MT") {
+ exp_control->set("enable-queue", Element::create(false));
+ }
+
+ // Verify that the staged queue control equals the expected queue control.
+ EXPECT_TRUE(staged_control->equals(*exp_control))
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << isc::test::generateDiff(prettyPrint(staged_control), prettyPrint(exp_control)) << "\n"
+#endif
+ ;
+ }
+ }
+}
+
+// Check that we catch invalid dhcp-queue-control content
+TEST_F(Dhcp6ParserTest, dhcpQueueControlInvalid) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ std::string exp_error_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "not a map",
+ "75 \n",
+ "<string>:2.24-25: syntax error, unexpected integer, expecting {"
+ },
+ {
+ "enable-queue missing",
+ "{ \n"
+ " \"enable-type\": \"some-type\" \n"
+ "} \n",
+ "missing parameter 'enable-queue' (<string>:2:2) "
+ "[dhcp-queue-control map between <string>:2:24 and <string>:4:1]"
+ },
+ {
+ "enable-queue not boolean",
+ "{ \n"
+ " \"enable-queue\": \"always\" \n"
+ "} \n",
+ "<string>:3.20-27: syntax error, unexpected constant string, "
+ "expecting boolean"
+ },
+ {
+ "queue enabled, type not a string",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": 7777 \n"
+ "} \n",
+ "<string>:4.18-21: syntax error, unexpected integer, "
+ "expecting constant string"
+ }
+ };
+
+ // Iterate over the incorrect scenarios and verify they
+ // fail as expected. Note, we use parseDHCP6() directly
+ // as all of the errors above are enforced by the grammar.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ // Construct the config JSON
+ std::stringstream os;
+ os << "{ " + genIfaceConfig();
+ os << ",\n \"dhcp-queue-control\": " << scenario.json_;
+ os << "} \n";
+
+ std::string error_msg = "";
+ try {
+ ASSERT_TRUE(parseDHCP6(os.str(), false)) << "parser returned empty element";
+ } catch(const std::exception& ex) {
+ error_msg = ex.what();
+ }
+
+ ASSERT_FALSE(error_msg.empty()) << "parseDHCP6 should have thrown";
+ EXPECT_EQ(scenario.exp_error_, error_msg);
+ }
+ }
+}
+
+// Verifies the value of store-extended-info for subnets when there
+// is a global value defined.
+TEST_F(Dhcp6ParserTest, storeExtendedInfoGlobal) {
+ const string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"store-extended-info\": true,"
+ "\"subnet6\": [ "
+ "{ "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"store-extended-info\": false"
+ "},"
+ "{"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::ffff\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\" "
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json = parseJSON(config);
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ // First subnet should override the global value.
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ Subnet6Ptr subnet = cfg->selectSubnet(IOAddress("2001:db8:1::"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getStoreExtendedInfo());
+
+ // Second subnet should use the global value.
+ subnet = cfg->selectSubnet(IOAddress("2001:db8:2::"));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getStoreExtendedInfo());
+}
+
+// Verifies the value of store-extended-info for subnets when there
+// is no global value defined.
+TEST_F(Dhcp6ParserTest, storeExtendedInfoNoGlobal) {
+ const string config = "{ " + genIfaceConfig() + ","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ "{ "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ "},"
+ "{"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::ffff\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"store-extended-info\": true"
+ "} ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json = parseJSON(config);
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ // First subnet should use global default.
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ Subnet6Ptr subnet = cfg->selectSubnet(IOAddress("2001:db8:1::"));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getStoreExtendedInfo());
+
+ // Second subnet should use its own value.
+ subnet = cfg->selectSubnet(IOAddress("2001:db8:2::"));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getStoreExtendedInfo());
+}
+
+/// This test checks that the statistic-default-sample-count and age
+/// global parameters are committed to the stats manager as expected.
+TEST_F(Dhcp6ParserTest, statsDefaultLimits) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"statistic-default-sample-count\": 10, "
+ "\"statistic-default-sample-age\": 5, "
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ CfgMgr::instance().commit();
+
+ stats::StatsMgr& stats_mgr = stats::StatsMgr::instance();
+ EXPECT_EQ(10, stats_mgr.getMaxSampleCountDefault());
+ EXPECT_EQ("00:00:05",
+ util::durationToText(stats_mgr.getMaxSampleAgeDefault(), 0));
+}
+
+// This test checks that using default multi threading settings works.
+TEST_F(Dhcp6ParserTest, multiThreadingDefaultSettings) {
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ ConstElementPtr cfg = CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading();
+ ASSERT_TRUE(cfg);
+
+ std::string content_json =
+ "{"
+ " \"enable-multi-threading\": true,\n"
+ " \"thread-pool-size\": 0,\n"
+ " \"packet-queue-size\": 64\n"
+ "}";
+ ConstElementPtr param;
+ ASSERT_NO_THROW(param = Element::fromJSON(content_json))
+ << "invalid context_json, test is broken";
+ ASSERT_TRUE(param->equals(*cfg))
+ << "expected: " << *(param) << std::endl
+ << " actual: " << *(cfg) << std::endl;
+}
+
+// This test checks that adding multi threading settings works.
+TEST_F(Dhcp6ParserTest, multiThreadingSettings) {
+ std::string content_json =
+ "{"
+ " \"enable-multi-threading\": true,\n"
+ " \"thread-pool-size\": 48,\n"
+ " \"packet-queue-size\": 1024\n"
+ "}";
+ std::string config = "{ " + genIfaceConfig() + "," +
+ "\"subnet6\": [ ], "
+ "\"multi-threading\": " + content_json + "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = Dhcpv6SrvTest::configure(srv_, json));
+ checkResult(status, 0);
+
+ ConstElementPtr cfg = CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading();
+ ASSERT_TRUE(cfg);
+
+ ConstElementPtr param;
+ ASSERT_NO_THROW(param = Element::fromJSON(content_json))
+ << "invalid context_json, test is broken";
+ ASSERT_TRUE(param->equals(*cfg))
+ << "expected: " << *(param) << std::endl
+ << " actual: " << *(cfg) << std::endl;
+}
+
+// Verifies that client class definitions may specify
+// valid and preferred lifetime triplets.
+TEST_F(Dhcp6ParserTest, clientClassValidPreferredLifetime) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"client-classes\" : [ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"min-valid-lifetime\": 1000, \n"
+ " \"valid-lifetime\": 2000, \n"
+ " \"max-valid-lifetime\": 3000, \n"
+ " \"min-preferred-lifetime\": 4000, \n"
+ " \"preferred-lifetime\": 5000, \n"
+ " \"max-preferred-lifetime\": 6000 \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " } \n"
+ "], \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ] \n"
+ "} \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW_LOG(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // We check staging config because CfgMgr::commit hasn't been executed.
+ ClientClassDictionaryPtr dictionary;
+ dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ASSERT_TRUE(dictionary);
+ EXPECT_EQ(2, dictionary->getClasses()->size());
+
+ // Execute the commit
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Verify that after commit, the current config has the correct dictionary
+ dictionary = CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ ASSERT_TRUE(dictionary);
+ EXPECT_EQ(2, dictionary->getClasses()->size());
+
+ ClientClassDefPtr class_def = dictionary->findClass("one");
+ ASSERT_TRUE(class_def);
+ EXPECT_EQ(class_def->getValid().getMin(), 1000);
+ EXPECT_EQ(class_def->getValid().get(), 2000);
+ EXPECT_EQ(class_def->getValid().getMax(), 3000);
+
+ EXPECT_EQ(class_def->getPreferred().getMin(), 4000);
+ EXPECT_EQ(class_def->getPreferred().get(), 5000);
+ EXPECT_EQ(class_def->getPreferred().getMax(), 6000);
+
+ class_def = dictionary->findClass("two");
+ ASSERT_TRUE(class_def);
+ EXPECT_TRUE(class_def->getValid().unspecified());
+}
+
+// Verifies that template client class definitions may specify
+// valid and preferred lifetime triplets.
+TEST_F(Dhcp6ParserTest, templateClientClassValidPreferredLifetime) {
+ string config = "{ " + genIfaceConfig() + "," +
+ "\"client-classes\" : [ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"min-valid-lifetime\": 1000, \n"
+ " \"valid-lifetime\": 2000, \n"
+ " \"max-valid-lifetime\": 3000, \n"
+ " \"min-preferred-lifetime\": 4000, \n"
+ " \"preferred-lifetime\": 5000, \n"
+ " \"max-preferred-lifetime\": 6000, \n"
+ " \"template-test\": \"''\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-test\": \"''\" \n"
+ " } \n"
+ "], \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ] \n"
+ "} \n";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = parseDHCP6(config));
+ extractConfig(config);
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW_LOG(status = Dhcpv6SrvTest::configure(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // We check staging config because CfgMgr::commit hasn't been executed.
+ ClientClassDictionaryPtr dictionary;
+ dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ASSERT_TRUE(dictionary);
+ EXPECT_EQ(2, dictionary->getClasses()->size());
+
+ // Execute the commit
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Verify that after commit, the current config has the correct dictionary
+ dictionary = CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ ASSERT_TRUE(dictionary);
+ EXPECT_EQ(2, dictionary->getClasses()->size());
+
+ ClientClassDefPtr class_def = dictionary->findClass("one");
+ ASSERT_TRUE(class_def);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(class_def.get()));
+ EXPECT_EQ(class_def->getValid().getMin(), 1000);
+ EXPECT_EQ(class_def->getValid().get(), 2000);
+ EXPECT_EQ(class_def->getValid().getMax(), 3000);
+
+ EXPECT_EQ(class_def->getPreferred().getMin(), 4000);
+ EXPECT_EQ(class_def->getPreferred().get(), 5000);
+ EXPECT_EQ(class_def->getPreferred().getMax(), 6000);
+
+ class_def = dictionary->findClass("two");
+ ASSERT_TRUE(class_def);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(class_def.get()));
+ EXPECT_TRUE(class_def->getValid().unspecified());
+}
+
+} // namespace
diff --git a/src/bin/dhcp6/tests/confirm_unittest.cc b/src/bin/dhcp6/tests/confirm_unittest.cc
new file mode 100644
index 0000000..f100c2b
--- /dev/null
+++ b/src/bin/dhcp6/tests/confirm_unittest.cc
@@ -0,0 +1,348 @@
+// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+#include <dhcpsrv/utils.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Confirm tests.
+///
+/// - Configuration 0:
+/// - only addresses (no prefixes)
+/// - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::/64
+/// - 1 subnet for eth0 and 1 subnet for eth1
+///
+/// - Configuration 1:
+/// - similar to Configuration 0
+/// - pools configured: 3000:1::/64 and 3000:2::/64
+/// - this specific configuration is used by tests using relays
+///
+const char* CONFIRM_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"3000:1::/64\" } ],"
+ " \"subnet\": \"3000:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"3000:2::/64\" } ],"
+ " \"subnet\": \"3000:2::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+};
+
+/// @brief Test fixture class for testing Confirm..
+class ConfirmTest : public isc::dhcp::test::Dhcpv6MessageTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ ConfirmTest()
+ : Dhcpv6MessageTest() {
+ }
+
+};
+
+
+// Test that client-id is mandatory and server-id forbidden for Confirm messages
+TEST_F(ConfirmTest, sanityCheck) {
+ NakedDhcpv6Srv srv(0);
+
+ // A message with no client-id should fail
+ Pkt6Ptr confirm = Pkt6Ptr(new Pkt6(DHCPV6_CONFIRM, 1234));
+ EXPECT_FALSE(srv.sanityCheck(confirm));
+
+ // A message with a single client-id should succeed
+ OptionPtr clientid = generateClientId();
+ confirm->addOption(clientid);
+ EXPECT_TRUE(srv.sanityCheck(confirm));
+
+ // A message with server-id present should fail
+ confirm->addOption(srv.getServerID());
+ EXPECT_FALSE(srv.sanityCheck(confirm));
+}
+
+// Test that directly connected client's Confirm message is processed and Reply
+// message is sent back. In this test case, the client sends Confirm for two
+// addresses that belong to the same IAID and are sent within the same IA_NA
+// option (RFC 8415, section 18.3.3).
+TEST_F(ConfirmTest, directClientSameIAID) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(CONFIRM_CONFIGS[0], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client1 = client.getLease(0);
+ // Clone the lease and modify its address so as it is still in the range
+ // of the subnet to which the first lease belongs. When the client sends
+ // the Confirm it should include both addresses and the server should
+ // send Success because both of these addresses are on-link, regardless
+ // what the server has in the lease database.
+ Lease6 lease_client2 = lease_client1;
+ lease_client2.addr_ = bumpAddress(lease_client2.addr_);
+ client.createLease(lease_client2);
+ ASSERT_EQ(2, client.getLeaseNum());
+ // Send Confirm message to the server.
+ ASSERT_NO_THROW(client.doConfirm());
+ // Client should have received a status code option and this option should
+ // indicate the success.
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_Success, client.getStatusCode());
+
+ ASSERT_EQ(2, client.getLeaseNum());
+ lease_client2 = client.getLease(1);
+ lease_client2.addr_ = bumpSubnet(lease_client2.addr_);
+ client.createLease(lease_client2);
+ // Send confirm to the server. This time, one of the leases contains the
+ // address which doesn't belong to the configured subnet and the server
+ // should respond with STATUS_NotOnLink.
+ ASSERT_NO_THROW(client.doConfirm());
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_NotOnLink, client.getStatusCode());
+ // Make sure that the server id has been included.
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+}
+
+// Test that directly connected client's Confirm message is processed and Reply
+// message is sent back. In this test case, the client sends Confirm for two
+// addresses that belong to different IAIDs and are sent within the different
+// IA_NA options (RFC 8415, section 18.3.3).
+TEST_F(ConfirmTest, directClientDifferentIAID) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(CONFIRM_CONFIGS[0], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client1 = client.getLease(0);
+ // Clone the lease and modify its address so as it is still in the range
+ // of the subnet to which the first lease belongs. When the client sends
+ // the Confirm it should include both addresses and the server should
+ // send Success because both of these addresses are on-link, regardless
+ // what the server has in the lease database.
+ Lease6 lease_client2 = lease_client1;
+ ++lease_client2.iaid_;
+ lease_client2.addr_ = bumpAddress(lease_client2.addr_);
+ client.createLease(lease_client2);
+ ASSERT_EQ(2, client.getLeaseNum());
+ // Send Confirm message to the server.
+ ASSERT_NO_THROW(client.doConfirm());
+ // Client should have received a status code option and this option should
+ // indicate the success.
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_Success, client.getStatusCode());
+ // Make sure that the server id and client id have been included.
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_CLIENTID));
+
+ ASSERT_EQ(2, client.getLeaseNum());
+ lease_client2 = client.getLease(1);
+ lease_client2.addr_ = bumpSubnet(lease_client2.addr_);
+ client.createLease(lease_client2);
+ // Send confirm to the server. This time, one of the leases contains the
+ // address which doesn't belong to the configured subnet and the server
+ // should respond with STATUS_NotOnLink.
+ ASSERT_NO_THROW(client.doConfirm());
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_NotOnLink, client.getStatusCode());
+ // Make sure that the server id have been included.
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_CLIENTID));
+}
+
+
+// Test that relayed client's Confirm message is processed and Reply message
+// is sent back (RFC 8415, section 18.3.3).
+TEST_F(ConfirmTest, relayedClient) {
+ Dhcp6Client client;
+ // Client to send relayed message.
+ client.useRelay();
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(CONFIRM_CONFIGS[1], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client1 = client.getLease(0);
+ // Clone the lease and modify its address so as it is still in the range
+ // of the subnet to which the first lease belongs. When the client sends
+ // the Confirm it should include both addresses and the server should
+ // send Success because both of these addresses are on-link, regardless
+ // what the server has in the lease database.
+ Lease6 lease_client2 = lease_client1;
+ lease_client2.addr_ = bumpAddress(lease_client2.addr_);
+ ++lease_client2.iaid_;
+ client.createLease(lease_client2);
+ // Send Confirm message to the server.
+ ASSERT_NO_THROW(client.doConfirm());
+ // Client should have received a status code option and this option should
+ // indicate the success.
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_Success, client.getStatusCode());
+
+ lease_client2 = client.getLease(1);
+ lease_client2.addr_ = bumpSubnet(lease_client2.addr_);
+ client.createLease(lease_client2);
+ // Send confirm to the server. This time, one of the leases contains the
+ // address which doesn't belong to the configured subnet and the server
+ // should respond with STATUS_NotOnLink.
+ ASSERT_NO_THROW(client.doConfirm());
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_NotOnLink, client.getStatusCode());
+ // Make sure that the server id and client id have been included.
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_CLIENTID));
+}
+
+// Test that the Confirm message without any addresses is discarded
+// (RFC 8415, section 18.3.3).
+TEST_F(ConfirmTest, relayedClientNoAddress) {
+ Dhcp6Client client;
+ // Configure the server.
+ configure(CONFIRM_CONFIGS[1], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+ // Client to send relayed message.
+ client.useRelay();
+ // Send Confirm message to the server. This message will contain no
+ // addresses because client has no leases.
+ ASSERT_NO_THROW(client.doConfirm());
+ EXPECT_FALSE(client.getContext().response_);
+}
+
+// This test checks that the server processes Confirm message correctly if
+// the subnet can't be selected for the client (RFC 8415, section 18.3.3).
+TEST_F(ConfirmTest, relayedClientNoSubnet) {
+ Dhcp6Client client;
+ // Client to send relayed message.
+ client.useRelay();
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(CONFIRM_CONFIGS[1], 2, client));
+ // Now that the client has a lease, let's remove any subnets to check
+ // how the server would respond to the Confirm.
+ ASSERT_NO_THROW(CfgMgr::instance().clear());
+ // Send Confirm message to the server.
+ ASSERT_NO_THROW(client.doConfirm());
+ // Client should have received a status code option and this option should
+ // indicate that the client is NotOnLink because subnet could not be
+ // selected.
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_NotOnLink, client.getStatusCode());
+
+ // Let's test another case that the client sends no addresses in the Confirm
+ // message. The subnet can't be selected for that client as in the previous
+ // case but this time the server must discard the client's message because
+ // it contains no addresses (is invalid).
+
+ // Set lifetimes to 0 so as the Confirm will ignore the specific address
+ // and send an empty IA_NA.
+ client.config_.leases_[0].preferred_lft_ = 0;
+ client.config_.leases_[0].valid_lft_ = 0;
+ ASSERT_NO_THROW(client.doConfirm());
+ EXPECT_FALSE(client.getContext().response_);
+
+ // Do similar test but this time remove the lease so as no IA_NA option
+ // is sent.
+ client.config_.clear();
+ ASSERT_NO_THROW(client.doConfirm());
+ EXPECT_FALSE(client.getContext().response_);
+}
+
+// This test checks that the relayed Confirm message is processed by the server
+// when sent to unicast address.
+TEST_F(ConfirmTest, relayedUnicast) {
+ Dhcp6Client client;
+ // Client to send relayed message.
+ client.useRelay();
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(CONFIRM_CONFIGS[1], 2, client));
+ // Make sure we have got the lease.
+ ASSERT_GT(client.getLeaseNum(), 0);
+ client.setDestAddress(IOAddress("2001:db8:1::1"));
+ // Send Confirm message to the server.
+ ASSERT_NO_THROW(client.doConfirm());
+ // Client should have received a response.
+ ASSERT_TRUE(client.getContext().response_);
+ // Client should have received a status code option and this option should
+ // indicate the success.
+ ASSERT_TRUE(client.receivedStatusCode());
+ ASSERT_EQ(STATUS_Success, client.getStatusCode());
+ // Make sure that the server id and client id have been included.
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_SERVERID));
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_CLIENTID));
+}
+
+// This test checks that the Confirm message is discarded by the server if it
+// has been sent to unicast address (RFC 8415, section 18.3.3).
+TEST_F(ConfirmTest, unicast) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(CONFIRM_CONFIGS[0], 2, client));
+ // Make sure the client has got the lease.
+ ASSERT_GT(client.getLeaseNum(), 0);
+ // Send Confirm message to the server to the unicast address.
+ client.setDestAddress(IOAddress("2001:db8:1::1"));
+ ASSERT_NO_THROW(client.doConfirm());
+ // Mak sure that the server discarded client's Confirm message.
+ EXPECT_FALSE(client.getContext().response_);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
new file mode 100644
index 0000000..f05a402
--- /dev/null
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -0,0 +1,2261 @@
+// Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <hooks/hooks_manager.h>
+#include <log/logger_support.h>
+#include <stats/stats_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/io_utils.h>
+#include <testutils/unix_control_client.h>
+#include <testutils/sandbox.h>
+#include <util/chrono_time_utils.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+#include <thread>
+
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <cstdlib>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace isc::stats;
+using namespace isc::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Simple RAII class which stops IO service upon destruction
+/// of the object.
+class IOServiceWork {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Pointer to the IO service to be stopped.
+ explicit IOServiceWork(const IOServicePtr& io_service)
+ : io_service_(io_service) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Stops IO service.
+ ~IOServiceWork() {
+ io_service_->stop();
+ }
+
+private:
+
+ /// @brief Pointer to the IO service to be stopped upon destruction.
+ IOServicePtr io_service_;
+
+};
+
+class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
+ // "Naked" DHCPv6 server, exposes internal fields
+public:
+ NakedControlledDhcpv6Srv() : ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) {
+ CfgMgr::instance().setFamily(AF_INET6);
+ }
+
+ /// Expose internal methods for the sake of testing
+ using Dhcpv6Srv::receivePacket;
+ using Dhcpv6Srv::network_state_;
+};
+
+/// @brief Default control connection timeout.
+const size_t DEFAULT_CONNECTION_TIMEOUT = 10000;
+
+class CtrlDhcpv6SrvTest : public BaseServerTest {
+public:
+ CtrlDhcpv6SrvTest()
+ : BaseServerTest() {
+ reset();
+ }
+
+ virtual ~CtrlDhcpv6SrvTest() {
+ LeaseMgrFactory::destroy();
+ StatsMgr::instance().removeAll();
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT);
+
+ reset();
+ };
+
+ /// @brief Reset hooks data
+ ///
+ /// Resets the data for the hooks-related portion of the test by ensuring
+ /// that no libraries are loaded and that any marker files are deleted.
+ virtual void reset() {
+ // Unload any previously-loaded libraries.
+ EXPECT_TRUE(HooksManager::unloadLibraries());
+
+ // Get rid of any marker files.
+ static_cast<void>(remove(LOAD_MARKER_FILE));
+ static_cast<void>(remove(UNLOAD_MARKER_FILE));
+ IfaceMgr::instance().deleteAllExternalSockets();
+ CfgMgr::instance().clear();
+ }
+
+};
+
+class CtrlChannelDhcpv6SrvTest : public CtrlDhcpv6SrvTest {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Path to the UNIX socket being used to communicate with the server
+ std::string socket_path_;
+
+ /// @brief List of interfaces (defaults to "*").
+ std::string interfaces_;
+
+ /// @brief Pointer to the tested server object
+ boost::shared_ptr<NakedControlledDhcpv6Srv> server_;
+
+ /// @brief Default constructor
+ ///
+ /// Sets socket path to its default value.
+ CtrlChannelDhcpv6SrvTest() : interfaces_("\"*\"") {
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path_ = string(env) + "/kea6.sock";
+ } else {
+ socket_path_ = sandbox.join("/kea6.sock");
+ }
+ reset();
+ IfaceMgr::instance().setTestMode(false);
+ IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
+ IfaceMgr::instancePtr().get(), ph::_1));
+ }
+
+ /// @brief Destructor
+ ~CtrlChannelDhcpv6SrvTest() {
+ server_.reset();
+ reset();
+ IfaceMgr::instance().setTestMode(false);
+ IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
+ IfaceMgr::instancePtr().get(), ph::_1));
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().detectIfaces();
+ };
+
+ /// @brief Returns pointer to the server's IO service.
+ ///
+ /// @return Pointer to the server's IO service or null pointer if the server
+ /// hasn't been created.
+ IOServicePtr getIOService() {
+ return (server_ ? server_->getIOService() : IOServicePtr());
+ }
+
+ void createUnixChannelServer() {
+ static_cast<void>(::remove(socket_path_.c_str()));
+
+ // Just a simple config. The important part here is the socket
+ // location information.
+ std::string header =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [";
+
+ std::string body = "]"
+ " },"
+ " \"expired-leases-processing\": {"
+ " \"reclaim-timer-wait-time\": 60,"
+ " \"hold-reclaimed-time\": 500,"
+ " \"flush-reclaimed-timer-wait-time\": 60"
+ " },"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ ],"
+ " \"valid-lifetime\": 4000,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"";
+
+ std::string footer =
+ "\" },"
+ " \"lease-database\": {"
+ " \"type\": \"memfile\", \"persist\": false },"
+ " \"loggers\": [ {"
+ " \"name\": \"kea-dhcp6\","
+ " \"severity\": \"INFO\","
+ " \"debuglevel\": 0"
+ " } ]"
+ "}";
+
+ // Fill in the socket-name value with socket_path_ to
+ // make the actual configuration text.
+ std::string config_txt = header + interfaces_ + body + socket_path_ + footer;
+ ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+
+ // Parse the logger configuration explicitly into the staging config.
+ // Note this does not alter the current loggers, they remain in
+ // effect until we apply the logging config below. If no logging
+ // is supplied logging will revert to default logging.
+ server_->configureLogger(config, CfgMgr::instance().getStagingCfg());
+
+ // Let's apply the new logging. We do it early, so we'll be able to print
+ // out what exactly is wrong with the new config in case of problems.
+ CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
+
+ ConstElementPtr answer = server_->processConfig(config);
+
+ // Commit the configuration so any subsequent reconfigurations
+ // will only close the command channel if its configuration has
+ // changed.
+ CfgMgr::instance().commit();
+
+ ASSERT_TRUE(answer);
+
+ int status = 0;
+ ConstElementPtr txt = isc::config::parseAnswer(status, answer);
+ // This should succeed. If not, print the error message.
+ ASSERT_EQ(0, status) << txt->str();
+
+ // Now check that the socket was indeed open.
+ ASSERT_GT(isc::config::CommandMgr::instance().getControlSocketFD(), -1);
+ }
+
+ /// @brief Reset
+ void reset() {
+ CtrlDhcpv6SrvTest::reset();
+
+ // Remove unix socket file
+ static_cast<void>(::remove(socket_path_.c_str()));
+ }
+
+ /// @brief Conducts a command/response exchange via UnixCommandSocket
+ ///
+ /// This method connects to the given server over the given socket path.
+ /// If successful, it then sends the given command and retrieves the
+ /// server's response. Note that it calls the server's receivePacket()
+ /// method where needed to cause the server to process IO events on
+ /// control channel the control channel sockets.
+ ///
+ /// @param command the command text to execute in JSON form
+ /// @param response variable into which the received response should be
+ /// placed.
+ void sendUnixCommand(const std::string& command, std::string& response) {
+ response = "";
+ boost::scoped_ptr<UnixControlClient> client;
+ client.reset(new UnixControlClient());
+ ASSERT_TRUE(client);
+
+ // Connect to the server. This is expected to trigger server's acceptor
+ // handler when IOService::poll() is run.
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Send the command. This will trigger server's handler which receives
+ // data over the unix domain socket. The server will start sending
+ // response to the client.
+ ASSERT_TRUE(client->sendCommand(command));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Read the response generated by the server. Note that getResponse
+ // only fails if there an IO error or no response data was present.
+ // It is not based on the response content.
+ ASSERT_TRUE(client->getResponse(response));
+
+ // Now disconnect and process the close event
+ client->disconnectFromServer();
+
+ ASSERT_NO_THROW(getIOService()->poll());
+ }
+
+ /// @brief Checks response for list-commands
+ ///
+ /// This method checks if the list-commands response is generally sane
+ /// and whether specified command is mentioned in the response.
+ ///
+ /// @param rsp response sent back by the server
+ /// @param command command expected to be on the list.
+ void checkListCommands(const ConstElementPtr& rsp, const std::string& command) {
+ ConstElementPtr params;
+ int status_code = -1;
+ EXPECT_NO_THROW(params = parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+ ASSERT_TRUE(params);
+ ASSERT_EQ(Element::list, params->getType());
+
+ int cnt = 0;
+ for (size_t i = 0; i < params->size(); ++i) {
+ string tmp = params->get(i)->stringValue();
+ if (tmp == command) {
+ // Command found, but that's not enough. Need to continue working
+ // through the list to see if there are no duplicates.
+ cnt++;
+ }
+ }
+
+ // Exactly one command on the list is expected.
+ EXPECT_EQ(1, cnt) << "Command " << command << " not found";
+ }
+
+ /// @brief Check if the answer for write-config command is correct
+ ///
+ /// @param response_txt response in text form (as read from the control socket)
+ /// @param exp_status expected status (0 success, 1 failure)
+ /// @param exp_txt for success cases this defines the expected filename,
+ /// for failure cases this defines the expected error message
+ void checkConfigWrite(const std::string& response_txt, int exp_status,
+ const std::string& exp_txt = "") {
+
+ ConstElementPtr rsp;
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response_txt));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr params = parseAnswer(status, rsp);
+ EXPECT_EQ(exp_status, status);
+
+ if (exp_status == CONTROL_RESULT_SUCCESS) {
+ // Let's check couple things...
+
+ // The parameters must include filename
+ ASSERT_TRUE(params);
+ ASSERT_TRUE(params->get("filename"));
+ ASSERT_EQ(Element::string, params->get("filename")->getType());
+ EXPECT_EQ(exp_txt, params->get("filename")->stringValue());
+
+ // The parameters must include size. And the size
+ // must indicate some content.
+ ASSERT_TRUE(params->get("size"));
+ ASSERT_EQ(Element::integer, params->get("size")->getType());
+ int64_t size = params->get("size")->intValue();
+ EXPECT_LE(1, size);
+
+ // Now check if the file is really there and suitable for
+ // opening.
+ ifstream f(exp_txt, ios::binary | ios::ate);
+ ASSERT_TRUE(f.good());
+
+ // Now check that it is the correct size as reported.
+ EXPECT_EQ(size, static_cast<int64_t>(f.tellg()));
+
+ // Finally, check that it's really a JSON.
+ ElementPtr from_file = Element::fromJSONFile(exp_txt);
+ ASSERT_TRUE(from_file);
+ } else if (exp_status == CONTROL_RESULT_ERROR) {
+
+ // Let's check if the reason for failure was given.
+ ConstElementPtr text = rsp->get("text");
+ ASSERT_TRUE(text);
+ ASSERT_EQ(Element::string, text->getType());
+ EXPECT_EQ(exp_txt, text->stringValue());
+ } else {
+ ADD_FAILURE() << "Invalid expected status: " << exp_status;
+ }
+ }
+
+ /// @brief Handler for long command.
+ ///
+ /// It checks whether the received command is equal to the one specified
+ /// as an argument.
+ ///
+ /// @param expected_command String representing an expected command.
+ /// @param command_name Command name received by the handler.
+ /// @param arguments Command arguments received by the handler.
+ ///
+ /// @returns Success answer.
+ static ConstElementPtr
+ longCommandHandler(const std::string& expected_command,
+ const std::string& command_name,
+ const ConstElementPtr& arguments) {
+ // The handler is called with a command name and the structure holding
+ // command arguments. We have to rebuild the command from those
+ // two arguments so as it can be compared against expected_command.
+ ElementPtr entire_command = Element::createMap();
+ entire_command->set("command", Element::create(command_name));
+ entire_command->set("arguments", (arguments));
+
+ // The rebuilt command will have a different order of parameters so
+ // let's parse expected_command back to JSON to guarantee that
+ // both structures are built using the same order.
+ EXPECT_EQ(Element::fromJSON(expected_command)->str(),
+ entire_command->str());
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "long command received ok"));
+ }
+
+ /// @brief Command handler which generates long response
+ ///
+ /// This handler generates a large response (over 400kB). It includes
+ /// a list of randomly generated strings to make sure that the test
+ /// can catch out of order delivery.
+ static ConstElementPtr longResponseHandler(const std::string&,
+ const ConstElementPtr&) {
+ ElementPtr arguments = Element::createList();
+ for (unsigned i = 0; i < 80000; ++i) {
+ std::ostringstream s;
+ s << std::setw(5) << i;
+ arguments->add(Element::create(s.str()));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+};
+
+TEST_F(CtrlDhcpv6SrvTest, commands) {
+
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000))
+ );
+
+ // Use empty parameters list
+ ElementPtr params(new isc::data::MapElement());
+ int rcode = -1;
+
+ // Case 1: send bogus command
+ ConstElementPtr result = ControlledDhcpv6Srv::processCommand("blah", params);
+ ConstElementPtr comment = parseAnswer(rcode, result);
+ EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
+
+ // Case 2: send shutdown command without any parameters
+ result = ControlledDhcpv6Srv::processCommand("shutdown", params);
+ comment = parseAnswer(rcode, result);
+ EXPECT_EQ(0, rcode); // expect success
+
+ // Case 3: send shutdown command with exit-value parameter.
+ ConstElementPtr x(new isc::data::IntElement(77));
+ params->set("exit-value", x);
+
+ result = ControlledDhcpv6Srv::processCommand("shutdown", params);
+ comment = parseAnswer(rcode, result);
+ EXPECT_EQ(0, rcode); // expect success
+
+ // Exit value should match.
+ EXPECT_EQ(77, srv->getExitValue());
+}
+
+// Check that the "libreload" command will reload libraries
+TEST_F(CtrlChannelDhcpv6SrvTest, libreload) {
+ createUnixChannelServer();
+
+ // Ensure no marker files to start with.
+ ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Load two libraries
+ HookLibsCollection libraries;
+ libraries.push_back(make_pair(CALLOUT_LIBRARY_1, ConstElementPtr()));
+ HooksManager::loadLibraries(libraries);
+
+ // Check they are loaded.
+ HookLibsCollection loaded_libraries =
+ HooksManager::getLibraryInfo();
+ ASSERT_TRUE(libraries == loaded_libraries);
+
+ // ... which also included checking that the marker file created by the
+ // load functions exists and holds the correct value (of "12" - the
+ // first library appends "1" to the file, the second appends "2"). Also
+ // check that the unload marker file does not yet exist.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Now execute the "libreload" command. This should cause the libraries
+ // to unload and to reload.
+ std::string response;
+ sendUnixCommand("{ \"command\": \"libreload\" }", response);
+ EXPECT_EQ("{ \"result\": 0, "
+ "\"text\": \"Hooks libraries successfully reloaded"
+ " (WARNING: libreload is deprecated).\" }"
+ , response);
+
+ // Check that the libraries have unloaded and reloaded. The libraries are
+ // unloaded in the reverse order to which they are loaded. When they load,
+ // they should append information to the loading marker file.
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "1"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "11"));
+}
+
+// Check that the "libreload" command will fail to reload libraries which are
+// not compatible when multi-threading is enabled
+TEST_F(CtrlChannelDhcpv6SrvTest, libreloadFailMultiThreading) {
+ createUnixChannelServer();
+
+ // Ensure no marker files to start with.
+ ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Disable multi-threading to temporarily trick the hook manager
+ // into loading single-threaded libraries.
+ MultiThreadingMgr::instance().setMode(false);
+
+ // Load two libraries
+ HookLibsCollection libraries;
+ libraries.push_back(make_pair(CALLOUT_LIBRARY_1, ConstElementPtr()));
+ libraries.push_back(make_pair(CALLOUT_LIBRARY_2, ConstElementPtr()));
+ HooksManager::loadLibraries(libraries);
+
+ // Check they are loaded.
+ HookLibsCollection loaded_libraries =
+ HooksManager::getLibraryInfo();
+ ASSERT_TRUE(libraries == loaded_libraries);
+
+ // ... which also included checking that the marker file created by the
+ // load functions exists and holds the correct value (of "12" - the
+ // first library appends "1" to the file, the second appends "2"). Also
+ // check that the unload marker file does not yet exist.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Enable multi-threading before libreload command which should now fail
+ // as the second library is not multi-threading compatible.
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Now execute the "libreload" command. This should cause the libraries
+ // to unload and to reload.
+ std::string response;
+ sendUnixCommand("{ \"command\": \"libreload\" }", response);
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"Failed to reload hooks libraries "
+ "(WARNING: libreload is deprecated).\" }"
+ , response);
+
+ // Check that the libraries have unloaded and failed to reload. The
+ // libraries are unloaded in the reverse order to which they are loaded.
+ // When they load, they should append information to the loading marker
+ // file. Failing to load the second library will also unload the first
+ // library.
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "211"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "121"));
+}
+
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+// This test checks which commands are registered by the DHCPv6 server.
+TEST_F(CtrlDhcpv6SrvTest, commandsRegistration) {
+
+ ConstElementPtr list_cmds = createCommand("list-commands");
+ ConstElementPtr answer;
+
+ // By default the list should be empty (except the standard list-commands
+ // supported by the CommandMgr itself)
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+
+ // Created server should register several additional commands.
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0));
+ );
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+
+ ASSERT_TRUE(answer->get("arguments"));
+ std::string command_list = answer->get("arguments")->str();
+
+ EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"build-report\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-backend-pull\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-hash-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"leases-reclaim\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"libreload\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"server-tag-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-remove\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-remove-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-reset\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-reset-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-sample-age-set\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-sample-age-set-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-sample-count-set\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-sample-count-set-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"status-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
+
+ // Ok, and now delete the server. It should deregister its commands.
+ srv.reset();
+
+ // The list should be (almost) empty again.
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+}
+
+// Tests that the server properly responds to invalid commands sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelNegative) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"bogus\" }", response);
+ EXPECT_EQ("{ \"result\": 2,"
+ " \"text\": \"'bogus' command not supported.\" }", response);
+
+ sendUnixCommand("utter nonsense", response);
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"invalid first character u\" }",
+ response);
+}
+
+// Tests that the server properly responds to shutdown command sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelShutdown) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"shutdown\" }", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
+}
+
+// Check that the "config-set" command will replace current configuration
+TEST_F(CtrlChannelDhcpv6SrvTest, configSet) {
+ createUnixChannelServer();
+
+ // Define strings to permutate the config arguments
+ // (Note the line feeds makes errors easy to find)
+ string set_config_txt = "{ \"command\": \"config-set\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string dhcp6_cfg_txt =
+ " \"Dhcp6\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \n"
+ " \"preferred-lifetime\": 3000, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"renew-timer\": 1000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"lease-database\": { \n"
+ " \"type\": \"memfile\", \n"
+ " \"persist\":false, \n"
+ " \"lfc-interval\": 0 \n"
+ " }, \n"
+ " \"expired-leases-processing\": { \n"
+ " \"reclaim-timer-wait-time\": 0, \n"
+ " \"hold-reclaimed-time\": 0, \n"
+ " \"flush-reclaimed-timer-wait-time\": 0 \n"
+ " },"
+ " \"subnet6\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+ " \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+ " \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+ string bad_subnet =
+ " {\"comment\": \"3005::/64\", \"id\": 10, \n"
+ " \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+ string subnet_footer =
+ " ] \n";
+ string option_def =
+ " ,\"option-def\": [\n"
+ " {\n"
+ " \"name\": \"foo\",\n"
+ " \"code\": 163,\n"
+ " \"type\": \"uint32\",\n"
+ " \"array\": false,\n"
+ " \"record-types\": \"\",\n"
+ " \"space\": \"dhcp6\",\n"
+ " \"encapsulate\": \"\"\n"
+ " }\n"
+ "]\n";
+ string option_data =
+ " ,\"option-data\": [\n"
+ " {\n"
+ " \"name\": \"foo\",\n"
+ " \"code\": 163,\n"
+ " \"space\": \"dhcp6\",\n"
+ " \"csv-format\": true,\n"
+ " \"data\": \"12345\"\n"
+ " }\n"
+ "]\n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+ string logger_txt =
+ " ,\"loggers\": [ { \n"
+ " \"name\": \"kea\", \n"
+ " \"severity\": \"FATAL\", \n"
+ " \"output_options\": [{ \n"
+ " \"output\": \"/dev/null\" \n"
+ " }] \n"
+ " }] \n";
+
+ std::ostringstream os;
+
+ // Create a valid config with all the parts should parse
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << subnet_footer
+ << option_def
+ << option_data
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << logger_txt
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-set command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful. The config contains random
+ // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+ // hash will be different each time. As such, we can do simplified checks:
+ // - verify the "result": 0 is there
+ // - verify the "text": "Configuration successful." is there
+ EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+ EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+ // Check that the config was indeed applied.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ OptionDefinitionPtr def =
+ LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+ ASSERT_TRUE(def);
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-set command
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"subnet configuration failed: mandatory 'subnet' "
+ "parameter is missing for a subnet being configured (<wire>:20:17)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ def = LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
+ ASSERT_TRUE(def);
+
+ // Create a valid config with two subnets and no command channel.
+ // It should succeed, client should still receive the response
+ os.str("");
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Verify the control channel socket exists.
+ ASSERT_TRUE(fileExists(socket_path_));
+
+ // Send the config-set command.
+ sendUnixCommand(os.str(), response);
+
+ // Verify the control channel socket no longer exists.
+ EXPECT_FALSE(fileExists(socket_path_));
+
+ // Verify the configuration was successful. The config contains random
+ // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+ // hash will be different each time. As such, we can do simplified checks:
+ // - verify the "result": 0 is there
+ // - verify the "text": "Configuration successful." is there
+ EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+ EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ // Clean up after the test.
+ CfgMgr::instance().clear();
+}
+
+// Tests if the server returns its configuration using config-get.
+// Note there are separate tests that verify if toElement() called by the
+// config-get handler are actually converting the configuration correctly.
+TEST_F(CtrlChannelDhcpv6SrvTest, configGet) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"config-get\" }", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ // Ok, now roughly check if the response seems legit.
+ ASSERT_TRUE(cfg);
+ ASSERT_EQ(Element::map, cfg->getType());
+ EXPECT_TRUE(cfg->get("Dhcp6"));
+ EXPECT_TRUE(cfg->get("Dhcp6")->get("loggers"));
+}
+
+// Tests if the server returns the hash of its configuration using
+// config-hash-get.
+TEST_F(CtrlChannelDhcpv6SrvTest, configHashGet) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"config-hash-get\" }", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr args = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+ // The parseAnswer is trying to be smart with ignoring hash.
+ // But this time we really want to see the hash, so we'll retrieve
+ // the arguments manually.
+ args = rsp->get(CONTROL_ARGUMENTS);
+
+ // Ok, now roughly check if the response seems legit.
+ ASSERT_TRUE(args);
+ ASSERT_EQ(Element::map, args->getType());
+ ConstElementPtr hash = args->get("hash");
+ ASSERT_TRUE(hash);
+ ASSERT_EQ(Element::string, hash->getType());
+ // SHA-256 -> 64 hex digits.
+ EXPECT_EQ(64, hash->stringValue().size());
+}
+
+// Verify that the "config-test" command will do what we expect.
+TEST_F(CtrlChannelDhcpv6SrvTest, configTest) {
+ createUnixChannelServer();
+
+ // Define strings to permutate the config arguments
+ // (Note the line feeds makes errors easy to find)
+ string set_config_txt = "{ \"command\": \"config-set\" \n";
+ string config_test_txt = "{ \"command\": \"config-test\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string dhcp6_cfg_txt =
+ " \"Dhcp6\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \n"
+ " \"preferred-lifetime\": 3000, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"renew-timer\": 1000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"lease-database\": { \n"
+ " \"type\": \"memfile\", \n"
+ " \"persist\":false, \n"
+ " \"lfc-interval\": 0 \n"
+ " }, \n"
+ " \"expired-leases-processing\": { \n"
+ " \"reclaim-timer-wait-time\": 0, \n"
+ " \"hold-reclaimed-time\": 0, \n"
+ " \"flush-reclaimed-timer-wait-time\": 0 \n"
+ " },"
+ " \"subnet6\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"3002::/64\", \"id\": 1, \n"
+ " \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"3003::/64\", \"id\": 2, \n"
+ " \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+ string bad_subnet =
+ " {\"comment\": \"3005::/64\", \"id\": 10, \n"
+ " \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+ string subnet_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+ string logger_txt =
+ " ,\"loggers\": [ { \n"
+ " \"name\": \"kea\", \n"
+ " \"severity\": \"FATAL\", \n"
+ " \"output_options\": [{ \n"
+ " \"output\": \"/dev/null\" \n"
+ " }] \n"
+ " }] \n";
+
+ std::ostringstream os;
+
+ // Create a valid config with all the parts should parse
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << logger_txt
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-set command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful. The config contains random
+ // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+ // hash will be different each time. As such, we can do simplified checks:
+ // - verify the "result": 0 is there
+ // - verify the "text": "Configuration successful." is there
+ EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+ EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+ // Check that the config was indeed applied.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Send the config-test command
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"subnet configuration failed: mandatory 'subnet' parameter "
+ "is missing for a subnet being configured (<wire>:20:17)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a valid config with two subnets and no command channel.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ // Verify the control channel socket exists.
+ ASSERT_TRUE(fileExists(socket_path_));
+
+ // Send the config-test command.
+ sendUnixCommand(os.str(), response);
+
+ // Verify the control channel socket still exists.
+ EXPECT_TRUE(fileExists(socket_path_));
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration seems sane. "
+ "Control-socket, hook-libraries, and D2 configuration were "
+ "sanity checked, but not applied.\" }",
+ response);
+
+ // Check that the config was not applied.
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Clean up after the test.
+ CfgMgr::instance().clear();
+}
+
+// This test verifies that the DHCP server handles version-get commands
+TEST_F(CtrlChannelDhcpv6SrvTest, getVersion) {
+ createUnixChannelServer();
+
+ std::string response;
+
+ // Send the version-get command
+ sendUnixCommand("{ \"command\": \"version-get\" }", response);
+ EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+ EXPECT_TRUE(response.find("log4cplus") != string::npos);
+ EXPECT_FALSE(response.find("GTEST_VERSION") != string::npos);
+
+ // Send the build-report command
+ sendUnixCommand("{ \"command\": \"build-report\" }", response);
+ EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+ EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
+}
+
+// This test verifies that the DHCP server handles status-get commands
+TEST_F(CtrlChannelDhcpv6SrvTest, statusGet) {
+ createUnixChannelServer();
+
+ // start_ is initialized by init.
+ ASSERT_THROW(server_->init("/no/such/file"), BadValue);
+
+ std::string response_txt;
+
+ // Send the status-get command.
+ sendUnixCommand("{ \"command\": \"status-get\" }", response_txt);
+ ConstElementPtr response;
+ ASSERT_NO_THROW(response = Element::fromJSON(response_txt));
+ ASSERT_TRUE(response);
+ ASSERT_EQ(Element::map, response->getType());
+ EXPECT_EQ(2, response->size());
+ ConstElementPtr result = response->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(Element::integer, result->getType());
+ EXPECT_EQ(0, result->intValue());
+ ConstElementPtr arguments = response->get("arguments");
+ ASSERT_EQ(Element::map, arguments->getType());
+
+ // The returned pid should be the pid of our process.
+ auto found_pid = arguments->get("pid");
+ ASSERT_TRUE(found_pid);
+ EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+ // It is hard to check the actual uptime (and reload) as it is based
+ // on current time. Let's just make sure it is within a reasonable
+ // range.
+ auto found_uptime = arguments->get("uptime");
+ ASSERT_TRUE(found_uptime);
+ EXPECT_LE(found_uptime->intValue(), 5);
+ EXPECT_GE(found_uptime->intValue(), 0);
+
+ auto found_reload = arguments->get("reload");
+ ASSERT_TRUE(found_reload);
+ EXPECT_LE(found_reload->intValue(), 5);
+ EXPECT_GE(found_reload->intValue(), 0);
+
+ auto found_multi_threading = arguments->get("multi-threading-enabled");
+ ASSERT_TRUE(found_multi_threading);
+ EXPECT_TRUE(found_multi_threading->boolValue());
+
+ auto found_thread_count = arguments->get("thread-pool-size");
+ ASSERT_TRUE(found_thread_count);
+ // The default value varies between systems.
+ // Let's just make sure it's a positive value.
+ EXPECT_LE(0, found_thread_count->intValue());
+
+ auto found_queue_size = arguments->get("packet-queue-size");
+ ASSERT_TRUE(found_queue_size);
+ EXPECT_EQ(64, found_queue_size->intValue());
+
+ auto found_queue_stats = arguments->get("packet-queue-statistics");
+ ASSERT_TRUE(found_queue_stats);
+ EXPECT_FALSE(found_queue_stats->str().empty());
+
+ MultiThreadingMgr::instance().setMode(true);
+ MultiThreadingMgr::instance().setThreadPoolSize(4);
+ MultiThreadingMgr::instance().setPacketQueueSize(64);
+ sendUnixCommand("{ \"command\": \"status-get\" }", response_txt);
+ ASSERT_NO_THROW(response = Element::fromJSON(response_txt));
+ ASSERT_TRUE(response);
+ ASSERT_EQ(Element::map, response->getType());
+ EXPECT_EQ(2, response->size());
+ result = response->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(Element::integer, result->getType());
+ EXPECT_EQ(0, result->intValue());
+ arguments = response->get("arguments");
+ ASSERT_EQ(Element::map, arguments->getType());
+
+ // The returned pid should be the pid of our process.
+ found_pid = arguments->get("pid");
+ ASSERT_TRUE(found_pid);
+ EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+ // It is hard to check the actual uptime (and reload) as it is based
+ // on current time. Let's just make sure it is within a reasonable
+ // range.
+ found_uptime = arguments->get("uptime");
+ ASSERT_TRUE(found_uptime);
+ EXPECT_LE(found_uptime->intValue(), 5);
+ EXPECT_GE(found_uptime->intValue(), 0);
+
+ found_reload = arguments->get("reload");
+ ASSERT_TRUE(found_reload);
+ EXPECT_LE(found_reload->intValue(), 5);
+ EXPECT_GE(found_reload->intValue(), 0);
+
+ found_multi_threading = arguments->get("multi-threading-enabled");
+ ASSERT_TRUE(found_multi_threading);
+ EXPECT_TRUE(found_multi_threading->boolValue());
+
+ found_thread_count = arguments->get("thread-pool-size");
+ ASSERT_TRUE(found_thread_count);
+ EXPECT_EQ(found_thread_count->intValue(), 4);
+
+ found_queue_size = arguments->get("packet-queue-size");
+ ASSERT_TRUE(found_queue_size);
+ EXPECT_EQ(found_queue_size->intValue(), 64);
+
+ found_queue_stats = arguments->get("packet-queue-statistics");
+ ASSERT_TRUE(found_queue_stats);
+ ASSERT_EQ(Element::list, found_queue_stats->getType());
+ EXPECT_EQ(3, found_queue_stats->size());
+}
+
+// Checks that socket status exists in status-get responses.
+TEST_F(CtrlChannelDhcpv6SrvTest, statusGetSockets) {
+ // Create dummy interfaces to test socket status.
+ isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+ // Send the status-get command.
+ createUnixChannelServer();
+ string response_text;
+ sendUnixCommand(R"({ "command": "status-get" })", response_text);
+ ConstElementPtr response;
+ ASSERT_NO_THROW(response = Element::fromJSON(response_text));
+ ASSERT_TRUE(response);
+ ASSERT_EQ(Element::map, response->getType());
+ ConstElementPtr result(response->get("result"));
+ ASSERT_TRUE(result);
+ ASSERT_EQ(Element::integer, result->getType());
+ EXPECT_EQ(0, result->intValue());
+ ConstElementPtr arguments(response->get("arguments"));
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(Element::map, arguments->getType());
+
+ ConstElementPtr sockets(arguments->get("sockets"));
+ ASSERT_TRUE(sockets);
+ ASSERT_EQ(Element::map, sockets->getType());
+
+ ConstElementPtr status(sockets->get("status"));
+ ASSERT_TRUE(status);
+ ASSERT_EQ(Element::string, status->getType());
+ EXPECT_EQ("ready", status->stringValue());
+
+ ConstElementPtr errors(sockets->get("errors"));
+ ASSERT_FALSE(errors);
+}
+
+// Checks that socket status includes errors in status-get responses.
+TEST_F(CtrlChannelDhcpv6SrvTest, statusGetSocketsErrors) {
+ // Create dummy interfaces to test socket status and add a custom down interface.
+ isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+ test_config.addIface("down_interface", 4);
+ test_config.setIfaceFlags("down_interface", FlagLoopback(false), FlagUp(false),
+ FlagRunning(true), FlagInactive4(false),
+ FlagInactive6(false));
+
+ // Send the status-get command.
+ createUnixChannelServer();
+ string response_text;
+ sendUnixCommand(R"({ "command": "status-get" })", response_text);
+ ConstElementPtr response;
+ ASSERT_NO_THROW(response = Element::fromJSON(response_text));
+ ASSERT_TRUE(response);
+ ASSERT_EQ(Element::map, response->getType());
+ ConstElementPtr result(response->get("result"));
+ ASSERT_TRUE(result);
+ ASSERT_EQ(Element::integer, result->getType());
+ EXPECT_EQ(0, result->intValue());
+ ConstElementPtr arguments(response->get("arguments"));
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(Element::map, arguments->getType());
+
+ ConstElementPtr sockets(arguments->get("sockets"));
+ ASSERT_TRUE(sockets);
+ ASSERT_EQ(Element::map, sockets->getType());
+
+ ConstElementPtr status(sockets->get("status"));
+ ASSERT_TRUE(status);
+ ASSERT_EQ(Element::string, status->getType());
+ EXPECT_EQ("failed", status->stringValue());
+
+ ConstElementPtr errors(sockets->get("errors"));
+ ASSERT_TRUE(errors);
+ ASSERT_EQ(Element::list, errors->getType());
+ ASSERT_EQ(1, errors->size());
+
+ ConstElementPtr error(errors->get(0));
+ ASSERT_TRUE(error);
+ ASSERT_EQ(Element::string, error->getType());
+ ASSERT_EQ("the interface down_interface is down", error->stringValue());
+}
+
+// This test verifies that the DHCP server handles server-tag-get command
+TEST_F(CtrlChannelDhcpv6SrvTest, serverTagGet) {
+ createUnixChannelServer();
+
+ std::string response;
+ std::string expected;
+
+ // Send the server-tag-get command
+ sendUnixCommand("{ \"command\": \"server-tag-get\" }", response);
+ expected = "{ \"arguments\": { \"server-tag\": \"\" }, \"result\": 0 }";
+ EXPECT_EQ(expected, response);
+
+ // Set a value to the server tag
+ CfgMgr::instance().getCurrentCfg()->setServerTag("foobar");
+
+ // Retry...
+ sendUnixCommand("{ \"command\": \"server-tag-get\" }", response);
+ expected = "{ \"arguments\": { \"server-tag\": \"foobar\" }, \"result\": 0 }";
+}
+
+// This test verifies that the DHCP server handles config-backend-pull command
+TEST_F(CtrlChannelDhcpv6SrvTest, configBackendPull) {
+ createUnixChannelServer();
+
+ std::string response;
+ std::string expected;
+
+ // Send the config-backend-pull command. Note there is no configured backed.
+ sendUnixCommand("{ \"command\": \"config-backend-pull\" }", response);
+ expected = "{ \"result\": 3, \"text\": \"No config backend.\" }";
+ EXPECT_EQ(expected, response);
+}
+
+// This test verifies that the DHCP server immediately reclaims expired
+// leases on leases-reclaim command
+TEST_F(CtrlChannelDhcpv6SrvTest, controlLeasesReclaim) {
+ createUnixChannelServer();
+
+ // Create expired leases. Leases are expired by 40 seconds ago
+ // (valid lifetime = 60, cltt = now - 100).
+ DuidPtr duid0(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
+ Lease6Ptr lease0(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
+ duid0, 1, 50, 60, SubnetID(1)));
+ lease0->cltt_ = time(NULL) - 100;
+ DuidPtr duid1(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
+ Lease6Ptr lease1(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
+ duid1, 1, 50, 60, SubnetID(1)));
+ lease1->cltt_ = time(NULL) - 100;
+
+ // Add leases to the database.
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ ASSERT_NO_THROW(lease_mgr.addLease(lease0));
+ ASSERT_NO_THROW(lease_mgr.addLease(lease1));
+
+ // Make sure they have been added.
+ ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1")));
+ ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2")));
+
+ // No arguments
+ std::string response;
+ sendUnixCommand("{ \"command\": \"leases-reclaim\" }", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": "
+ "\"Missing mandatory 'remove' parameter.\" }", response);
+
+ // Bad argument name
+ sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+ "\"arguments\": { \"reclaim\": true } }", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": "
+ "\"Missing mandatory 'remove' parameter.\" }", response);
+
+ // Bad remove argument type
+ sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+ "\"arguments\": { \"remove\": \"bogus\" } }", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": "
+ "\"'remove' parameter expected to be a boolean.\" }", response);
+
+ // Send the command
+ sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+ "\"arguments\": { \"remove\": false } }", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": "
+ "\"Reclamation of expired leases is complete.\" }", response);
+
+ // Leases should be reclaimed, but not removed
+ ASSERT_NO_THROW(
+ lease0 = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
+ );
+ ASSERT_NO_THROW(
+ lease1 = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
+ );
+ ASSERT_TRUE(lease0);
+ ASSERT_TRUE(lease1);
+ EXPECT_TRUE(lease0->stateExpiredReclaimed());
+ EXPECT_TRUE(lease1->stateExpiredReclaimed());
+}
+
+// This test verifies that the DHCP server immediately reclaims expired
+// leases on leases-reclaim command with remove = true
+TEST_F(CtrlChannelDhcpv6SrvTest, controlLeasesReclaimRemove) {
+ createUnixChannelServer();
+
+ // Create expired leases. Leases are expired by 40 seconds ago
+ // (valid lifetime = 60, cltt = now - 100).
+ DuidPtr duid0(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
+ Lease6Ptr lease0(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
+ duid0, 1, 50, 60, SubnetID(1)));
+ lease0->cltt_ = time(NULL) - 100;
+ DuidPtr duid1(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
+ Lease6Ptr lease1(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
+ duid1, 1, 50, 60, SubnetID(1)));
+ lease1->cltt_ = time(NULL) - 100;
+
+ // Add leases to the database.
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ ASSERT_NO_THROW(lease_mgr.addLease(lease0));
+ ASSERT_NO_THROW(lease_mgr.addLease(lease1));
+
+ // Make sure they have been added.
+ ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1")));
+ ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2")));
+
+ // Send the command
+ std::string response;
+ sendUnixCommand("{ \"command\": \"leases-reclaim\", "
+ "\"arguments\": { \"remove\": true } }", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": "
+ "\"Reclamation of expired leases is complete.\" }", response);
+
+ // Leases should have been removed.
+ ASSERT_NO_THROW(
+ lease0 = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
+ );
+ ASSERT_NO_THROW(
+ lease1 = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
+ );
+ ASSERT_FALSE(lease0);
+ ASSERT_FALSE(lease1);
+}
+
+// Tests that the server properly responds to statistics commands. Note this
+// is really only intended to verify that the appropriate Statistics handler
+// is called based on the command. It is not intended to be an exhaustive
+// test of Dhcpv6 statistics.
+TEST_F(CtrlChannelDhcpv6SrvTest, controlChannelStats) {
+ createUnixChannelServer();
+ std::string response;
+
+ // Check statistic-get
+ sendUnixCommand("{ \"command\" : \"statistic-get\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\" }}", response);
+ EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", response);
+
+ // Check statistic-get-all
+ sendUnixCommand("{ \"command\" : \"statistic-get-all\", "
+ " \"arguments\": {}}", response);
+
+ std::set<std::string> initial_stats = {
+ "pkt6-received",
+ "pkt6-solicit-received",
+ "pkt6-advertise-received",
+ "pkt6-request-received",
+ "pkt6-reply-received",
+ "pkt6-renew-received",
+ "pkt6-rebind-received",
+ "pkt6-decline-received",
+ "pkt6-release-received",
+ "pkt6-infrequest-received",
+ "pkt6-dhcpv4-query-received",
+ "pkt6-dhcpv4-response-received",
+ "pkt6-unknown-received",
+ "pkt6-sent",
+ "pkt6-advertise-sent",
+ "pkt6-reply-sent",
+ "pkt6-dhcpv4-response-sent",
+ "pkt6-parse-failed",
+ "pkt6-receive-drop",
+ "v6-allocation-fail",
+ "v6-allocation-fail-shared-network",
+ "v6-allocation-fail-subnet",
+ "v6-allocation-fail-no-pools",
+ "v6-allocation-fail-classes",
+ "v6-ia-na-lease-reuses",
+ "v6-ia-pd-lease-reuses",
+ };
+
+ std::ostringstream s;
+ s << "{ \"arguments\": { ";
+ for (auto st = initial_stats.begin(); st != initial_stats.end();) {
+ s << "\"" << *st << "\": [ [ 0, \"";
+ s << isc::util::clockToText(StatsMgr::instance().getObservation(*st)->getInteger().second);
+ s << "\" ] ]";
+ if (++st != initial_stats.end()) {
+ s << ", ";
+ }
+ }
+ s << " }, \"result\": 0 }";
+
+ auto stats_get_all = s.str();
+
+ EXPECT_EQ(stats_get_all, response);
+
+ // Check statistic-reset
+ sendUnixCommand("{ \"command\" : \"statistic-reset\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\" }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-reset-all
+ sendUnixCommand("{ \"command\" : \"statistic-reset-all\", "
+ " \"arguments\": {}}", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": "
+ "\"All statistics reset to neutral values.\" }", response);
+
+ // Check statistic-remove
+ sendUnixCommand("{ \"command\" : \"statistic-remove\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\" }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-remove-all (deprecated)
+
+ // Check statistic-sample-age-set
+ sendUnixCommand("{ \"command\" : \"statistic-sample-age-set\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\", \"duration\": 1245 }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-sample-age-set-all
+ sendUnixCommand("{ \"command\" : \"statistic-sample-age-set-all\", "
+ " \"arguments\": {"
+ " \"duration\": 1245 }}", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics duration limit are set.\" }",
+ response);
+
+ // Check statistic-sample-count-set
+ sendUnixCommand("{ \"command\" : \"statistic-sample-count-set\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\", \"max-samples\": 100 }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-sample-count-set-all
+ sendUnixCommand("{ \"command\" : \"statistic-sample-count-set-all\", "
+ " \"arguments\": {"
+ " \"max-samples\": 100 }}", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics count limit are set.\" }",
+ response);
+}
+
+// Tests that the server properly responds to shutdown command sent
+// via ControlChannel
+TEST_F(CtrlChannelDhcpv6SrvTest, listCommands) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"list-commands\" }", response);
+
+ ConstElementPtr rsp;
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+
+ // We expect the server to report at least the following commands:
+ checkListCommands(rsp, "build-report");
+ checkListCommands(rsp, "config-backend-pull");
+ checkListCommands(rsp, "config-get");
+ checkListCommands(rsp, "config-hash-get");
+ checkListCommands(rsp, "config-reload");
+ checkListCommands(rsp, "config-set");
+ checkListCommands(rsp, "config-test");
+ checkListCommands(rsp, "config-write");
+ checkListCommands(rsp, "list-commands");
+ checkListCommands(rsp, "leases-reclaim");
+ checkListCommands(rsp, "libreload");
+ checkListCommands(rsp, "version-get");
+ checkListCommands(rsp, "server-tag-get");
+ checkListCommands(rsp, "shutdown");
+ checkListCommands(rsp, "statistic-get");
+ checkListCommands(rsp, "statistic-get-all");
+ checkListCommands(rsp, "statistic-remove");
+ checkListCommands(rsp, "statistic-remove-all");
+ checkListCommands(rsp, "statistic-reset");
+ checkListCommands(rsp, "statistic-reset-all");
+ checkListCommands(rsp, "statistic-sample-age-set");
+ checkListCommands(rsp, "statistic-sample-age-set-all");
+ checkListCommands(rsp, "statistic-sample-count-set");
+ checkListCommands(rsp, "statistic-sample-count-set-all");
+}
+
+// Tests if config-write can be called without any parameters.
+TEST_F(CtrlChannelDhcpv6SrvTest, configWriteNoFilename) {
+ createUnixChannelServer();
+ std::string response;
+
+ // This is normally set by the command line -c parameter.
+ server_->setConfigFile("test1.json");
+
+ // If the filename is not explicitly specified, the name used
+ // in -c command line switch is used.
+ sendUnixCommand("{ \"command\": \"config-write\" }", response);
+
+ checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test1.json");
+ ::remove("test1.json");
+}
+
+// Tests if config-write can be called with a valid filename as parameter.
+TEST_F(CtrlChannelDhcpv6SrvTest, configWriteFilename) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"config-write\", "
+ "\"arguments\": { \"filename\": \"test2.json\" } }", response);
+
+ checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test2.json");
+ ::remove("test2.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is missing.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadMissingFile) {
+ createUnixChannelServer();
+ std::string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("test6.json");
+
+ // Tell the server to reload its configuration. It should attempt to load
+ // test6.json (and fail, because the file is not there).
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload was rejected.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed: "
+ "configuration error using file 'test6.json': Unable to open file "
+ "test6.json\" }",
+ response);
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is not a valid JSON.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadBrokenFile) {
+ createUnixChannelServer();
+ std::string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("test7.json");
+
+ // Although Kea is smart, its AI routines are not smart enough to handle
+ // this one... at least not yet.
+ ofstream f("test7.json", ios::trunc);
+ f << "gimme some addrs, bro!";
+ f.close();
+
+ // Now tell Kea to reload its config.
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload will fail.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Config reload failed: "
+ "configuration error using file 'test7.json': "
+ "test7.json:1.1: Invalid character: g\" }",
+ response);
+
+ ::remove("test7.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is loaded correctly.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadValid) {
+ createUnixChannelServer();
+ std::string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("test8.json");
+
+ // Ok, enough fooling around. Let's create a valid config.
+ const std::string cfg_txt =
+ "{ \"Dhcp6\": {"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"subnet6\": ["
+ " { \"subnet\": \"2001:db8:1::/64\", \"id\": 1 },"
+ " { \"subnet\": \"2001:db8:2::/64\", \"id\": 2 }"
+ " ],"
+ " \"lease-database\": {"
+ " \"type\": \"memfile\", \"persist\": false }"
+ "} }";
+ ofstream f("test8.json", ios::trunc);
+ f << cfg_txt;
+ f.close();
+
+ // This command should reload test8.json config.
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the configuration was successful. The config contains random
+ // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+ // hash will be different each time. As such, we can do simplified checks:
+ // - verify the "result": 0 is there
+ // - verify the "text": "Configuration successful." is there
+ EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+ EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+ // Check that the config was indeed applied.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ ::remove("test8.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is loaded correctly.
+TEST_F(CtrlChannelDhcpv6SrvTest, configReloadDetectInterfaces) {
+ interfaces_ = "\"eth0\"";
+ IfacePtr eth0 = IfaceMgrTestConfig::createIface("eth0", ETH0_INDEX,
+ "11:22:33:44:55:66");
+ auto detectIfaces = [&](bool update_only) {
+ if (!update_only) {
+ eth0->addAddress(IOAddress("10.0.0.1"));
+ eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+ eth0->addAddress(IOAddress("2001:db8:1::1"));
+ IfaceMgr::instance().addInterface(eth0);
+ }
+ return (false);
+ };
+ IfaceMgr::instance().setDetectCallback(detectIfaces);
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().detectIfaces();
+ createUnixChannelServer();
+ std::string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("test8.json");
+
+ // Ok, enough fooling around. Let's create a valid config.
+ const std::string cfg_txt =
+ "{ \"Dhcp6\": {"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"eth1\" ]"
+ " },"
+ " \"subnet6\": ["
+ " { \"subnet\": \"2001:db8:1::/64\", \"id\": 1 },"
+ " { \"subnet\": \"2001:db8:2::/64\", \"id\": 2 }"
+ " ],"
+ " \"lease-database\": {"
+ " \"type\": \"memfile\", \"persist\": false }"
+ "} }";
+ ofstream f("test8.json", ios::trunc);
+ f << cfg_txt;
+ f.close();
+
+ IfacePtr eth1 = IfaceMgrTestConfig::createIface("eth1", ETH1_INDEX,
+ "AA:BB:CC:DD:EE:FF");
+ auto detectUpdateIfaces = [&](bool update_only) {
+ if (!update_only) {
+ eth1->addAddress(IOAddress("192.0.2.3"));
+ eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
+ eth1->addAddress(IOAddress("3001:db8:100::1"));
+ IfaceMgr::instance().addInterface(eth1);
+ }
+ return (false);
+ };
+ IfaceMgr::instance().setDetectCallback(detectUpdateIfaces);
+
+ // This command should reload test8.json config.
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the configuration was successful. The config contains random
+ // socket name (/tmp/kea-<value-changing-each-time>/kea6.sock), so the
+ // hash will be different each time. As such, we can do simplified checks:
+ // - verify the "result": 0 is there
+ // - verify the "text": "Configuration successful." is there
+ EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
+ EXPECT_NE(response.find("\"text\": \"Configuration successful.\""), std::string::npos);
+
+ // Check that the config was indeed applied.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(2, subnets->size());
+
+ ::remove("test8.json");
+}
+
+// This test verifies that disable DHCP service command performs sanity check on
+// parameters.
+TEST_F(CtrlChannelDhcpv6SrvTest, dhcpDisableBadParam) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-disable\","
+ " \"arguments\": {"
+ " \"max-period\": -3"
+ " }"
+ "}", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"'max-period' must be positive "
+ "integer\" }", response);
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-disable\","
+ " \"arguments\": {"
+ " \"origin\": \"\""
+ " }"
+ "}", response);
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"invalid value used for 'origin' "
+ "parameter: (empty string)\" }", response);
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-disable\","
+ " \"arguments\": {"
+ " \"origin\": \"test\""
+ " }"
+ "}", response);
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"invalid value used for 'origin' "
+ "parameter: test\" }", response);
+}
+
+// This test verifies if it is possible to disable DHCP service via command.
+TEST_F(CtrlChannelDhcpv6SrvTest, dhcpDisable) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"dhcp-disable\" }", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ EXPECT_FALSE(server_->network_state_->isServiceEnabled());
+
+ server_->network_state_->enableService(NetworkState::Origin::USER_COMMAND);
+
+ EXPECT_TRUE(server_->network_state_->isServiceEnabled());
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-disable\","
+ " \"arguments\": {"
+ " \"origin\": \"user\""
+ " }"
+ "}", response);
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ EXPECT_FALSE(server_->network_state_->isServiceEnabled());
+
+ server_->network_state_->enableService(NetworkState::Origin::USER_COMMAND);
+
+ EXPECT_TRUE(server_->network_state_->isServiceEnabled());
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-disable\","
+ " \"arguments\": {"
+ " \"origin\": \"ha-partner\""
+ " }"
+ "}", response);
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ EXPECT_FALSE(server_->network_state_->isServiceEnabled());
+
+ server_->network_state_->enableService(NetworkState::Origin::HA_COMMAND);
+
+ EXPECT_TRUE(server_->network_state_->isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable DHCP service for a short
+// period of time, after which the service is automatically enabled.
+TEST_F(CtrlChannelDhcpv6SrvTest, dhcpDisableTemporarily) {
+ createUnixChannelServer();
+ std::string response;
+
+ // Send a command to disable DHCP service for 3 seconds.
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-disable\","
+ " \"arguments\": {"
+ " \"max-period\": 3"
+ " }"
+ "}", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ // The service should be disabled.
+ EXPECT_FALSE(server_->network_state_->isServiceEnabled());
+ // And the timer should be scheduled which counts the time to automatic
+ // enabling of the service.
+ EXPECT_TRUE(server_->network_state_->isDelayedEnableAll());
+}
+
+// This test verifies that enable DHCP service command performs sanity check on
+// parameters.
+TEST_F(CtrlChannelDhcpv6SrvTest, dhcpEnableBadParam) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-enable\","
+ " \"arguments\": {"
+ " \"origin\": \"\""
+ " }"
+ "}", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"invalid value used for 'origin' "
+ "parameter: (empty string)\" }", response);
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-enable\","
+ " \"arguments\": {"
+ " \"origin\": \"test\""
+ " }"
+ "}", response);
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"invalid value used for 'origin' "
+ "parameter: test\" }", response);
+}
+
+// This test verifies if it is possible to enable DHCP service via command.
+TEST_F(CtrlChannelDhcpv6SrvTest, dhcpEnable) {
+ createUnixChannelServer();
+ std::string response;
+
+ sendUnixCommand("{ \"command\": \"dhcp-enable\" }", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ EXPECT_TRUE(server_->network_state_->isServiceEnabled());
+
+ server_->network_state_->disableService(NetworkState::Origin::USER_COMMAND);
+
+ EXPECT_FALSE(server_->network_state_->isServiceEnabled());
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-enable\","
+ " \"arguments\": {"
+ " \"origin\": \"user\""
+ " }"
+ "}", response);
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ EXPECT_TRUE(server_->network_state_->isServiceEnabled());
+
+ server_->network_state_->disableService(NetworkState::Origin::HA_COMMAND);
+
+ EXPECT_FALSE(server_->network_state_->isServiceEnabled());
+
+ sendUnixCommand("{"
+ " \"command\": \"dhcp-enable\","
+ " \"arguments\": {"
+ " \"origin\": \"ha-partner\""
+ " }"
+ "}", response);
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ EXPECT_TRUE(server_->network_state_->isServiceEnabled());
+}
+
+/// Verify that concurrent connections over the control channel can be
+/// established.
+/// @todo Future Kea 1.3 tickets will modify the behavior of the CommandMgr
+/// such that the server will be able to send response in multiple chunks.
+/// This test will need to be extended. For now, the receive and write
+/// operations are atomic and there is no conflict between concurrent
+/// connections.
+TEST_F(CtrlChannelDhcpv6SrvTest, concurrentConnections) {
+ createUnixChannelServer();
+
+ boost::scoped_ptr<UnixControlClient> client1(new UnixControlClient());
+ ASSERT_TRUE(client1);
+
+ boost::scoped_ptr<UnixControlClient> client2(new UnixControlClient());
+ ASSERT_TRUE(client2);
+
+ // Client 1 connects.
+ ASSERT_TRUE(client1->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Client 2 connects.
+ ASSERT_TRUE(client2->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Send the command while another client is connected.
+ ASSERT_TRUE(client2->sendCommand("{ \"command\": \"list-commands\" }"));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ std::string response;
+ // The server should respond ok.
+ ASSERT_TRUE(client2->getResponse(response));
+ EXPECT_TRUE(response.find("\"result\": 0") != std::string::npos);
+
+ // Disconnect the servers.
+ client1->disconnectFromServer();
+ client2->disconnectFromServer();
+ ASSERT_NO_THROW(getIOService()->poll());
+}
+
+// This test verifies that the server can receive and process a large command.
+TEST_F(CtrlChannelDhcpv6SrvTest, longCommand) {
+
+ std::ostringstream command;
+
+ // This is the desired size of the command sent to the server (1MB). The
+ // actual size sent will be slightly greater than that.
+ const size_t command_size = 1024 * 1000;
+
+ while (command.tellp() < command_size) {
+
+ // We're sending command 'foo' with arguments being a list of
+ // strings. If this is the first transmission, send command name
+ // and open the arguments list. Also insert the first argument
+ // so as all subsequent arguments can be prefixed with a comma.
+ if (command.tellp() == 0) {
+ command << "{ \"command\": \"foo\", \"arguments\": [ \"begin\"";
+
+ } else {
+ // Generate a random number and insert it into the stream as
+ // 10 digits long string.
+ std::ostringstream arg;
+ arg << setw(10) << std::rand();
+ // Append the argument in the command.
+ command << ", \"" << arg.str() << "\"\n";
+
+ // If we have hit the limit of the command size, close braces to
+ // get appropriate JSON.
+ if (command.tellp() > command_size) {
+ command << "] }";
+ }
+ }
+ }
+
+ ASSERT_NO_THROW(
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CtrlChannelDhcpv6SrvTest::longCommandHandler,
+ command.str(), ph::_1, ph::_2));
+ );
+
+ createUnixChannelServer();
+
+ std::string response;
+ std::thread th([this, &response, &command]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create client which we will use to send command to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+
+ // Connect to the server. This will trigger acceptor handler on the
+ // server side and create a new connection.
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Initially the remaining_string holds the entire command and we
+ // will be erasing the portions that we have sent.
+ std::string remaining_data = command.str();
+ while (!remaining_data.empty()) {
+ // Send the command in chunks of 1024 bytes.
+ const size_t l = remaining_data.size() < 1024 ? remaining_data.size() : 1024;
+ ASSERT_TRUE(client->sendCommand(remaining_data.substr(0, l)));
+ remaining_data.erase(0, l);
+ }
+
+ // Set timeout to 5 seconds to allow the time for the server to send
+ // a response.
+ const unsigned int timeout = 5;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // We're done. Close the connection to the server.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until the command has been processed and response
+ // received.
+ getIOService()->run();
+
+ // Wait for the thread to complete.
+ th.join();
+
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"long command received ok\" }",
+ response);
+}
+
+// This test verifies that the server can send long response to the client.
+TEST_F(CtrlChannelDhcpv6SrvTest, longResponse) {
+ // We need to generate large response. The simplest way is to create
+ // a command and a handler which will generate some static response
+ // of a desired size.
+ ASSERT_NO_THROW(
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CtrlChannelDhcpv6SrvTest::longResponseHandler, ph::_1, ph::_2));
+ );
+
+ createUnixChannelServer();
+
+ // The UnixControlClient doesn't have any means to check that the entire
+ // response has been received. What we want to do is to generate a
+ // reference response using our command handler and then compare
+ // what we have received over the unix domain socket with this reference
+ // response to figure out when to stop receiving.
+ std::string reference_response = longResponseHandler("foo", ConstElementPtr())->str();
+
+ // In this stream we're going to collect out partial responses.
+ std::ostringstream response;
+
+ // The client is synchronous so it is useful to run it in a thread.
+ std::thread th([this, &response, reference_response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Remember the response size so as we know when we should stop
+ // receiving.
+ const size_t long_response_size = reference_response.size();
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Send the stub command.
+ std::string command = "{ \"command\": \"foo\", \"arguments\": { } }";
+ ASSERT_TRUE(client->sendCommand(command));
+
+ // Keep receiving response data until we have received the full answer.
+ while (response.tellp() < long_response_size) {
+ std::string partial;
+ const unsigned int timeout = 5;
+ ASSERT_TRUE(client->getResponse(partial, timeout));
+ response << partial;
+ }
+
+ // We have received the entire response, so close the connection and
+ // stop the IO service.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until the entire response has been received.
+ getIOService()->run();
+
+ // Wait for the thread to complete.
+ th.join();
+
+ // Make sure we have received correct response.
+ EXPECT_EQ(reference_response, response.str());
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, having received a partial command.
+TEST_F(CtrlChannelDhcpv6SrvTest, connectionTimeoutPartialCommand) {
+ createUnixChannelServer();
+
+ // Set connection timeout to 2s to prevent long waiting time for the
+ // timeout during this test.
+ const unsigned short timeout = 2000;
+ CommandMgr::instance().setConnectionTimeout(timeout);
+
+ // Server's response will be assigned to this variable.
+ std::string response;
+
+ // It is useful to create a thread and run the server and the client
+ // at the same time and independently.
+ std::thread th([this, &response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Send partial command. The server will be waiting for the remaining
+ // part to be sent and will eventually signal a timeout.
+ std::string command = "{ \"command\": \"foo\" ";
+ ASSERT_TRUE(client->sendCommand(command));
+
+ // Let's wait up to 15s for the server's response. The response
+ // should arrive sooner assuming that the timeout mechanism for
+ // the server is working properly.
+ const unsigned int timeout = 15;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // Explicitly close the client's connection.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until stopped.
+ getIOService()->run();
+
+ // Wait for the thread to return.
+ th.join();
+
+ // Check that the server has signalled a timeout.
+ EXPECT_EQ("{ \"result\": 1, \"text\": "
+ "\"Connection over control channel timed out, "
+ "discarded partial command of 19 bytes\" }", response);
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, having received no data from the client.
+TEST_F(CtrlChannelDhcpv6SrvTest, connectionTimeoutNoData) {
+ createUnixChannelServer();
+
+ // Set connection timeout to 2s to prevent long waiting time for the
+ // timeout during this test.
+ const unsigned short timeout = 2000;
+ CommandMgr::instance().setConnectionTimeout(timeout);
+
+ // Server's response will be assigned to this variable.
+ std::string response;
+
+ // It is useful to create a thread and run the server and the client
+ // at the same time and independently.
+ std::thread th([this, &response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Having sent nothing let's just wait and see if Server times us out.
+ // Let's wait up to 15s for the server's response. The response
+ // should arrive sooner assuming that the timeout mechanism for
+ // the server is working properly.
+ const unsigned int timeout = 15;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // Explicitly close the client's connection.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until stopped.
+ getIOService()->run();
+
+ // Wait for the thread to return.
+ th.join();
+
+ // Check that the server has signalled a timeout.
+ EXPECT_EQ("{ \"result\": 1, \"text\": "
+ "\"Connection over control channel timed out\" }", response);
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/dhcp6/tests/d2_unittest.cc b/src/bin/dhcp6/tests/d2_unittest.cc
new file mode 100644
index 0000000..da9d6ee
--- /dev/null
+++ b/src/bin/dhcp6/tests/d2_unittest.cc
@@ -0,0 +1,415 @@
+// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/d2_unittest.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @todo
+void
+D2Dhcpv6Srv::d2ClientErrorHandler(const
+ dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ ++error_count_;
+ // call base class error handler
+ Dhcpv6Srv::d2ClientErrorHandler(result, ncr);
+}
+
+const bool Dhcp6SrvD2Test::SHOULD_PASS;
+const bool Dhcp6SrvD2Test::SHOULD_FAIL;
+
+Dhcp6SrvD2Test::Dhcp6SrvD2Test() : rcode_(-1) {
+ reset();
+}
+
+Dhcp6SrvD2Test::~Dhcp6SrvD2Test() {
+ reset();
+}
+
+dhcp_ddns::NameChangeRequestPtr
+Dhcp6SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
+ // Build an NCR from json string.
+ std::ostringstream stream;
+
+ stream <<
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"myhost.example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \""
+
+ << std::hex << std::setfill('0') << std::setw(16)
+ << dhcid_id_num << "\" , "
+
+ " \"lease-expires-on\" : \"20140121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
+}
+
+void
+Dhcp6SrvD2Test::reset() {
+ CfgMgr::instance().clear();
+
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"hooks-libraries\": [ ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet6\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ configure(config, SHOULD_PASS);
+}
+
+void
+Dhcp6SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
+ const std::string& server_ip,
+ const size_t port,
+ const std::string& sender_ip,
+ const size_t sender_port,
+ const size_t max_queue_size) {
+ std::ostringstream config;
+ config <<
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"hooks-libraries\": [ ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : " << (enable_d2 ? "true" : "false") << ", "
+ " \"server-ip\" : \"" << server_ip << "\", "
+ " \"server-port\" : " << port << ", "
+ " \"sender-ip\" : \"" << sender_ip << "\", "
+ " \"sender-port\" : " << sender_port << ", "
+ " \"max-queue-size\" : " << max_queue_size << ", "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : \"when-present\", "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" },"
+ "\"valid-lifetime\": 4000 }";
+
+ configure(config.str(), exp_result);
+}
+
+void
+Dhcp6SrvD2Test::configure(const std::string& config, bool exp_result) {
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+
+ int rcode;
+ ConstElementPtr comment = config::parseAnswer(rcode, status);
+ if (exp_result == SHOULD_PASS) {
+ ASSERT_EQ(0, rcode) << "parse comment: " << comment->stringValue();
+ } else {
+ ASSERT_EQ(1, rcode) << "parse comment: " << comment->stringValue();
+ }
+
+ if (rcode == 0) {
+ CfgMgr::instance().commit();
+ }
+}
+
+// Tests ability to turn on and off ddns updates by submitting
+// by submitting the appropriate configuration to Dhcp6 server
+// and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, enableDisable) {
+ // Grab the manager and verify that be default ddns is off
+ // and a sender was not started.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_FALSE(mgr.ddnsEnabled());
+ ASSERT_FALSE(mgr.amSending());
+
+ // Verify a valid config with ddns enabled configures ddns properly,
+ // but does not start the sender.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_FALSE(mgr.amSending());
+
+ // Verify that calling start does not throw and starts the sender.
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Verify a valid config with ddns disabled configures ddns properly.
+ // Sender should not have been started.
+ ASSERT_NO_FATAL_FAILURE(configureD2(false));
+ ASSERT_FALSE(mgr.ddnsEnabled());
+ ASSERT_FALSE(mgr.amSending());
+
+ // Verify that the sender does NOT get started when ddns is disabled.
+ srv_.startD2();
+ ASSERT_FALSE(mgr.amSending());
+}
+
+// Tests Dhcp6 server's ability to correctly handle a flawed dhcp-ddns
+// configuration. It does so by first enabling updates by submitting a valid
+// configuration and then ensuring they remain on after submitting a flawed
+// configuration and then invoking its startD2() method.
+TEST_F(Dhcp6SrvD2Test, badConfig) {
+ // Grab the manager and verify that be default ddns is off
+ // and a sender was not started.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_FALSE(mgr.ddnsEnabled());
+
+ // Configure it enabled and start it.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Now attempt to give it an invalid configuration.
+ // Result should indicate failure.
+ ASSERT_NO_FATAL_FAILURE(configureD2(false, SHOULD_FAIL, "bogus_ip"));
+
+ // Configure was not altered, so ddns should be enabled and still sending.
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Verify that calling start does not throw or stop the sender.
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+}
+
+// Checks that submitting an identical dhcp-ddns configuration
+// is handled properly. Not effect should be no change in
+// status for ddns updating. Updates should still enabled and
+// in send mode. This indicates that the sender was not stopped.
+TEST_F(Dhcp6SrvD2Test, sameConfig) {
+ // Grab the manager and verify that be default ddns is off
+ // and a sender was not started.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_FALSE(mgr.ddnsEnabled());
+
+ // Configure it enabled and start it.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Now submit an identical configuration.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+
+ // Configuration was not altered, so ddns should still enabled and sending.
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Verify that calling start does not throw or stop the sender.
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that submitting an different, but valid dhcp-ddns configuration
+// is handled properly. Updates should be enabled, however they should
+// not yet be running. This indicates that the sender was stopped and
+// replaced, but not yet started.
+TEST_F(Dhcp6SrvD2Test, differentConfig) {
+ // Grab the manager and verify that be default ddns is off
+ // and a sender was not started.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_FALSE(mgr.ddnsEnabled());
+
+ // Configure it enabled and start it.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Now enable it on a different port.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "::1", 54001));
+
+ // Configuration was altered, so ddns should still enabled but not sending.
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_FALSE(mgr.amSending());
+
+ // Verify that calling start starts the sender.
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that given a valid, enabled configuration and placing
+// sender in send mode, permits NCR requests to be sent via UPD
+// socket. Note this test does not employ any sort of receiving
+// client to verify actual transmission. These types of tests
+// are including under dhcp_ddns and d2 unit testing.
+TEST_F(Dhcp6SrvD2Test, simpleUDPSend) {
+ // Grab the manager and verify that be default ddns is off
+ // and a sender was not started.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_FALSE(mgr.ddnsEnabled());
+
+ // Configure it enabled and start it.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(mgr.clearQueue());
+ EXPECT_EQ(0, mgr.getQueueSize());
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Verify that we can queue up a message.
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(mgr.sendRequest(ncr));
+ EXPECT_EQ(1, mgr.getQueueSize());
+
+ // Calling receive should detect the ready IO on the sender's select-fd,
+ // and invoke callback, which should complete the send.
+ ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+ // Verify the queue is now empty.
+ EXPECT_EQ(0, mgr.getQueueSize());
+}
+
+// Checks that an IO error in sending a request to D2, results in ddns updates
+// being suspended. This indicates that Dhcp6Srv's error handler has been
+// invoked as expected. Note that this unit test relies on an attempt to send
+// to a server address of 0.0.0.0 port 0 fails, which it does under all OSs
+// except Solaris 11.
+/// @todo Eventually we should find a way to test this under Solaris.
+#ifndef OS_SOLARIS
+TEST_F(Dhcp6SrvD2Test, forceUDPSendFailure) {
+#else
+TEST_F(Dhcp6SrvD2Test, DISABLED_forceUDPSendFailure) {
+#endif
+ // Grab the manager and verify that be default ddns is off
+ // and a sender was not started.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_FALSE(mgr.ddnsEnabled());
+
+ // Configure it enabled and start it.
+ // Using server address of 0.0.0.0/0 should induce failure on send.
+ // Pass in a non-zero sender port to avoid validation error when
+ // server-ip/port are same as sender-ip/port
+ ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "::", 0,
+ "::", 53001));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(mgr.clearQueue());
+ EXPECT_EQ(0, mgr.getQueueSize());
+ try {
+ srv_.startD2();
+ } catch (const std::exception& ex) {
+ FAIL() << "startD2 failed with " << ex.what();
+ } catch (...) {
+ FAIL() << "startD2 failed";
+ }
+ ASSERT_TRUE(mgr.amSending());
+
+ // Queue up 3 messages.
+ for (int i = 0; i < 3; i++) {
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+ ASSERT_NO_THROW(mgr.sendRequest(ncr));
+ }
+ EXPECT_EQ(3, mgr.getQueueSize());
+
+ // Calling receive should detect the ready IO on the sender's select-fd,
+ // and invoke callback, which should complete the send, which should
+ // fail.
+ ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+ // Verify the error handler was invoked.
+ EXPECT_EQ(1, srv_.error_count_);
+
+ // Verify that updates are disabled and we are no longer sending.
+ ASSERT_FALSE(mgr.ddnsEnabled());
+ ASSERT_FALSE(mgr.amSending());
+
+ // Verify message is still in the queue.
+ EXPECT_EQ(3, mgr.getQueueSize());
+
+ // Verify that we can't just restart it.
+ /// @todo This may change if we add ability to resume.
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_FALSE(mgr.amSending());
+
+ // Configure it enabled and start it.
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Verify message is still in the queue.
+ EXPECT_EQ(3, mgr.getQueueSize());
+
+ // This will finish sending the 1st message in queue
+ // and initiate send of 2nd message.
+ ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+ EXPECT_EQ(1, srv_.error_count_);
+
+ // First message is off the queue.
+ EXPECT_EQ(2, mgr.getQueueSize());
+}
+
+// Tests error handling of D2ClientMgr::sendRequest() failure
+// by attempting to queue maximum number of messages.
+TEST_F(Dhcp6SrvD2Test, queueMaxError) {
+ // Configure it enabled and start it.
+ dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ ASSERT_NO_FATAL_FAILURE(configureD2(true));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_NO_THROW(mgr.clearQueue());
+ EXPECT_EQ(0, mgr.getQueueSize());
+ ASSERT_NO_THROW(srv_.startD2());
+ ASSERT_TRUE(mgr.amSending());
+
+ // Attempt to queue more then the maximum allowed.
+ int max_msgs = mgr.getQueueMaxSize();
+ for (int i = 0; i < max_msgs + 1; i++) {
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+ ASSERT_NO_THROW(mgr.sendRequest(ncr));
+ }
+
+ // Stopping sender will complete the first message so there
+ // should be max less one.
+ EXPECT_EQ(max_msgs - 1, mgr.getQueueSize());
+
+ // Verify the error handler was invoked.
+ EXPECT_EQ(1, srv_.error_count_);
+
+ // Verify that updates are disabled and we are no longer sending.
+ ASSERT_FALSE(mgr.ddnsEnabled());
+ ASSERT_FALSE(mgr.amSending());
+}
+
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/bin/dhcp6/tests/d2_unittest.h b/src/bin/dhcp6/tests/d2_unittest.h
new file mode 100644
index 0000000..55b9462
--- /dev/null
+++ b/src/bin/dhcp6/tests/d2_unittest.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file d2_unittest.h Defines classes for testing Dhcpv6srv with D2ClientMgr
+
+#ifndef D2_UNITTEST_H
+#define D2_UNITTEST_H
+
+#include <dhcp6/dhcp6_srv.h>
+#include <cc/command_interpreter.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test derivation of Dhcpv6Srv class used in D2 testing.
+/// Use of this class allows the intervention at strategic points in testing
+/// by permitting overridden methods and access to scope protected members.
+class D2Dhcpv6Srv : public Dhcpv6Srv {
+public:
+ /// @brief Counts the number of times the client error handler is called.
+ int error_count_;
+
+ /// @brief Constructor
+ D2Dhcpv6Srv()
+ : Dhcpv6Srv(0), error_count_(0) {
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~D2Dhcpv6Srv() {
+ }
+
+ /// @brief Override the error handler.
+ virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender::
+ Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr);
+};
+
+/// @brief Test fixture which permits testing the interaction between the
+/// D2ClientMgr and Dhcpv6Srv.
+class Dhcp6SrvD2Test : public ::testing::Test {
+public:
+ /// @brief Mnemonic constants for calls to configuration methods.
+ static const bool SHOULD_PASS = true;
+ static const bool SHOULD_FAIL = false;
+
+ /// @brief Constructor
+ Dhcp6SrvD2Test();
+
+ /// @brief virtual Destructor
+ virtual ~Dhcp6SrvD2Test();
+
+ /// @brief Resets the CfgMgr singleton to defaults.
+ /// Primarily used in the test destructor as gtest doesn't exit between
+ /// tests.
+ /// @todo CfgMgr should provide a method to reset everything or maybe
+ /// reconstruct the singleton.
+ void reset();
+
+ /// @brief Configures the server with D2 enabled or disabled
+ ///
+ /// Constructs a configuration string including dhcp-ddns with the
+ /// parameters given and passes it into the server's configuration handler.
+ ///
+ /// @param enable_updates value to assign to the enable-updates parameter
+ /// @param server_ip IP address for the D2 server
+ /// @param port port for the D2 server
+ /// @param sender_ip NCR sender's IP address
+ /// @param sender_port NCR sender port
+ /// @param max_queue_size maximum number of NCRs allowed in sender's queue
+ void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
+ const std::string& server_ip = "::1",
+ const size_t port = 53001,
+ const std::string& sender_ip = "::",
+ const size_t sender_port = 0,
+ const size_t max_queue_size = 1024);
+
+ /// @brief Configures the server with the given configuration
+ ///
+ /// Passes the given configuration string into the server's configuration
+ /// handler. It accepts a flag indicating whether or not the configuration
+ /// is expected to succeed or fail. This permits testing the server's
+ /// response to both valid and invalid configurations.
+ ///
+ /// @param config JSON string containing the configuration
+ /// @param exp_result indicates if configuration should pass or fail
+ void configure(const std::string& config, bool exp_result = SHOULD_PASS);
+
+ /// @brief Constructs a NameChangeRequest message from a fixed JSON string.
+ ///
+ /// @param dhcid_id_num Integer value to use as the DHCID.
+ dhcp_ddns::NameChangeRequestPtr buildTestNcr(uint32_t
+ dhcid_id_num = 0xdeadbeef);
+
+ /// @brief Stores the return code of the last configuration attempt.
+ int rcode_;
+
+ /// @brief Stores the message component of the last configuration attempt.
+ isc::data::ConstElementPtr comment_;
+
+ /// @brief Server object under test.
+ D2Dhcpv6Srv srv_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // D2_UNITTEST_H
diff --git a/src/bin/dhcp6/tests/decline_unittest.cc b/src/bin/dhcp6/tests/decline_unittest.cc
new file mode 100644
index 0000000..b3db3ad
--- /dev/null
+++ b/src/bin/dhcp6/tests/decline_unittest.cc
@@ -0,0 +1,332 @@
+// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+#include <dhcpsrv/lease.h>
+#include <stats/stats_mgr.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Renew tests.
+///
+/// - Configuration 0:
+/// - only addresses (no prefixes)
+/// - 1 subnet with 2001:db8:1::/64 pool
+const char* DECLINE_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+// Configuration 1 - only use when mysql is enabled
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"lease-database\": {"
+ "\"type\": \"mysql\","
+ "\"name\": \"keatest\","
+ "\"user\": \"keatest\","
+ "\"password\": \"keatest\""
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+// Configuration 2 - only use when pgsql is enabled
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"lease-database\": {"
+ "\"type\": \"postgresql\","
+ "\"name\": \"keatest\","
+ "\"user\": \"keatest\","
+ "\"password\": \"keatest\""
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+};
+
+/// @brief Test fixture class for testing Renew.
+class DeclineTest : public Dhcpv6MessageTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ DeclineTest()
+ : Dhcpv6MessageTest(), na_iaid_(1234) {
+ }
+
+ /// @brief IAID used for IA_NA.
+ uint32_t na_iaid_;
+};
+
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+Dhcpv6SrvTest::acquireAndDecline(Dhcp6Client& client,
+ const std::string& duid1,
+ const uint32_t iaid1,
+ const std::string& duid2,
+ const uint32_t iaid2,
+ AddressInclusion addr_type,
+ ExpectedResult expected_result,
+ uint8_t config_index) {
+ // Set this global statistic explicitly to zero.
+ StatsMgr::instance().setValue("declined-addresses", static_cast<int64_t>(0));
+
+ client.setDUID(duid1);
+ client.requestAddress(iaid1);
+
+ // Configure the server with a configuration.
+ ASSERT_NO_THROW(configure(DECLINE_CONFIGS[config_index], *client.getServer()));
+
+ // Let's get the subnet-id and generate statistics name out of it.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Let's generate the subnet specific statistic
+ std::string name = StatsMgr::generateName("subnet",
+ (*subnets->begin())->getID(),
+ "declined-addresses");
+
+ // Set this statistic explicitly to zero.
+ StatsMgr::instance().setValue(name, static_cast<int64_t>(0));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(iaid1));
+
+ // Remember the acquired address.
+ IOAddress acquired_address = leases_client_na[0].addr_;
+
+ // Check the declined-addresses (subnet) before the Decline operation.
+ ObservationPtr declined_cnt = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(declined_cnt);
+ uint64_t before = declined_cnt->getInteger().first;
+
+ // Check the global declined-addresses statistic before the Decline.
+ ObservationPtr declined_global = StatsMgr::instance()
+ .getObservation("declined-addresses");
+ ASSERT_TRUE(declined_global);
+ uint64_t before_global = declined_cnt->getInteger().first;
+
+ /// Determines if the client will include address in the messages it sends.
+ bool include_address_ = true;
+
+ // Let's tamper with the address if necessary.
+ switch (addr_type) {
+ case VALID_ADDR:
+ // Nothing to do, client will do its job correctly by default
+ break;
+ case BOGUS_ADDR:
+ // Simple increase by one.
+ client.config_.leases_[0].addr_ =
+ IOAddress::increase(client.config_.leases_[0].addr_);
+ break;
+ case NO_ADDR:
+ // Tell the client to not include an address in its IA_NA
+ include_address_ = false;
+ break;
+ case NO_IA:
+ // Tell the client to not include IA_NA at all
+ client.config_.clear();
+ client.clearRequestedIAs();
+ }
+
+ // Use the second duid
+ client.setDUID(duid2);
+
+ // Use the second IAID (but not in NO_IA which has cleared leases)
+ if (addr_type != NO_IA) {
+ ASSERT_NE(0, client.config_.leases_.size());
+ client.config_.leases_[0].iaid_ = iaid2;
+ }
+
+ // Ok, let's decline the lease.
+ ASSERT_NO_THROW(client.doDecline(include_address_));
+
+ // Let's check if there's a lease
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ acquired_address);
+ ASSERT_TRUE(lease);
+
+ declined_cnt = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(declined_cnt);
+ uint64_t after = declined_cnt->getInteger().first;
+
+ declined_global = StatsMgr::instance().getObservation("declined-addresses");
+ ASSERT_TRUE(declined_global);
+ uint64_t after_global = declined_global->getInteger().first;
+
+ // We check if the decline process was successful by checking if the
+ // lease is in the database and what is its state.
+ if (expected_result == SHOULD_PASS) {
+ ASSERT_EQ(Lease::STATE_DECLINED, lease->state_);
+
+ ASSERT_FALSE(lease->hwaddr_);
+ ASSERT_TRUE(lease->duid_);
+ ASSERT_EQ(*lease->duid_, DUID::EMPTY());
+ ASSERT_EQ(lease->preferred_lft_, 0);
+ ASSERT_TRUE(lease->hostname_.empty());
+ ASSERT_FALSE(lease->fqdn_fwd_);
+ ASSERT_FALSE(lease->fqdn_rev_);
+
+ // The decline succeeded, so the declined-addresses statistic should
+ // be increased by one
+ ASSERT_EQ(after, before + 1);
+ ASSERT_EQ(after_global, before_global + 1);
+ } else {
+ // the decline was supposed, to be rejected.
+ ASSERT_EQ(Lease::STATE_DEFAULT, lease->state_);
+
+ ASSERT_TRUE(lease->hwaddr_);
+ ASSERT_TRUE(lease->duid_);
+ ASSERT_NE(*lease->duid_, DUID::EMPTY());
+ ASSERT_NE(lease->preferred_lft_, 0);
+ ASSERT_FALSE(lease->fqdn_fwd_);
+ ASSERT_FALSE(lease->fqdn_rev_);
+
+ // The decline failed, so the declined-addresses should be the same
+ // as before
+ ASSERT_EQ(before, after);
+ ASSERT_EQ(before_global, after_global);
+ }
+}
+
+// This test checks that the client can acquire and decline the lease.
+TEST_F(DeclineTest, basicMemfile) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1234, VALID_ADDR, SHOULD_PASS);
+}
+
+#ifdef HAVE_MYSQL
+// This test checks that the client can acquire and decline the lease.
+TEST_F(DeclineTest, basicMySQL) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1234, VALID_ADDR, SHOULD_PASS, 1);
+}
+#endif
+
+#ifdef HAVE_PGSQL
+TEST_F(DeclineTest, basicPgSQL) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1234, VALID_ADDR, SHOULD_PASS, 2);
+}
+#endif
+
+}
+}
+}
+
+namespace {
+
+// This test verifies the decline is rejected in the following case:
+// - Client acquires new lease using duid, iaid
+// - Client sends the DECLINE with duid, iaid, but uses wrong address.
+// - The server rejects Decline due to address mismatch
+TEST_F(DeclineTest, addressMismatch) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1234, BOGUS_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline is rejected in the following case:
+// - Client acquires new lease using duid, iaid1
+// - Client sends the DECLINE with duid, iaid2
+// - The server rejects Decline due to IAID mismatch
+TEST_F(DeclineTest, iaidMismatch) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1235, VALID_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using duid1, iaid
+// - Client sends the DECLINE using duid2, iaid
+// - The server rejects the Decline due to DUID mismatch
+TEST_F(DeclineTest, duidMismatch) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:07", 1234,
+ VALID_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using duid1, iaid
+// - Client sends the DECLINE using valid duid/iaid, but does not
+// include the address in it
+// - The server rejects the Decline due to missing address
+TEST_F(DeclineTest, noAddrsSent) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:06", 1234,
+ NO_ADDR, SHOULD_FAIL);
+}
+
+// This test verifies the decline correctness in the following case:
+// - Client acquires new lease using duid1, iaid
+// - Client sends the DECLINE using valid duid, but does not
+// include IA_NA at all
+// - The server rejects the Decline due to missing IA_NA
+TEST_F(DeclineTest, noIAs) {
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234,
+ "01:02:03:04:05:06", 1234,
+ NO_IA, SHOULD_FAIL);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc
new file mode 100644
index 0000000..7021176
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_client.cc
@@ -0,0 +1,1053 @@
+// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/pool.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <util/buffer.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
+#include <algorithm>
+#include <cstdlib>
+#include <time.h>
+#include <utility>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Functor searching for the leases using a specified property.
+///
+/// @tparam BaseType Base type to which the property belongs: @c Lease or
+/// @c Lease6.
+/// @tparam PropertyType A type of the property, e.g. @c uint32_t for IAID.
+/// @tparam MemberPointer A pointer to the member, e.g. @c &Lease6::iaid_.
+template<typename BaseType, typename PropertyType,
+ PropertyType BaseType::*MemberPointer>
+struct getLeasesByPropertyFun {
+
+ /// @brief Returns leases matching the specified condition.
+ ///
+ /// @param config DHCP client configuration structure holding leases.
+ /// @param property A value of the lease property used to search the lease.
+ /// @param equals A flag which indicates if the operator should search for
+ /// the leases which property is equal to the value of @c property parameter
+ /// (if true), or unequal (if false).
+ /// @param [out] leases A vector in which the operator will store leases
+ /// found.
+ void operator()(const Dhcp6Client::Configuration& config,
+ const PropertyType& property, const bool equals,
+ std::vector<Lease6>& leases) {
+
+ // Iterate over the leases and match the property with a given lease
+ //field.
+ for (typename std::vector<Lease6>::const_iterator lease =
+ config.leases_.begin(); lease != config.leases_.end();
+ ++lease) {
+ // Check if fulfills the condition.
+ if ((equals && ((*lease).*MemberPointer) == property) ||
+ (!equals && ((*lease).*MemberPointer) != property)) {
+ // Found the matching lease.
+ leases.push_back(*lease);
+ }
+ }
+ }
+};
+
+/// @brief Returns leases which belong to specified pool.
+///
+/// @param config DHCP client configuration structure holding leases.
+/// @param pool Pool to which returned leases belong.
+/// @param [out] leases A vector in which the function will store leases
+/// found.
+void getLeasesByPool(const Dhcp6Client::Configuration& config,
+ const Pool6& pool, std::vector<Lease6>& leases) {
+ for (std::vector<Lease6>::const_iterator lease =
+ config.leases_.begin(); lease != config.leases_.end();
+ ++lease) {
+ // Check if prefix in range.
+ if (pool.inRange(lease->addr_)) {
+ // Found the matching lease.
+ leases.push_back(*lease);
+ }
+ }
+}
+
+}; // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Dhcp6Client::Dhcp6Client() :
+ relay_link_addr_("3000:1::1"),
+ curr_transid_(0),
+ dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+ duid_(generateDUID(DUID::DUID_LLT)),
+ link_local_("fe80::3a60:77ff:fed5:cdef"),
+ iface_name_("eth0"),
+ iface_index_(ETH0_INDEX),
+ srv_(boost::shared_ptr<NakedDhcpv6Srv>(new NakedDhcpv6Srv(0))),
+ use_relay_(false),
+ use_oro_(false),
+ use_docsis_oro_(false),
+ use_client_id_(true),
+ use_rapid_commit_(false),
+ client_ias_(),
+ fqdn_(),
+ interface_id_() {
+}
+
+Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
+ relay_link_addr_("3000:1::1"),
+ curr_transid_(0),
+ dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+ duid_(generateDUID(DUID::DUID_LLT)),
+ link_local_("fe80::3a60:77ff:fed5:cdef"),
+ iface_name_("eth0"),
+ iface_index_(ETH0_INDEX),
+ srv_(srv),
+ use_relay_(false),
+ use_oro_(false),
+ use_docsis_oro_(false),
+ use_client_id_(true),
+ use_rapid_commit_(false),
+ client_ias_(),
+ fqdn_(),
+ interface_id_() {
+}
+
+void
+Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply, uint32_t state) {
+ // Let's try to get a MAC
+ HWAddrPtr hwaddr = reply->getMAC(HWAddr::HWADDR_SOURCE_ANY);
+
+ // Get all options in the reply message and pick IA_NA, IA_PD and
+ // Status code.
+ for (const auto& opt : reply->options_) {
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt.second);
+ if (!ia) {
+ // This is not IA, so let's just store it.
+ config_.options_.insert(opt);
+ continue;
+ }
+
+ const auto& ia_opts = ia->getOptions();
+ for (const auto& iter_ia_opt : ia_opts) {
+ OptionPtr ia_opt = iter_ia_opt.second;
+ Lease6 lease;
+ lease.type_ = (ia->getType() == D6O_IA_NA ? Lease::TYPE_NA : Lease::TYPE_PD);
+ lease.iaid_ = ia->getIAID();
+
+ switch (ia_opt->getType()) {
+ case D6O_IAADDR:
+ {
+ Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+ Option6IAAddr>(ia_opt);
+
+ if (iaaddr) {
+ lease = Lease6(Lease::TYPE_NA,
+ iaaddr->getAddress(),
+ duid_, ia->getIAID(),
+ iaaddr->getPreferred(),
+ iaaddr->getValid(), 0,
+ hwaddr);
+ lease.cltt_ = time(NULL);
+ lease.state_ = state;
+ applyLease(lease);
+ }
+ }
+ break;
+
+ case D6O_IAPREFIX:
+ {
+ Option6IAPrefixPtr iaprefix = boost::dynamic_pointer_cast<
+ Option6IAPrefix>(ia_opt);
+
+ if (iaprefix) {
+ lease = Lease6(Lease::TYPE_PD,
+ iaprefix->getAddress(), duid_,
+ ia->getIAID(),
+ iaprefix->getPreferred(),
+ iaprefix->getValid(), 0,
+ hwaddr,
+ iaprefix->getLength());
+ lease.cltt_ = time(NULL);
+ applyLease(lease);
+ }
+ }
+ break;
+
+ case D6O_STATUS_CODE:
+ {
+ // Check if the server has sent status code. If no status
+ // code, assume the status code to be 0.
+ Option6StatusCodePtr status_code = boost::dynamic_pointer_cast<
+ Option6StatusCode>(ia->getOption(D6O_STATUS_CODE));
+ config_.status_codes_[ia->getIAID()] =
+ (status_code ? status_code->getStatusCode() : 0);
+ }
+ break;
+
+ default:
+ ; // no-op
+ }
+
+ }
+ }
+
+ // Get the global status code.
+ Option6StatusCodePtr status_code = boost::dynamic_pointer_cast<
+ Option6StatusCode>(reply->getOption(D6O_STATUS_CODE));
+ // If status code has been sent, we override the default status code:
+ // Success and record that we have received the status code.
+ if (status_code) {
+ config_.received_status_code_ = true;
+ config_.status_code_ = status_code->getStatusCode();
+ }
+}
+
+void
+Dhcp6Client::applyLease(const Lease6& lease) {
+ // Go over existing leases and try to match the one that we have.
+ for (size_t i = 0; i < config_.leases_.size(); ++i) {
+ Lease6 existing_lease = config_.leases_[i];
+ // If IAID is matching and there is an actual address assigned
+ // replace the current lease. The default address is :: if the
+ // server hasn't sent the IA option. In this case, there is no
+ // lease assignment so we keep what we have.
+ if ((existing_lease.iaid_ == lease.iaid_)
+ && (existing_lease.type_ == lease.type_)
+ && (lease.addr_ != asiolink::IOAddress("::"))
+ && (existing_lease.addr_ == lease.addr_)) {
+ config_.leases_[i] = lease;
+ return;
+ }
+ }
+
+ // It is a new lease. Add it.
+ config_.leases_.push_back(lease);
+}
+
+void
+Dhcp6Client::appendFQDN() {
+ if (fqdn_) {
+ context_.query_->addOption(fqdn_);
+ }
+}
+
+void
+Dhcp6Client::appendRequestedIAs(const Pkt6Ptr& query) const {
+ BOOST_FOREACH(const ClientIA& ia, client_ias_) {
+ OptionCollection options =
+ query->getOptions(ia.type_ == Lease::TYPE_NA ?
+ D6O_IA_NA : D6O_IA_PD);
+ std::pair<unsigned int, OptionPtr> option_pair;
+ Option6IAPtr existing_ia;
+ BOOST_FOREACH(option_pair, options) {
+ Option6IAPtr ia_opt =
+ boost::dynamic_pointer_cast<Option6IA>(option_pair.second);
+ // This shouldn't happen.
+ if (!ia_opt) {
+ isc_throw(Unexpected,
+ "Dhcp6Client: IA option has an invalid C++ type;"
+ " this is a programming issue");
+ }
+ if (ia_opt->getIAID() == ia.iaid_) {
+ existing_ia = ia_opt;
+ }
+ }
+ if (!existing_ia) {
+ existing_ia.reset(new Option6IA(ia.type_ == Lease::TYPE_NA ?
+ D6O_IA_NA : D6O_IA_PD, ia.iaid_));
+ query->addOption(existing_ia);
+ }
+
+ bool option_exists = false;
+ if ((ia.type_ == Lease::TYPE_NA) && !ia.prefix_.isV6Zero()) {
+ Option6IAAddrPtr ia_addr(new Option6IAAddr(D6O_IAADDR, ia.prefix_,
+ 0, 0));
+ BOOST_FOREACH(option_pair, existing_ia->getOptions()) {
+ Option6IAAddrPtr existing_addr = boost::dynamic_pointer_cast<
+ Option6IAAddr>(option_pair.second);
+ if (existing_addr &&
+ (existing_addr->getAddress() == ia.prefix_)) {
+ option_exists = true;
+ }
+ }
+
+ if (!option_exists) {
+ existing_ia->addOption(ia_addr);
+ }
+
+ } else if ((ia.type_ == Lease::TYPE_PD) &&
+ (!ia.prefix_.isV6Zero() || (ia.prefix_len_ > 0))) {
+ Option6IAPrefixPtr ia_prefix(new Option6IAPrefix(D6O_IAPREFIX,
+ ia.prefix_,
+ ia.prefix_len_,
+ 0, 0));
+ BOOST_FOREACH(option_pair, existing_ia->getOptions()) {
+ Option6IAPrefixPtr existing_prefix =
+ boost::dynamic_pointer_cast<Option6IAPrefix>(option_pair.second);
+ if (existing_prefix &&
+ (existing_prefix->getAddress() == ia.prefix_) &&
+ existing_prefix->getLength()) {
+ option_exists = true;
+ }
+ }
+
+ if (!option_exists) {
+ existing_ia->addOption(ia_prefix);
+ }
+ }
+ }
+}
+
+void
+Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
+ typedef OptionCollection Opts;
+ // Copy IA_NAs.
+ Opts opts = source->getOptions(D6O_IA_NA);
+ for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
+ // Only copy the entire IA_NA if there is at lease one IA Address option.
+ if (opt->second->getOption(D6O_IAADDR)) {
+ dest->addOption(opt->second);
+ }
+ }
+ // Copy IA_PDs.
+ opts = source->getOptions(D6O_IA_PD);
+ for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
+ // Only copy the entire IA_PD if there is at least one IA Prefix option
+ // in it.
+ if (opt->second->getOption(D6O_IAPREFIX)) {
+ dest->addOption(opt->second);
+ }
+ }
+}
+
+void
+Dhcp6Client::copyIAsFromLeases(const Pkt6Ptr& dest) const {
+ // Go over leases and create IA_NA and IA_PD options from them.
+ // Create one IA per lease.
+ std::set<uint32_t> iaids = getIAIDs();
+ for (std::set<uint32_t>::const_iterator iaid = iaids.begin();
+ iaid != iaids.end(); ++iaid) {
+ std::vector<Lease6> leases = getLeasesByIAID(*iaid);
+ // Only a valid lease should be included. Do not copy a
+ // lease which have been marked by the server as invalid.
+ if (leases[0].valid_lft_ == 0) {
+ continue;
+ }
+ Option6IAPtr opt(new Option6IA(leases[0].type_ == Lease::TYPE_NA ?
+ D6O_IA_NA : D6O_IA_PD, *iaid));
+ for (std::vector<Lease6>::const_iterator lease = leases.begin();
+ lease != leases.end(); ++lease) {
+ if ((lease->preferred_lft_ != 0) && (lease->valid_lft_ != 0)) {
+ if (lease->type_ == Lease::TYPE_NA) {
+ opt->addOption(Option6IAAddrPtr(new Option6IAAddr(
+ D6O_IAADDR,
+ lease->addr_,
+ lease->preferred_lft_,
+ lease->valid_lft_)));
+ } else if (lease->type_ == Lease::TYPE_PD) {
+ opt->addOption(Option6IAAddrPtr(new Option6IAPrefix(
+ D6O_IAPREFIX,
+ lease->addr_,
+ lease->prefixlen_,
+ lease->preferred_lft_,
+ lease->valid_lft_)));
+ }
+ }
+ }
+ dest->addOption(opt);
+ }
+}
+
+void
+Dhcp6Client::createLease(const Lease6& lease) {
+ applyLease(lease);
+}
+
+Pkt6Ptr
+Dhcp6Client::createMsg(const uint8_t msg_type) {
+ Pkt6Ptr msg(new Pkt6(msg_type, curr_transid_++));
+
+ if (use_client_id_) {
+ msg->addOption(getClientId());
+ }
+
+ if (use_oro_) {
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ oro->setValues(oro_);
+
+ msg->addOption(oro);
+ };
+
+ if (use_docsis_oro_) {
+ OptionUint16ArrayPtr vendor_oro(new OptionUint16Array(Option::V6,
+ DOCSIS3_V6_ORO));
+ vendor_oro->setValues(docsis_oro_);
+ OptionVendorPtr vendor(new OptionVendor(Option::V6, VENDOR_ID_CABLE_LABS));
+ vendor->addOption(vendor_oro);
+ msg->addOption(vendor);
+ }
+
+ // If there are any custom options specified, add them all to the message.
+ if (!extra_options_.empty()) {
+ for (OptionCollection::iterator opt = extra_options_.begin();
+ opt != extra_options_.end(); ++opt) {
+ msg->addOption(opt->second);
+ }
+ }
+
+ // Add classes.
+ for (ClientClasses::const_iterator cclass = classes_.cbegin();
+ cclass != classes_.cend(); ++cclass) {
+ msg->addClass(*cclass);
+ }
+
+ return (msg);
+}
+
+void
+Dhcp6Client::doSARR() {
+ doSolicit();
+ // Don't send the Request if there was no Advertise.
+ if (context_.response_) {
+ doRequest();
+ }
+}
+
+void
+Dhcp6Client::doSolicit(const bool always_apply_config) {
+ context_.query_ = createMsg(DHCPV6_SOLICIT);
+ if (forced_server_id_) {
+ context_.query_->addOption(forced_server_id_);
+ }
+
+ // Append requested (empty) IAs.
+ appendRequestedIAs(context_.query_);
+
+ if (use_rapid_commit_) {
+ context_.query_->addOption(OptionPtr(new Option(Option::V6,
+ D6O_RAPID_COMMIT)));
+ }
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // If using Rapid Commit and the server has responded with Reply,
+ // let's apply received configuration. We also apply the configuration
+ // for the Advertise if instructed to do so.
+ if (context_.response_ &&
+ (always_apply_config ||
+ (use_rapid_commit_ &&
+ context_.response_->getType() == DHCPV6_REPLY))) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRequest() {
+ Pkt6Ptr query = createMsg(DHCPV6_REQUEST);
+ if (!forced_server_id_) {
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ } else {
+ query->addOption(forced_server_id_);
+ }
+ copyIAs(context_.response_, query);
+ appendRequestedIAs(query);
+
+ context_.query_ = query;
+
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ /// @todo sanity check here.
+
+ // Apply new configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doInfRequest() {
+ context_.query_ = createMsg(DHCPV6_INFORMATION_REQUEST);
+
+ // IA_NA, IA_TA and IA_PD options are not allowed in INF-REQUEST,
+ // but hey! Let's test it.
+ appendRequestedIAs(context_.query_);
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+ // Apply new configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRenew() {
+ Pkt6Ptr query = createMsg(DHCPV6_RENEW);
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ copyIAsFromLeases(query);
+
+ // During the Renew the client may request additional bindings per
+ // RFC 8415.
+ appendRequestedIAs(query);
+
+ context_.query_ = query;
+
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // Apply configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRebind() {
+ Pkt6Ptr query = createMsg(DHCPV6_REBIND);
+ copyIAsFromLeases(query);
+
+ // During the Rebind the client may request additional bindings per
+ // RFC 8415.
+ appendRequestedIAs(query);
+
+ // Add Client FQDN if configured.
+ appendFQDN();
+
+ context_.query_ = query;
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+ // Apply configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doConfirm() {
+ context_.query_ = createMsg(DHCPV6_CONFIRM);
+ copyIAsFromLeases(context_.query_);
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+ // Set the global status code to default: success and not received.
+ config_.resetGlobalStatusCode();
+ if (context_.response_) {
+ config_.options_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doDecline(const bool include_address) {
+ Pkt6Ptr query = createMsg(DHCPV6_DECLINE);
+ if (!forced_server_id_) {
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ } else {
+ query->addOption(forced_server_id_);
+ }
+
+ generateIAFromLeases(query, include_address);
+
+ context_.query_ = query;
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // Apply new configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::doRelease() {
+ Pkt6Ptr query = createMsg(DHCPV6_RELEASE);
+ query->addOption(context_.response_->getOption(D6O_SERVERID));
+ copyIAsFromLeases(query);
+
+ context_.query_ = query;
+
+ sendMsg(context_.query_);
+ context_.response_ = receiveOneMsg();
+
+ // Apply configuration only if the server has responded.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::generateIAFromLeases(const Pkt6Ptr& query,
+ const bool include_address) {
+ /// @todo: add support for IAPREFIX here.
+
+ for (std::vector<Lease6>::const_iterator lease = config_.leases_.begin();
+ lease != config_.leases_.end(); ++lease) {
+ if (lease->type_ != Lease::TYPE_NA) {
+ continue;
+ }
+
+ Option6IAPtr ia(new Option6IA(D6O_IA_NA, lease->iaid_));
+
+ if (include_address) {
+ ia->addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR,
+ lease->addr_, lease->preferred_lft_, lease->valid_lft_)));
+ }
+ query->addOption(ia);
+ }
+}
+
+void
+Dhcp6Client::fastFwdTime(const uint32_t secs, const bool update_server) {
+ // Iterate over all leases and move their cltt backwards.
+ for (size_t i = 0; i < config_.leases_.size(); ++i) {
+ config_.leases_[i].cltt_ -= secs;
+ if (update_server) {
+ Lease6Ptr lease(new Lease6(config_.leases_[i]));
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+ }
+}
+
+DuidPtr
+Dhcp6Client::generateDUID(DUID::DUIDType duid_type) const {
+ std::vector<uint8_t> duid;
+
+ /// @todo remove this check once other DUID types are implemented.
+ if (duid_type != DUID::DUID_LLT) {
+ isc_throw(NotImplemented, "currently the Dhcp6Client only supports"
+ " generation of DUID LLT");
+ }
+ duid.push_back(static_cast<uint8_t>(duid_type));
+ for (int i = 0; i < 4; ++i) {
+ duid.push_back(static_cast<uint8_t>(rand() % 255));
+ }
+ for (int i = 0; i < 6; ++i) {
+ duid.push_back(static_cast<uint8_t>(i));
+ }
+
+ return (DuidPtr(new DUID(duid)));
+}
+
+OptionPtr
+Dhcp6Client::getClientId() const {
+ OptionPtr opt_client_id(new Option(Option::V6,
+ D6O_CLIENTID,
+ duid_->getDuid().begin(),
+ duid_->getDuid().end()));
+ return (opt_client_id);
+}
+
+std::set<uint32_t>
+Dhcp6Client::getIAIDs() const {
+ std::set<uint32_t> iaids;
+ for (std::vector<Lease6>::const_iterator lease = config_.leases_.begin();
+ lease != config_.leases_.end(); ++lease) {
+ iaids.insert(lease->iaid_);
+ }
+ return (iaids);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByIAID(const uint32_t iaid) const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease6, uint32_t, &Lease6::iaid_>(iaid, true, leases);
+ return (leases);
+}
+
+template<typename BaseType, typename PropertyType, PropertyType BaseType::*MemberPointer>
+void
+Dhcp6Client::getLeasesByProperty(const PropertyType& property, const bool equals,
+ std::vector<Lease6>& leases) const {
+ getLeasesByPropertyFun<BaseType, PropertyType, MemberPointer> fun;
+ fun(config_, property, equals, leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByType(const Lease6::Type& lease_type) const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease6, Lease6::Type, &Lease6::type_>(lease_type, true, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesWithNonZeroLifetime() const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease, uint32_t, &Lease::valid_lft_>(0, false, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesWithZeroLifetime() const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease, uint32_t, &Lease::valid_lft_>(0, true, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddress(const IOAddress& address) const {
+ std::vector<Lease6> leases;
+ getLeasesByProperty<Lease, IOAddress, &Lease::addr_>(address, true, leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByAddressRange(const IOAddress& first,
+ const IOAddress& second) const {
+ std::vector<Lease6> leases;
+ getLeasesByPool(config_, Pool6(Lease::TYPE_NA, first, second), leases);
+ return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const {
+ std::vector<Lease6> leases;
+ getLeasesByPool(config_, Pool6(Lease::TYPE_PD, prefix, prefix_len,
+ delegated_len), leases);
+ return (leases);
+}
+
+bool
+Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address) const {
+ std::vector<Lease6> leases = getLeasesByAddress(address);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseForAddress(const asiolink::IOAddress& address,
+ const IAID& iaid) const {
+ std::vector<Lease6> leases = getLeasesByAddress(address);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if (lease.iaid_ == iaid) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForAddressRange(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& last) const {
+ std::vector<Lease6> leases = getLeasesByAddressRange(first, last);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseWithZeroLifetimeForAddress(const asiolink::IOAddress& address) const {
+ std::vector<Lease6> leases = getLeasesByAddress(address);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if ((lease.preferred_lft_ == 0) && (lease.valid_lft_ == 0)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+
+bool
+Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ std::vector<Lease6> leases = getLeasesByAddress(prefix);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if (lease.prefixlen_ == prefix_len) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const IAID& iaid) const {
+ std::vector<Lease6> leases = getLeasesByAddress(prefix);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if ((lease.prefixlen_ == prefix_len) &&
+ (lease.iaid_ == iaid)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasLeaseForPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const {
+ std::vector<Lease6> leases = getLeasesByPrefixPool(prefix, prefix_len,
+ delegated_len);
+ return (!leases.empty());
+}
+
+bool
+Dhcp6Client::hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ std::vector<Lease6> leases = getLeasesByAddress(prefix);
+ BOOST_FOREACH(const Lease6& lease, leases) {
+ if ((lease.prefixlen_ == prefix_len) && (lease.preferred_lft_ == 0) &&
+ (lease.valid_lft_ == 0)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+Dhcp6Client::hasOptionWithAddress(const uint16_t option_type,
+ const std::string& expected_address) const {
+ Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(config_.findOption(option_type));
+ if (opt) {
+ Option6AddrLst::AddressContainer addrs = opt->getAddresses();
+ if (!addrs.empty()) {
+ return (std::find(addrs.begin(), addrs.end(),
+ IOAddress(expected_address)) != addrs.end());
+ }
+ }
+ return (false);
+}
+
+uint16_t
+Dhcp6Client::getStatusCode(const uint32_t iaid) const {
+ std::map<uint32_t, uint16_t>::const_iterator status_code =
+ config_.status_codes_.find(iaid);
+ if (status_code == config_.status_codes_.end()) {
+ if (!getLeasesByIAID(iaid).empty()) {
+ return (STATUS_Success);
+ }
+
+ } else {
+ return (status_code->second);
+ }
+
+ return (0xFFFF);
+}
+
+bool
+Dhcp6Client::getTeeTimes(const uint32_t iaid, uint32_t& t1, uint32_t& t2) const {
+ // Sanity check.
+ if (!context_.response_) {
+ return (false);
+ }
+
+ // Get all options in the response message and pick IA_NA, IA_PD.
+ for (const auto& opt : context_.response_->options_) {
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt.second);
+ if (!ia) {
+ // This is not IA, so let's just skip it.
+ continue;
+ }
+ if (ia->getIAID() != iaid) {
+ // This is not the right IA, so let's just skip it.
+ continue;
+ }
+ // Found the IA.
+ t1 = ia->getT1();
+ t2 = ia->getT2();
+ return (true);
+ }
+
+ // Not found the IA.
+ return (false);
+}
+
+void
+Dhcp6Client::setDUID(const std::string& str) {
+ DUID d = DUID::fromText(str);
+ duid_.reset(new DUID(d));
+}
+
+void
+Dhcp6Client::modifyDUID() {
+ if (!duid_) {
+ duid_ = generateDUID(DUID::DUID_LLT);
+ return;
+ }
+ std::vector<uint8_t> new_duid = duid_->getDuid();
+ // Modify the DUID by adding 1 to its last byte.
+ ++new_duid[new_duid.size() - 1];
+ duid_.reset(new DUID(new_duid));
+}
+
+Pkt6Ptr
+Dhcp6Client::receiveOneMsg() {
+ return (srv_->receiveOneMsg());
+}
+
+void
+Dhcp6Client::receiveResponse() {
+ context_.response_ = receiveOneMsg();
+ // If the server has responded, store the configuration received.
+ if (context_.response_) {
+ config_.clear();
+ applyRcvdConfiguration(context_.response_);
+ }
+}
+
+void
+Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
+ srv_->shutdown_ = false;
+ // The client is configured to send through the relay. We achieve that
+ // adding the relay structure.
+ if (use_relay_ || !relay_info_.empty()) {
+ if (relay_info_.empty()) {
+ // Let's craft the relay info by hand
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = relay_link_addr_;
+ relay.peeraddr_ = asiolink::IOAddress("fe80::1");
+ relay.msg_type_ = DHCPV6_RELAY_FORW;
+ relay.hop_count_ = 1;
+
+ // Interface identifier, if specified.
+ if (interface_id_) {
+ relay.options_.insert(std::make_pair(D6O_INTERFACE_ID, interface_id_));
+ }
+
+ msg->relay_info_.push_back(relay);
+
+ } else {
+ // The test provided relay_info_, let's use that.
+ msg->relay_info_ = relay_info_;
+ }
+ }
+ // Repack the message to simulate wire-data parsing.
+ msg->pack();
+
+ Pkt6Ptr msg_copy(new Pkt6(static_cast<const uint8_t*>
+ (msg->getBuffer().getData()),
+ msg->getBuffer().getLength()));
+ msg_copy->setRemoteAddr(link_local_);
+ msg_copy->setLocalAddr(dest_addr_);
+ msg_copy->setIface(iface_name_);
+ msg_copy->setIndex(iface_index_);
+
+ // Copy classes
+ const ClientClasses& classes = msg->getClasses();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ msg_copy->addClass(*cclass);
+ }
+
+ srv_->fakeReceive(msg_copy);
+
+ try {
+ // Invoke run_one instead of run, because we want to avoid triggering
+ // IO service.
+ srv_->run_one();
+ } catch (...) {
+ // Suppress errors, as the DHCPv6 server does.
+ }
+
+ // Make sure the server processed all packets in MT.
+ isc::util::MultiThreadingMgr::instance().getThreadPool().wait(3);
+}
+
+void
+Dhcp6Client::requestAddress(const uint32_t iaid,
+ const asiolink::IOAddress& address) {
+ client_ias_.push_back(ClientIA(Lease::TYPE_NA, iaid, address, 128));
+}
+
+void
+Dhcp6Client::requestPrefix(const uint32_t iaid,
+ const uint8_t prefix_len,
+ const asiolink::IOAddress& prefix) {
+ client_ias_.push_back(ClientIA(Lease::TYPE_PD, iaid, prefix, prefix_len));
+}
+
+void
+Dhcp6Client::useInterfaceId(const std::string& interface_id) {
+ OptionBuffer buf(interface_id.begin(), interface_id.end());
+ interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, buf));
+}
+
+void
+Dhcp6Client::useFQDN(const uint8_t flags, const std::string& fqdn_name,
+ Option6ClientFqdn::DomainNameType fqdn_type) {
+ fqdn_.reset(new Option6ClientFqdn(flags, fqdn_name, fqdn_type));
+}
+
+void
+Dhcp6Client::addExtraOption(const OptionPtr& opt) {
+ extra_options_.insert(std::make_pair(opt->getType(), opt));
+}
+
+void
+Dhcp6Client::clearExtraOptions() {
+ extra_options_.clear();
+}
+
+void
+Dhcp6Client::addClass(const ClientClass& client_class) {
+ if (!classes_.contains(client_class)) {
+ classes_.insert(client_class);
+ }
+}
+
+void
+Dhcp6Client::clearClasses() {
+ classes_.clear();
+}
+
+void
+Dhcp6Client::printConfiguration() const {
+
+ // Print DUID
+ std::cout << "Client " << (duid_ ? duid_->toText() : "(without duid)")
+ << " got " << getLeaseNum() << " lease(s): ";
+
+ // Print leases
+ for (int i = 0; i < getLeaseNum(); i++) {
+ Lease6 lease = getLease(i);
+ std::cout << lease.addr_.toText() << " ";
+ }
+
+ /// @todo: Print many other parameters.
+ std::cout << std::endl;
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h
new file mode 100644
index 0000000..e236a3d
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_client.h
@@ -0,0 +1,973 @@
+// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DHCP6_CLIENT_H
+#define DHCP6_CLIENT_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcpsrv/lease.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <util/staged_value.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <set>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief DHCPv6 client used for unit testing.
+///
+/// This class implements a DHCPv6 "client" which interoperates with the
+/// @c NakedDhcpv6Srv class. It calls @c NakedDhcpv6Srv::fakeReceive to
+/// deliver client messages to the server for processing. The server places
+/// the response in the @c NakedDhcpv6Srv::fake_sent_ container. The client
+/// pops messages from this container which simulates reception of the
+/// response from the server.
+///
+/// The client maintains the leases it acquired from the server. If it has
+/// acquired the lease as a result of SARR exchange, it will use this lease
+/// when the Rebind process is triggered by the unit test.
+///
+/// The client exposes a set of functions which simulate different exchange
+/// types between the client and the server. It also provides the access to
+/// the objects encapsulating responses from the server so as it is possible
+/// to verify from the unit test that the server's response is correct.
+///
+/// @todo This class has been implemented to simplify the structure of the
+/// unit test and to make unit tests code self-explanatory. Currently,
+/// this class is mostly used by the unit tests which test Rebind processing
+/// logic. At some point we may want to use this class to test some other
+/// message types, e.g. Renew, in which case it may need to be extended.
+/// Also, once we implement the support for multiple IAAddr and IAPrefix
+/// options within single IA, the logic which maintains leases will have
+/// to be extended to support it.
+class Dhcp6Client : public boost::noncopyable {
+public:
+
+ /// @brief Holds an information about single lease.
+ struct LeaseInfo {
+ /// @brief A structure describing the lease.
+ Lease6 lease_;
+
+ /// @brief Holds the last status code that server has sent for
+ /// the particular lease.
+ uint16_t status_code_;
+
+ /// @brief Default constructor for the structure.
+ LeaseInfo() :
+ lease_(), status_code_(0) { }
+
+ /// @brief Constructor which sets the lease type.
+ ///
+ /// @param lease_type One of the D6O_IA_NA or D6O_IA_PD.
+ LeaseInfo(const uint16_t lease_type) :
+ lease_(), status_code_(0) {
+ lease_.type_ = lease_type == D6O_IA_NA ? Lease::TYPE_NA :
+ Lease::TYPE_PD;
+ }
+ };
+
+ /// @brief Holds the current client configuration obtained from the
+ /// server over DHCP.
+ ///
+ /// Currently it simply contains the collection of leases acquired
+ /// and a list of options. Note: this is a simple copy of all
+ /// non-IA options and often includes "protocol" options, like
+ /// server-id and client-id.
+ struct Configuration {
+ /// @brief List of received leases
+ std::vector<Lease6> leases_;
+
+ /// @brief A map of IAID, status code tuples.
+ std::map<uint32_t, uint16_t> status_codes_;
+
+ /// @brief List of received options
+ OptionCollection options_;
+
+ /// @brief Status code received in the global option scope.
+ uint16_t status_code_;
+
+ /// @brief Indicates if the status code has been received in the
+ /// last transaction.
+ bool received_status_code_;
+
+ /// @brief Constructor.
+ Configuration() {
+ clear();
+ }
+
+ /// @brief Clears configuration.
+ void clear() {
+ leases_.clear();
+ status_codes_.clear();
+ resetGlobalStatusCode();
+ options_.clear();
+ }
+
+ /// @brief Clears global status code.
+ ///
+ /// This function should be called before the new message is received.
+ void resetGlobalStatusCode() {
+ status_code_ = 0;
+ received_status_code_ = false;
+ }
+
+ /// @brief Finds an option with the specific code in the received
+ /// configuration.
+ ///
+ /// @param code Option code.
+ ///
+ /// @return Pointer to the option if the option exists, or NULL if
+ /// the option doesn't exist.
+ OptionPtr findOption(const uint16_t code) const {
+ std::multimap<unsigned int, OptionPtr>::const_iterator it = options_.find(code);
+ if (it != options_.end()) {
+ return (it->second);
+ }
+ return (OptionPtr());
+ }
+ };
+
+ /// @brief Holds the DHCPv6 messages taking part in transaction between
+ /// the client and the server.
+ struct Context {
+ /// @brief Holds the last sent message from the client to the server.
+ Pkt6Ptr query_;
+ /// @brief Holds the last sent message by the server to the client.
+ Pkt6Ptr response_;
+ };
+
+ /// @brief Structure holding information to be placed in client's IA.
+ struct ClientIA {
+ Lease::Type type_; ///< IA type
+ uint32_t iaid_; ///< IAID
+ asiolink::IOAddress prefix_; ///< prefix or address
+ uint8_t prefix_len_; ///< prefix length
+
+ /// @brief Constructor.
+ ///
+ /// @param type IA type.
+ /// @param iaid IAID.
+ /// @param prefix Address or prefix.
+ /// @param prefix_len Prefix length.
+ ClientIA(const Lease::Type& type, const uint32_t iaid,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len)
+ : type_(type), iaid_(iaid), prefix_(prefix),
+ prefix_len_(prefix_len) {
+ }
+ };
+
+ /// @brief Creates a new client.
+ ///
+ /// This constructor initializes the class members to default values:
+ /// - relay link-addr = 3000:1::1
+ /// - first transaction id = 0
+ /// - dest-addr = All_DHCP_Relay_Agents_and_Servers
+ /// - duid (LLT) = <random 4 bytes>00010203040506
+ /// - link-local-addr = fe80::3a60:77ff:fed5:cdef
+ /// - IA_NA not requested
+ /// - IA_PD not requested
+ /// - not relayed
+ Dhcp6Client();
+
+ /// @brief Creates a new client that communicates with a specified server.
+ ///
+ /// This constructor allows passing a pointer to the server object which
+ /// should be used in a test. The server may be preconfigured before passed
+ /// to the constructor. The default configuration used by the client is:
+ /// - relay link-addr = 3000:1::1
+ /// - first transaction id = 0
+ /// - dest-addr = All_DHCP_Relay_Agents_and_Servers
+ /// - duid (LLT) = <random 4 bytes>00010203040506
+ /// - link-local-addr = fe80::3a60:77ff:fed5:cdef
+ /// - IA_NA not requested
+ /// - IA_PD not requested
+ /// - not relayed
+ ///
+ /// @param srv Object representing server under test.
+ Dhcp6Client(boost::shared_ptr<isc::dhcp::test::NakedDhcpv6Srv>& srv);
+
+ /// @brief Create lease for the client.
+ ///
+ /// This function creates new lease on the client side without contacting
+ /// the server. This may be useful for the negative tests in which the
+ /// client is supposed to send invalid addresses/prefixes to the server
+ /// and expect certain responses.
+ ///
+ /// @param lease A lease to be applied for the client.
+ void createLease(const Lease6& lease);
+
+ /// @brief Performs a 4-way exchange between the client and the server.
+ ///
+ /// If the 4-way exchange is successful, the client should acquire leases
+ /// according to the server's current configuration and the type of leases
+ /// that have been requested (IA_NA, IA_PD).
+ ///
+ /// The leases acquired are accessible through the @c config_ member.
+ ///
+ /// @throw This function doesn't throw exceptions on its own, but it calls
+ /// functions that are not exception safe, so it may throw exceptions if
+ /// error occurs.
+ ///
+ /// @todo Perform sanity checks on returned messages.
+ void doSARR();
+
+ /// @brief Send Solicit and receive Advertise.
+ ///
+ /// This function simulates the first transaction of the 4-way exchange,
+ /// i.e. sends a Solicit to the server and receives Advertise. It doesn't
+ /// set the lease configuration in the @c config_.
+ ///
+ /// @param always_apply_config Apply received configuration even if the
+ /// Advertise message is received. Default value is false.
+ ///
+ /// @throw This function doesn't throw exceptions on its own, but it calls
+ /// functions that are not exception safe, so it may throw exceptions if
+ /// error occurs.
+ ///
+ /// @todo Perform sanity checks on returned messages.
+ void doSolicit(const bool always_apply_config = false);
+
+ /// @brief Sends a Renew to the server and receives the Reply.
+ ///
+ /// This function simulates sending the Renew message to the server and
+ /// receiving server's response (if any). The client uses existing leases
+ /// (either address or prefixes) and places them in the Renew message.
+ /// If the server responds to the Renew (and extends the lease lifetimes)
+ /// the current lease configuration is updated.
+ ///
+ /// @throw This function doesn't throw exceptions on its own, but it calls
+ /// functions that are not exception safe, so it may throw exceptions if
+ /// error occurs.
+ ///
+ /// @todo Perform sanity checks on returned messages.
+ void doRenew();
+
+ /// @brief Sends a Rebind to the server and receives the Reply.
+ ///
+ /// This function simulates sending the Rebind message to the server and
+ /// receiving server's response (if any). The client uses existing leases
+ /// (either address or prefixes) and places them in the Rebind message.
+ /// If the server responds to the Rebind (and extends the lease lifetimes)
+ /// the current lease configuration is updated.
+ ///
+ /// @throw This function doesn't throw exceptions on its own, but it calls
+ /// functions that are not exception safe, so it may throw exceptions if
+ /// error occurs.
+ ///
+ /// @todo Perform sanity checks on returned messages.
+ void doRebind();
+
+ /// @brief Sends Request to the server and receives Reply.
+ ///
+ /// This function simulates sending the Request message to the server and
+ /// receiving server's response (if any). The client copies IA options
+ /// from the current context (server's Advertise) to request acquisition
+ /// of offered IAs. If the server responds to the Request (leases are
+ /// acquired) the client's lease configuration is updated.
+ ///
+ /// @throw This function doesn't throw exceptions on its own, but it calls
+ /// functions that are not exception safe, so it may throw exceptions if
+ /// error occurs.
+ ///
+ /// @todo Perform sanity checks on returned messages.
+ void doRequest();
+
+ /// @brief Sends Confirm to the server and receives Reply.
+ ///
+ /// This function simulates sending the Confirm message to the server and
+ /// receiving server's response (if any).
+ void doConfirm();
+
+ /// @brief Sends Decline to the server and receives Reply.
+ ///
+ /// This function simulates sending the Decline message to the server and
+ /// receiving the server's response.
+ /// @param include_address should the address be included?
+ void doDecline(const bool include_address = true);
+
+ /// @brief Performs stateless (inf-request / reply) exchange.
+ ///
+ /// This function generates Information-request message, sends it
+ /// to the server and then receives the reply. Contents of the Inf-Request
+ /// are controlled by client_ias_, use_client_id_ and use_oro_
+ /// fields. This method does not process the response in any specific
+ /// way, just stores it.
+ void doInfRequest();
+
+ /// @brief Sends Release to the server.
+ ///
+ /// This function simulates sending the Release message to the server
+ /// and receiving server's response.
+ void doRelease();
+
+ /// @brief Removes the stateful configuration obtained from the server.
+ ///
+ /// It removes all leases held by the client.
+ void clearConfig() {
+ config_.clear();
+ }
+
+ /// @brief Simulates aging of leases by the specified number of seconds.
+ ///
+ /// This function moves back the time of acquired leases by the specified
+ /// number of seconds. It is useful for checking whether the particular
+ /// lease has been later updated (e.g. as a result of Rebind) as it is
+ /// expected that the fresh lease has cltt set to "now" (not to the time
+ /// in the past).
+ ///
+ /// @param secs Number of seconds by which the time should be moved.
+ /// @param update_server Indicates if the leases should be updated on the
+ /// server.
+ void fastFwdTime(const uint32_t secs, const bool update_server = false);
+
+ /// @brief Returns DUID option used by the client.
+ OptionPtr getClientId() const;
+
+ /// @brief Returns current context.
+ const Context& getContext() const {
+ return (context_);
+ }
+
+ /// @brief Returns the collection of IAIDs held by the client.
+ std::set<uint32_t> getIAIDs() const;
+
+ /// @brief Returns lease at specified index.
+ ///
+ /// @warning This method doesn't check if the specified index is out of
+ /// range. The caller is responsible for using a correct offset by
+ /// invoking the @c getLeaseNum function.
+ ///
+ /// @param at Index of the lease held by the client.
+ /// @return A lease at the specified index.
+ Lease6 getLease(const size_t at) const {
+ return (config_.leases_[at]);
+ }
+
+ /// @brief Returns collection of leases for specified IAID.
+ ///
+ /// @param iaid IAID for which the leases should be returned.
+ ///
+ /// @return Vector containing leases for the IAID.
+ std::vector<Lease6> getLeasesByIAID(const uint32_t iaid) const;
+
+ /// @brief Returns collection of leases by type.
+ ///
+ /// @param type Lease type: D6O_IA_NA or D6O_IA_PD.
+ ///
+ /// @return Vector containing leases of the specified type.
+ std::vector<Lease6> getLeasesByType(const Lease::Type& lease_type) const;
+
+ /// @brief Returns leases with non-zero lifetimes.
+ std::vector<Lease6> getLeasesWithNonZeroLifetime() const;
+
+ /// @brief Returns leases with zero lifetimes.
+ std::vector<Lease6> getLeasesWithZeroLifetime() const;
+
+ /// @brief Returns leases by lease address/prefix.
+ ///
+ /// @param address Leased address.
+ ///
+ /// @return Vector containing leases for the specified address.
+ std::vector<Lease6>
+ getLeasesByAddress(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns leases belonging to specified address range.
+ ///
+ /// @param first Lower bound of the address range.
+ /// @param second Upper bound of the address range.
+ ///
+ /// @return Vector containing leases belonging to specified address range.
+ std::vector<Lease6>
+ getLeasesByAddressRange(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& second) const;
+
+ /// @brief Returns leases belonging to prefix pool.
+ ///
+ /// @param prefix Prefix of the pool.
+ /// @param prefix_len Prefix length.
+ /// @param delegated_len Delegated prefix length.
+ ///
+ /// @return Vector containing leases belonging to specified prefix pool.
+ std::vector<Lease6>
+ getLeasesByPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const;
+
+ /// @brief Checks if client has lease for the specified address.
+ ///
+ /// @param address Address for which lease should be found.
+ ///
+ /// @return true if client has lease for the address, false otherwise.
+ bool hasLeaseForAddress(const asiolink::IOAddress& address) const;
+
+ /// @brief Checks if client has lease for the specified address in the
+ /// IA_NA identified by IAID.
+ ///
+ /// @param address Address for which lease should be found.
+ /// @param iaid IAID of the IA_NA in which the lease is expected.
+ bool hasLeaseForAddress(const asiolink::IOAddress& address,
+ const IAID& iaid) const;
+
+ /// @brief Checks if client has a lease for an address within range.
+ ///
+ /// @param first Lower bound of the address range.
+ /// @param last Upper bound of the address range.
+ ///
+ /// @return true if client has lease for the address within the range,
+ /// false otherwise.
+ bool hasLeaseForAddressRange(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& last) const;
+
+ /// @brief Checks if client has a lease with zero lifetimes for the
+ /// specified address.
+ ///
+ /// @param address Address for which lease should be found.
+ ///
+ /// @return true if client has a lease, false otherwise.
+ bool hasLeaseWithZeroLifetimeForAddress(const asiolink::IOAddress& address) const;
+
+ /// @brief Checks if client has a lease for a prefix.
+ ///
+ /// @param prefix Prefix.
+ /// @param prefix_len Prefix length.
+ ///
+ /// @return true if client has a lease for the specified prefix, false
+ /// otherwise.
+ bool hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const;
+
+ /// @brief Checks if client as a lease for prefix in the IA_PD identified
+ /// by specified IAID.
+ ///
+ /// @param prefix Prefix.
+ /// @param prefix_len Prefix length.
+ /// @param iaid IAID of the IA_PD in which the lease is expected.
+ bool hasLeaseForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const IAID& iaid) const;
+
+ /// @brief Checks if client has a lease belonging to a prefix pool.
+ ///
+ /// @param prefix Pool prefix.
+ /// @param prefix_len Prefix length.
+ /// @param delegated_len Delegated prefix length.
+ ///
+ /// @return true if client has a lease belonging to specified pool,
+ /// false otherwise.
+ bool hasLeaseForPrefixPool(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len) const;
+
+ /// @brief Checks if client has a lease with zero lifetimes for a prefix.
+ ///
+ /// @param prefix Prefix.
+ /// @param prefix_len Prefix length.
+ ///
+ /// @return true if client has a lease with zero lifetimes for a prefix.
+ bool hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const;
+
+ /// @brief Checks that specified option exists and contains a desired
+ /// address.
+ ///
+ /// The option must cast to the @ref Option6AddrLst type. The function
+ /// expects that this option contains at least one address and checks
+ /// first address for equality with @ref expected_address.
+ ///
+ /// @param option_type Option type.
+ /// @param expected_address Desired address.
+ /// @param config Configuration obtained from the server.
+ bool hasOptionWithAddress(const uint16_t option_type,
+ const std::string& expected_address) const;
+
+ /// @brief Returns the value of the global status code for the last
+ /// transaction.
+ uint16_t getStatusCode() const {
+ return (config_.status_code_);
+ }
+
+ /// @brief Returns status code set by the server for the IAID.
+ ///
+ /// @param iaid for which the status is desired
+ /// @return A status code for the given iaid
+ uint16_t getStatusCode(const uint32_t iaid) const;
+
+ /// @brief Returns T1 and T2 timers associated with a given iaid
+ ///
+ /// Changed to get the T1 an T2 times from acquired leases to
+ /// IA options.
+ ///
+ /// @param iaid iaid of the target IA
+ /// @param[out] t1 set to the value of the IA's T1
+ /// @param[out] t2 set to the value of the IA's T2
+ /// @return true if there is an IA option for the given iaid
+ bool getTeeTimes(const uint32_t iaid, uint32_t& t1, uint32_t& t2) const;
+
+ /// @brief Returns number of acquired leases.
+ size_t getLeaseNum() const {
+ return (config_.leases_.size());
+ }
+
+ /// @brief Returns the server that the client is communicating with.
+ boost::shared_ptr<isc::dhcp::test::NakedDhcpv6Srv>& getServer() {
+ return (srv_);
+ }
+
+ /// @brief Sets the client's DUID from a string value
+ ///
+ /// Replaces the client's DUID with one constructed from the given
+ /// string. The string is expected to contain hexadecimal digits with or
+ /// without ":" separators.
+ ///
+ /// @param str The string of digits from which to create the DUID
+ ///
+ /// The DUID modification affects the value returned by the
+ /// @c Dhcp6Client::getClientId
+ void setDUID(const std::string& duid_str);
+
+ /// @brief Modifies the client's DUID (adds one to it).
+ ///
+ /// The DUID should be modified to test negative scenarios when the client
+ /// acquires a lease and tries to renew it with a different DUID. The server
+ /// should detect the DUID mismatch and react accordingly.
+ ///
+ /// The DUID modification affects the value returned by the
+ /// @c Dhcp6Client::getClientId
+ void modifyDUID();
+
+ /// @brief Checks if the global status code was received in the response
+ /// from the server.
+ ///
+ /// @return true if the global status code option was received.
+ bool receivedStatusCode() const {
+ return (config_.received_status_code_);
+ }
+
+ /// @brief Sets destination address for the messages being sent by the
+ /// client.
+ ///
+ /// By default, the client uses All_DHCP_Relay_Agents_and_Servers
+ /// multicast address to communicate with the server. In certain cases
+ /// it may be desired that different address is used (e.g. unicast in Renew).
+ /// This function sets the new address for all future exchanges with the
+ /// server.
+ ///
+ /// @param dest_addr New destination address.
+ void setDestAddress(const asiolink::IOAddress& dest_addr) {
+ dest_addr_ = dest_addr;
+ }
+
+ /// @brief Sets the interface to be used by the client.
+ ///
+ /// @param iface_name Interface name.
+ void setInterface(const std::string& iface_name) {
+ iface_name_ = iface_name;
+ }
+
+ /// @brief Sets the interface to be used by the client.
+ ///
+ /// @param iface_index Interface index.
+ void setIfaceIndex(uint32_t iface_index) {
+ iface_index_ = iface_index;
+ }
+
+ /// @brief Sets link local address used by the client.
+ ///
+ /// @param link_local New link local address.
+ void setLinkLocal(const asiolink::IOAddress& link_local) {
+ link_local_ = link_local;
+ }
+
+ /// @brief Specifies address to be included in client's message.
+ ///
+ /// This method specifies IPv6 address to be included within IA_NA
+ /// option sent by the client. In order to specify multiple addresses
+ /// to be included in a particular IA_NA, this method must be called
+ /// multiple times to specify each address separately. In such case,
+ /// the value of the IAID should remain the same across all calls to
+ /// this method.
+ ///
+ /// This method is typically called to specify IA_NA options to be
+ /// sent to the server during 4-way handshakes and during lease
+ /// renewal to request allocation of new leases (as per RFC 8415).
+ ///
+ /// @param iaid IAID.
+ /// @param address IPv6 address to be included in the IA_NA. It defaults
+ /// to IPv6 zero address, which indicates that no address should be
+ /// included in the IA_NA (empty IA_NA will be sent).
+ void requestAddress(const uint32_t iaid = 1234,
+ const asiolink::IOAddress& address =
+ asiolink::IOAddress::IPV6_ZERO_ADDRESS());
+
+ /// @brief Specifies IPv6 prefix to be included in client's message.
+ ///
+ /// This method specifies IPv6 prefix to be included within IA_PD
+ /// option sent by the client. In order to specify multiple prefixes
+ /// to be included in a particular IA_PD, this method must be called
+ /// multiple times to specify each prefix separately. In such case,
+ /// the value of the IAID should remain the same across all calls to
+ /// this method.
+ ///
+ /// This method is typically called to specify IA_PD options to be
+ /// sent to the server during 4-way handshakes and during lease
+ /// renewal to request allocation of new leases (as per RFC 8415).
+ ///
+ /// @param iaid IAID.
+ /// @param prefix_len Prefix length.
+ /// @param prefix Prefix to be included. This value defaults to the
+ /// IPv6 zero address. If zero address is specified and prefix_len is
+ /// set to 0, the IA Prefix option will not be included in the IA_PD.
+ /// If the prefix_len is non-zero and the prefix is IPv6 zero address
+ /// the prefix length hint will be included in the IA Prefix option.
+ void requestPrefix(const uint32_t iaid = 5678,
+ const uint8_t prefix_len = 0,
+ const asiolink::IOAddress& prefix =
+ asiolink::IOAddress::IPV6_ZERO_ADDRESS());
+
+ /// @brief Removes IAs specified by @ref requestAddress and
+ /// @ref requestPrefix methods.
+ ///
+ /// If this method is called and the client initiates an exchange with
+ /// a server the client will only include IAs for which it has leases.
+ /// If the client has no leases (e.g. a Solicit case), no IAs will be
+ /// included in the client's message.
+ void clearRequestedIAs() {
+ client_ias_.clear();
+ }
+
+ /// @brief Simulate sending messages through a relay.
+ ///
+ /// @param use Parameter which 'true' value indicates that client should
+ /// simulate sending messages via relay.
+ /// @param link_addr Relay link-addr.
+ void useRelay(const bool use = true,
+ const asiolink::IOAddress& link_addr = asiolink::IOAddress("3000:1::1")) {
+ use_relay_ = use;
+ relay_link_addr_ = link_addr;
+ }
+
+ /// @brief Sets interface id value to be inserted into relay agent option.
+ ///
+ /// @param interface_id Value of the interface id as string.
+ void useInterfaceId(const std::string& interface_id);
+
+ /// @brief Controls whether the client should send a client-id or not
+ /// @param send should the client-id be sent?
+ void useClientId(const bool send) {
+ use_client_id_ = send;
+ }
+
+ /// @brief Specifies if the Rapid Commit option should be included in
+ /// the Solicit message.
+ ///
+ /// @param rapid_commit Boolean parameter controlling if the Rapid Commit
+ /// option must be included in the Solicit (if true), or not (if false).
+ void useRapidCommit(const bool rapid_commit) {
+ use_rapid_commit_ = rapid_commit;
+ }
+
+ /// @brief Specifies server-id to be used in send messages
+ ///
+ /// Overrides the server-id to be sent when server-id is expected to be
+ /// sent. May be NULL, which means use proper server-id sent in Advertise
+ /// (which is a normal client behavior).
+ ///
+ /// @param server_id server-id to be sent
+ void useServerId(const OptionPtr& server_id) {
+ forced_server_id_ = server_id;
+ }
+
+ /// @brief Creates an instance of the Client FQDN option to be included
+ /// in the client's message.
+ ///
+ /// @param flags Flags.
+ /// @param fqdn_name Name in the textual format.
+ /// @param fqdn_type Type of the name (fully qualified or partial).
+ void useFQDN(const uint8_t flags, const std::string& fqdn_name,
+ Option6ClientFqdn::DomainNameType fqdn_type);
+
+ /// @brief Lease configuration obtained by the client.
+ Configuration config_;
+
+ /// @brief Link address of the relay to be used for relayed messages.
+ asiolink::IOAddress relay_link_addr_;
+
+ /// @brief RelayInfo (information about relays)
+ ///
+ /// Dhcp6Client will typically construct this info itself, but if
+ /// it is provided here by the test, this data will be used as is.
+ std::vector<Pkt6::RelayInfo> relay_info_;
+
+ /// @brief Controls whether the client will send ORO
+ ///
+ /// The actual content of the ORO is specified in oro_.
+ /// It is useful to split the actual content and the ORO sending
+ /// decision, so we could test cases of sending empty ORO.
+ /// @param send controls whether ORO will be sent or not.
+ void useORO(bool send) {
+ use_oro_ = send;
+ }
+
+ /// @brief Instructs client to request specified option in ORO
+ ///
+ /// @param option_code client will request this option code
+ void requestOption(uint16_t option_code) {
+ use_oro_ = true;
+ oro_.push_back(option_code);
+ }
+
+ /// @brief Controls whether the client will send DOCSIS vendor ORO
+ ///
+ /// The actual content of the ORO is specified in docsis_oro_.
+ /// It is useful to split the actual content and the ORO sending
+ /// decision, so we could test cases of sending empty ORO.
+ /// @param send controls whether ORO will be sent or not.
+ void useDocsisORO(bool send) {
+ use_docsis_oro_ = send;
+ }
+
+ /// @brief Instructs client to request specified option in DOCSIS
+ /// vendor ORO
+ ///
+ /// @param option_code client will request this option code
+ void requestDocsisOption(uint16_t option_code) {
+ use_docsis_oro_ = true;
+ docsis_oro_.push_back(option_code);
+ }
+
+ /// @brief returns client-id
+ /// @return client-id
+ DuidPtr getDuid() const {
+ return (duid_);
+ }
+
+ /// @brief Generates IA_NA based on lease information
+ ///
+ /// @param query generated IA_NA options will be added here
+ /// @param include_address should the address be included?
+ void generateIAFromLeases(const Pkt6Ptr& query,
+ const bool include_address = true);
+
+ /// @brief Adds extra option (an option the client will always send)
+ ///
+ /// @param opt additional option to be sent
+ void addExtraOption(const OptionPtr& opt);
+
+ /// @brief Configures the client to not send any extra options.
+ void clearExtraOptions();
+
+ /// @brief Add a client class.
+ ///
+ /// @param client_class name of the class to be added.
+ void addClass(const ClientClass& client_class);
+
+ /// @brief Configures the client to not add client classes.
+ void clearClasses();
+
+ /// @brief Debugging method the prints currently held configuration
+ ///
+ /// @todo: This is mostly useful when debugging tests. This method
+ /// is incomplete. Please extend it when needed.
+ void printConfiguration() const;
+
+ /// @brief Receives a response from the server.
+ ///
+ /// This method is useful to receive response from the server after
+ /// parking a packet. In this case, the packet is not received as a
+ /// result of initial exchange, e.g. @c doRequest. The test can call
+ /// this method to complete the transaction when it expects that the
+ /// packet has been unparked.
+ void receiveResponse();
+
+private:
+
+ /// @brief Applies the new leases for the client.
+ ///
+ /// This method is called when the client obtains a new configuration
+ /// from the server in the Reply message. This function adds new leases
+ /// or replaces existing ones, on the client's side. Client uses these
+ /// leases in any later communication with the server when doing Renew
+ /// or Rebind.
+ ///
+ /// @param reply Server response.
+ /// @param state specifies lease state (see Lease::STATE_* for details).
+ ///
+ /// The default for state is 0. We could have included dhcpsrv/lease.h
+ /// and used Lease::STATE_DEFAULT, but that would complicate the header
+ /// inclusion dependencies. It's easier to simply use 0 as the default.
+ ///
+ /// @todo Currently this function supports one IAAddr or IAPrefix option
+ /// within IA. We will need to extend it to support multiple options
+ /// within a single IA once server supports that.
+ void applyRcvdConfiguration(const Pkt6Ptr& reply, uint32_t state = 0);
+
+ /// @brief Applies configuration for the single lease.
+ ///
+ /// This method is called by the @c Dhcp6Client::applyRcvdConfiguration for
+ /// each individual lease.
+ ///
+ /// @param lease_info Structure holding new lease information.
+ void applyLease(const Lease6& lease);
+
+ /// @brief Includes Client FQDN in the client's message.
+ ///
+ /// This method checks if @c fqdn_ is specified and includes it in
+ /// the client's message.
+ void appendFQDN();
+
+ /// @brief Includes IAs to be requested.
+ ///
+ /// This method includes IAs explicitly requested using client_ias_
+ ///
+ /// @param query Pointer to the client's message to which IAs should be
+ /// added.
+ void appendRequestedIAs(const Pkt6Ptr& query) const;
+
+ /// @brief Copy IA options from one message to another.
+ ///
+ /// This method copies IA_NA and IA_PD options from one message to another.
+ /// It is useful when the client needs to construct the Request message
+ /// using addresses and prefixes returned by the client in Advertise.
+ ///
+ /// @param source Message from which IA options will be copied.
+ /// @param dest Message to which IA options will be copied.
+ ///
+ /// @todo Add support for IA_TA.
+ void copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest);
+
+ /// @brief Creates IA options from existing configuration.
+ ///
+ /// This method iterates over existing leases that client acquired and
+ /// places corresponding IA_NA or IA_PD options into a specified message.
+ /// This is useful to construct Renew, Rebind or Confirm message from the
+ /// existing configuration that client has obtained using 4-way exchange.
+ ///
+ /// If there are no leases no IA options will be added. If the lease exists
+ /// but any of the lifetime values is set to 0, the IA option will be added
+ /// but the IAAddr (or IAPrefix) option will not be added.
+ ///
+ /// @param dest Message to which the IA options will be added.
+ void copyIAsFromLeases(const Pkt6Ptr& dest) const;
+
+ /// @brief Creates client's side DHCP message.
+ ///
+ /// @param msg_type Type of the message to be created.
+ /// @return An instance of the message created.
+ Pkt6Ptr createMsg(const uint8_t msg_type);
+
+ /// @brief Generates DUID for the client.
+ ///
+ /// @param duid_type Type of the DUID. Currently, only LLT is accepted.
+ /// @return Object encapsulating a DUID.
+ DuidPtr generateDUID(DUID::DUIDType duid_type) const;
+
+ /// @brief Returns client's leases which match the specified condition.
+ ///
+ /// @param property A value of the lease property used to search the lease.
+ /// @param equals A flag which indicates if the operator should search
+ /// for the leases which property is equal to the value of @c property
+ /// parameter (if true), or unequal (if false).
+ /// @param [out] leases A vector in which the operator will store leases
+ /// found.
+ ///
+ /// @tparam BaseType Base type to which the property belongs: @c Lease or
+ /// @c Lease6.
+ /// @tparam PropertyType A type of the property, e.g. @c uint32_t for IAID.
+ /// @tparam MemberPointer A pointer to the member, e.g. @c &Lease6::iaid_.
+ template<typename BaseType, typename PropertyType,
+ PropertyType BaseType::*MemberPointer>
+ void getLeasesByProperty(const PropertyType& property, const bool equals,
+ std::vector<Lease6>& leases) const;
+
+ /// @brief Simulates reception of the message from the server.
+ ///
+ /// @return Received message.
+ Pkt6Ptr receiveOneMsg();
+
+ /// @brief Simulates sending a message to the server.
+ ///
+ /// This function instantly triggers processing of the message by the
+ /// server. The server's response can be gathered by invoking the
+ /// @c receiveOneMsg function.
+ ///
+ /// @param msg Message to be sent.
+ void sendMsg(const Pkt6Ptr& msg);
+
+ /// @brief Current context (sent and received message).
+ Context context_;
+
+ /// @brief Current transaction id (altered on each send).
+ uint32_t curr_transid_;
+
+ /// @brief Currently used destination address.
+ asiolink::IOAddress dest_addr_;
+
+ /// @brief Currently used DUID.
+ DuidPtr duid_;
+
+ /// @brief Currently used link local address.
+ asiolink::IOAddress link_local_;
+
+ /// @brief Currently used interface (name).
+ std::string iface_name_;
+
+ /// @brief Currently used interface (index).
+ uint32_t iface_index_;
+
+ /// @brief Pointer to the server that the client is communicating with.
+ boost::shared_ptr<isc::dhcp::test::NakedDhcpv6Srv> srv_;
+
+ bool use_relay_; ///< Enable relaying messages to the server.
+
+ bool use_oro_; ///< Conth
+ bool use_docsis_oro_;
+ bool use_client_id_;
+ bool use_rapid_commit_;
+
+ /// @brief List holding information to be sent in client's IAs.
+ std::list<ClientIA> client_ias_;
+
+ /// @brief List of options to be requested
+ ///
+ /// Content of this vector will be sent as ORO if use_oro_ is set
+ /// to true. See @ref sendORO for details.
+ std::vector<uint16_t> oro_;
+
+ /// @brief List of DOCSIS vendor options to be requested
+ ///
+ /// Content of this vector will be sent as DOCSIS vendor ORO if
+ /// use_docsis_oro_ is set to true. See @ref sendDocsisORO for details.
+ std::vector<uint16_t> docsis_oro_;
+
+ /// @brief forced (Overridden) value of the server-id option (may be NULL)
+ OptionPtr forced_server_id_;
+
+ /// @brief Extra options the client will send.
+ OptionCollection extra_options_;
+
+ /// @brief FQDN requested by the client.
+ Option6ClientFqdnPtr fqdn_;
+
+ /// @brief Interface id.
+ OptionPtr interface_id_;
+
+ /// @brief Extra classes to add to the query.
+ ClientClasses classes_;
+};
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // DHCP6_CLIENT
diff --git a/src/bin/dhcp6/tests/dhcp6_message_test.cc b/src/bin/dhcp6/tests/dhcp6_message_test.cc
new file mode 100644
index 0000000..b47e242
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_message_test.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Dhcpv6MessageTest::Dhcpv6MessageTest()
+ : Dhcpv6SrvTest(),
+ iface_mgr_test_config_(true) {
+}
+
+IOAddress
+Dhcpv6MessageTest::bumpAddress(const IOAddress& input_addr) {
+ return (bumpByteInAddress(input_addr, V6ADDRESS_LEN - 1));
+}
+
+IOAddress
+Dhcpv6MessageTest::bumpByteInAddress(const IOAddress& input_addr,
+ const size_t byte_num) {
+ std::vector<uint8_t> input_addr_buffer = input_addr.toBytes();
+ if (input_addr_buffer.size() > byte_num) {
+ ++input_addr_buffer[byte_num];
+ return (IOAddress::fromBytes(AF_INET6, &input_addr_buffer[0]));
+ }
+ return (input_addr);
+}
+
+IOAddress
+Dhcpv6MessageTest::bumpSubnet(const IOAddress& input_addr) {
+ return (bumpByteInAddress(input_addr, 0));
+}
+
+void
+Dhcpv6MessageTest::requestLease(const std::string& config,
+ const int subnets_num,
+ Dhcp6Client& client) {
+ // Configure the server.
+ configure(config, *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_EQ(subnets_num, subnets->size());
+ // Do the actual 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Simulate aging of leases, by moving their cltt_ back by 1000s.
+ client.fastFwdTime(1000);
+ // Make sure that we have obtained a lease that belongs to one of the
+ // subnets.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+
+ // Check if the lease belongs to one of the available pools.
+ bool pool_found = false;
+ auto subnet = subnets->begin();
+ for (int i = 0; i < subnets_num; ++i, ++subnet) {
+ ASSERT_TRUE(subnet != subnets->end());
+ if ((*subnet)->getPool(lease_client.type_, lease_client.addr_)) {
+ pool_found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(pool_found);
+
+ // Check that the client's lease matches the information on the server
+ // side.
+ Lease6Ptr lease_server = checkLease(lease_client);
+ ASSERT_TRUE(lease_server);
+ // And that status code was OK.
+ ASSERT_EQ(STATUS_Success, client.getStatusCode(lease_client.iaid_));
+}
+
+}
+}
+}
diff --git a/src/bin/dhcp6/tests/dhcp6_message_test.h b/src/bin/dhcp6/tests/dhcp6_message_test.h
new file mode 100644
index 0000000..0b7e7ad
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_message_test.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2014-2015,2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef DHCP6_MESSAGE_TEST_H
+#define DHCP6_MESSAGE_TEST_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Base class for test fixture classes used to validate the DHCPv6
+/// message processing by the server.
+class Dhcpv6MessageTest : public Dhcpv6SrvTest {
+public:
+ /// @brief Constructor.
+ ///
+ /// Sets up interfaces.
+ Dhcpv6MessageTest();
+
+ /// @brief Increases last byte of the address.
+ ///
+ /// This function is helpful to find a different address that is within
+ /// the same subnet as the input address. It is achieved by increasing
+ /// the last byte of the input address by one.
+ ///
+ /// @param input_addr An input address.
+ ///
+ /// @return New address.
+ asiolink::IOAddress bumpAddress(const asiolink::IOAddress& input_addr);
+
+ /// @brief Increases specific byte in the address by one.
+ ///
+ /// This function is called by @c bumpAddress and @c bumpSubnet.
+ ///
+ /// @warning This function is no-op if the byte index is out of range.
+ ///
+ /// @param input_addr An input address
+ /// @param byte_num An index of the byte which value should be increased..
+ ///
+ /// @return New address.
+ asiolink::IOAddress bumpByteInAddress(const asiolink::IOAddress& input_addr,
+ const size_t byte_num);
+
+ /// @brief Increases the first byte of the address.
+ ///
+ /// This function is helpful to find an address which belongs to the
+ /// different subnet than the input address. It is achieved by increasing
+ /// the first byte of the input address.
+ ///
+ /// @param input_addr An input addres.
+ ///
+ /// @return New address.
+ asiolink::IOAddress bumpSubnet(const asiolink::IOAddress& input_addr);
+
+ /// @brief Make 4-way exchange to obtain a lease.
+ ///
+ /// @param config Configuration in the JSON format to be applied before the
+ /// lease is requested..
+ /// @param subnets_num Number of subnets being created with the specified
+ /// configuration.
+ /// @param client Object representing a test DHCPv6 client to use.
+ void requestLease(const std::string& config, const int subnets_num,
+ Dhcp6Client& client);
+
+
+protected:
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+} // isc::dhcp::test
+} // isc::dhcp
+} // isc
+
+#endif // DHCP6_MESSAGE_TEST_H
diff --git a/src/bin/dhcp6/tests/dhcp6_process_tests.sh.in b/src/bin/dhcp6/tests/dhcp6_process_tests.sh.in
new file mode 100644
index 0000000..0857287
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_process_tests.sh.in
@@ -0,0 +1,611 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Path to the temporary configuration file.
+CFG_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test_config.json"
+# Path to the Kea log file.
+LOG_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test.log"
+# Path to the Kea lease file.
+LEASE_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test_leases.csv"
+# Path to the Kea LFC application
+export KEA_LFC_EXECUTABLE="@abs_top_builddir@/src/bin/lfc/kea-lfc"
+# Path to test hooks library
+HOOK_PATH="@abs_top_builddir@/src/bin/dhcp6/tests/.libs/libco3.so"
+# Kea configuration to be stored in the configuration file.
+CONFIG="{
+ \"Dhcp6\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"server-id\": {
+ \"type\": \"LLT\",
+ \"persist\": false
+ },
+ \"preferred-lifetime\": 3000,
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"name\": \"$LEASE_FILE\",
+ \"persist\": false,
+ \"lfc-interval\": 0
+ },
+ \"subnet6\": [
+ {
+ \"subnet\": \"2001:db8:1::/64\",
+ \"id\": 1,
+ \"pools\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
+ } ],
+ \"dhcp-ddns\": {
+ \"enable-updates\": true,
+ \"qualifying-suffix\": \"\"
+ },
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp6\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (syntax error) to check that Kea can check syntax.
+# This config has following errors:
+# - it should be interfaces-config/interfaces, not interfaces
+# - it should be subnet6/pools, no subnet6/pool
+CONFIG_BAD_SYNTAX="{
+ \"Dhcp6\":
+ {
+ \"interfaces\": [ ],
+ \"preferred-lifetime\": 3000,
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"persist\": false
+ },
+ \"subnet6\": [
+ {
+ \"subnet\": \"2001:db8:1::/64\",
+ \"id\": 1,
+ \"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
+ } ],
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp6\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (negative preferred-lifetime) to check that Kea
+# gracefully handles reconfiguration errors.
+CONFIG_INVALID="{
+ \"Dhcp6\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"preferred-lifetime\": -3,
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"persist\": false
+ },
+ \"subnet6\": [
+ {
+ \"subnet\": \"2001:db8:1::/64\",
+ \"id\": 1,
+ \"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
+ } ],
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp6\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# This config has bad pool values. The pool it out of scope for the subnet
+# it is defined in. Syntactically the config is correct, though.
+CONFIG_BAD_VALUES="{
+ \"Dhcp6\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"server-id\": {
+ \"type\": \"LLT\",
+ \"persist\": false
+ },
+ \"preferred-lifetime\": 3000,
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"name\": \"$LEASE_FILE\",
+ \"persist\": false,
+ \"lfc-interval\": 0
+ },
+ \"subnet6\": [
+ {
+ \"subnet\": \"2001:db8::/64\",
+ \"id\": 1,
+ \"pools\": [ { \"pool\": \"3000::-3000::ffff\" } ]
+ } ],
+ \"dhcp-ddns\": {
+ \"enable-updates\": true,
+ \"qualifying-suffix\": \"\"
+ }
+ }
+}"
+
+# Invalid configuration (hook explicitly fails to load) to check that performing
+# extra configuration checks detects the error.
+INVALID_CONFIG_HOOKS_LOAD="{
+ \"Dhcp6\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"multi-threading\": {
+ \"enable-multi-threading\": false
+ },
+ \"server-id\": {
+ \"type\": \"LLT\",
+ \"persist\": false
+ },
+ \"preferred-lifetime\": 3000,
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"name\": \"$LEASE_FILE\",
+ \"persist\": false,
+ \"lfc-interval\": 0
+ },
+ \"subnet6\": [
+ {
+ \"subnet\": \"2001:db8:1::/64\",
+ \"id\": 1,
+ \"pools\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
+ } ],
+ \"dhcp-ddns\": {
+ \"enable-updates\": true,
+ \"qualifying-suffix\": \"\"
+ },
+ \"hooks-libraries\": [
+ {
+ \"library\": \"$HOOK_PATH\",
+ \"parameters\": {
+ \"mode\": \"fail-on-load\"
+ }
+ } ],
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp6\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (hook point returns error) to check that performing
+# extra configuration checks detects the error.
+INVALID_CONFIG_HOOKS_CALLOUT_FAIL="{
+ \"Dhcp6\":
+ {
+ \"interfaces-config\": {
+ \"interfaces\": [ ]
+ },
+ \"multi-threading\": {
+ \"enable-multi-threading\": false
+ },
+ \"server-id\": {
+ \"type\": \"LLT\",
+ \"persist\": false
+ },
+ \"preferred-lifetime\": 3000,
+ \"valid-lifetime\": 4000,
+ \"renew-timer\": 1000,
+ \"rebind-timer\": 2000,
+ \"lease-database\":
+ {
+ \"type\": \"memfile\",
+ \"name\": \"$LEASE_FILE\",
+ \"persist\": false,
+ \"lfc-interval\": 0
+ },
+ \"subnet6\": [
+ {
+ \"subnet\": \"2001:db8:1::/64\",
+ \"id\": 1,
+ \"pools\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
+ } ],
+ \"dhcp-ddns\": {
+ \"enable-updates\": true,
+ \"qualifying-suffix\": \"\"
+ },
+ \"hooks-libraries\": [
+ {
+ \"library\": \"$HOOK_PATH\",
+ \"parameters\": {
+ \"mode\": \"fail-without-error\"
+ }
+ } ],
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp6\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Set the location of the executable.
+bin="kea-dhcp6"
+bin_path="@abs_top_builddir@/src/bin/dhcp6"
+
+# Import common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# This test verifies that syntax checking works properly. This function
+# requires 3 parameters:
+# test_name
+# config - string with a content of the config (will be written to a file)
+# expected_code - expected exit code returned by kea (0 - success, 1 - failure)
+syntax_check_test() {
+ local test_name="${1}"
+ local config="${2}"
+ local expected_code="${3}"
+ local check_type="${4}"
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create correct configuration file.
+ create_config "${config}"
+ # Check it
+ printf "Running command %s.\n" "\"${bin_path}/${bin} ${check_type} ${CFG_FILE}\""
+ run_command \
+ "${bin_path}/${bin}" "${check_type}" "${CFG_FILE}"
+ if [ "${EXIT_CODE}" -ne "${expected_code}" ]; then
+ printf 'ERROR: expected exit code %s, got %s\n' "${expected_code}" "${EXIT_CODE}"
+ clean_exit 1
+ fi
+
+ test_finish 0
+}
+
+# This test verifies that DHCPv6 can be reconfigured with a SIGHUP signal.
+dynamic_reconfiguration_test() {
+ # Log the start of the test and print test name.
+ test_start "dhcpv6_srv.dynamic_reconfiguration"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct Kea to log to the specific file.
+ set_logger
+ # Start Kea.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for Kea to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for Kea to start.\n"
+ clean_exit 1
+ fi
+
+ # Check if it is still running. It could have terminated (e.g. as a result
+ # of configuration failure).
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one Kea process to be started. Found %d processes\
+ started.\n" "${_GET_PIDS_NUM}"
+ clean_exit 1
+ fi
+
+ # Check in the log file, how many times server has been configured. It should
+ # be just once on startup.
+ get_reconfigs
+ if [ "${_GET_RECONFIGS}" -ne 1 ]; then
+ printf "ERROR: server hasn't been configured.\n"
+ clean_exit 1
+ else
+ printf "Server successfully configured.\n"
+ fi
+
+ # Now use invalid configuration.
+ create_config "${CONFIG_INVALID}"
+
+ # Try to reconfigure by sending SIGHUP
+ send_signal 1 ${bin}
+
+ # The configuration should fail and the error message should be there.
+ wait_for_message 10 "DHCP6_CONFIG_LOAD_FAIL" 1
+
+ # After receiving SIGHUP the server should try to reconfigure itself.
+ # The configuration provided is invalid so it should result in
+ # reconfiguration failure but the server should still be running.
+ get_reconfigs
+ if [ "${_GET_RECONFIGS}" -ne 1 ]; then
+ printf "ERROR: server has been reconfigured despite bogus configuration.\n"
+ clean_exit 1
+ elif [ "${_GET_RECONFIG_ERRORS}" -ne 1 ]; then
+ printf "ERROR: server did not report reconfiguration error despite attempt\
+ to configure it with invalid configuration.\n"
+ clean_exit 1
+ fi
+
+ # Make sure the server is still operational.
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
+ clean_exit 1
+ fi
+
+ # Restore the good configuration.
+ create_config "${CONFIG}"
+
+ # Reconfigure the server with SIGHUP.
+ send_signal 1 ${bin}
+
+ # There should be two occurrences of the DHCP6_CONFIG_COMPLETE messages.
+ # Wait for it up to 10s.
+ wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2
+
+ # After receiving SIGHUP the server should get reconfigured and the
+ # reconfiguration should be noted in the log file. We should now
+ # have two configurations logged in the log file.
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: server hasn't been reconfigured.\n"
+ clean_exit 1
+ else
+ printf "Server successfully reconfigured.\n"
+ fi
+
+ # Make sure the server is still operational.
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
+ clean_exit 1
+ fi
+
+ # When the server receives a signal the call to select() function is
+ # interrupted. This should not be logged as an error.
+ get_log_messages "DHCP6_PACKET_RECEIVE_FAIL"
+ assert_eq 0 "${_GET_LOG_MESSAGES}" \
+ "Expected get_log_messages DHCP6_PACKET_RECEIVE_FAIL return %d, \
+returned %d."
+
+ # All ok. Shut down Kea and exit.
+ test_finish 0
+}
+
+# This test verifies that DHCPv6 server is shut down gracefully when it
+# receives a SIGINT or SIGTERM signal.
+shutdown_test() {
+ local test_name="${1}" # Test name
+ local signum="${2}" # Signal number
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct Kea to log to the specific file.
+ set_logger
+ # Start Kea.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for Kea to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for Kea to start.\n"
+ clean_exit 1
+ fi
+
+ # Check if it is still running. It could have terminated (e.g. as a result
+ # of configuration failure).
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one Kea process to be started. Found %d processes\
+ started.\n" "${_GET_PIDS_NUM}"
+ clean_exit 1
+ fi
+
+ # Check in the log file, how many times server has been configured. It should
+ # be just once on startup.
+ get_reconfigs
+ if [ "${_GET_RECONFIGS}" -ne 1 ]; then
+ printf "ERROR: server hasn't been configured.\n"
+ clean_exit 1
+ else
+ printf "Server successfully configured.\n"
+ fi
+
+ # Send signal to Kea (SIGTERM, SIGINT etc.)
+ send_signal "${signum}" "${bin}"
+
+ # Wait up to 10s for the server's graceful shutdown. The graceful shut down
+ # should be recorded in the log file with the appropriate message.
+ wait_for_message 10 "DHCP6_SHUTDOWN" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: Server did not record shutdown in the log.\n"
+ clean_exit 1
+ fi
+
+ # Make sure the server is down.
+ wait_for_server_down 5 ${bin}
+ assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \
+ "Expected wait_for_server_down return %d, returned %d"
+
+ # When the server receives a signal the call to select() function is
+ # interrupted. This should not be logged as an error.
+ get_log_messages "DHCP6_PACKET_RECEIVE_FAIL"
+ assert_eq 0 "${_GET_LOG_MESSAGES}" \
+ "Expected get_log_messages DHCP6_PACKET_RECEIVE_FAIL return %d, \
+returned %d."
+
+ test_finish 0
+}
+
+# This test verifies that DHCPv6 can be configured to run lease file cleanup
+# periodically.
+lfc_timer_test() {
+ # Log the start of the test and print test name.
+ test_start "dhcpv6_srv.lfc_timer_test"
+ # Create a configuration with the LFC enabled, by replacing the section
+ # with the lfc-interval and persist parameters.
+ LFC_CONFIG=$(printf '%s' "${CONFIG}" | sed -e 's/\"lfc-interval\": 0/\"lfc-interval\": 3/g' \
+ | sed -e 's/\"persist\": false,/\"persist\": true,/g')
+ # Create new configuration file.
+ create_config "${LFC_CONFIG}"
+ # Instruct Kea to log to the specific file.
+ set_logger
+ # Start Kea.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for Kea to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for Kea to start.\n"
+ clean_exit 1
+ fi
+
+ # Check if it is still running. It could have terminated (e.g. as a result
+ # of configuration failure).
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one Kea process to be started. Found %d processes\
+ started.\n" "${_GET_PIDS_NUM}"
+ clean_exit 1
+ fi
+
+ # Check if Kea emits the log message indicating that LFC is started.
+ wait_for_message 10 "DHCPSRV_MEMFILE_LFC_EXECUTE" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: Server did not execute LFC.\n"
+ clean_exit 1
+ fi
+
+ # Give it a short time to run.
+ sleep 1
+
+ # Modify the interval.
+ LFC_CONFIG=$(printf '%s' "${LFC_CONFIG}" | sed -e 's/\"lfc-interval\": 3/\"lfc-interval\": 4/g')
+ # Create new configuration file.
+ create_config "${LFC_CONFIG}"
+
+ # Reconfigure the server with SIGHUP.
+ send_signal 1 ${bin}
+
+ # There should be two occurrences of the DHCP4_CONFIG_COMPLETE messages.
+ # Wait for it up to 10s.
+ wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2
+
+ # After receiving SIGHUP the server should get reconfigured and the
+ # reconfiguration should be noted in the log file. We should now
+ # have two configurations logged in the log file.
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: server hasn't been reconfigured.\n"
+ clean_exit 1
+ else
+ printf "Server successfully reconfigured.\n"
+ fi
+
+ # Make sure the server is still operational.
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
+ clean_exit 1
+ fi
+
+ # Wait for the LFC to run the second time.
+ wait_for_message 10 "DHCPSRV_MEMFILE_LFC_EXECUTE" 2
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: Server did not execute LFC.\n"
+ clean_exit 1
+ fi
+
+ # Send signal to Kea SIGTERM
+ send_signal 15 ${bin}
+
+ # Wait up to 10s for the server's graceful shutdown. The graceful shut down
+ # should be recorded in the log file with the appropriate message.
+ wait_for_message 10 "DHCP6_SHUTDOWN" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: Server did not record shutdown in the log.\n"
+ clean_exit 1
+ fi
+
+ # Make sure the server is down.
+ wait_for_server_down 5 ${bin}
+ assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \
+ "Expected wait_for_server_down return %d, returned %d"
+
+ # All ok. Shut down Kea and exit.
+ test_finish 0
+}
+
+server_pid_file_test "${CONFIG}" DHCP6_ALREADY_RUNNING
+dynamic_reconfiguration_test
+shutdown_test "dhcpv6.sigterm_test" 15
+shutdown_test "dhcpv6.sigint_test" 2
+version_test "dhcpv6.version"
+logger_vars_test "dhcpv6.variables"
+lfc_timer_test
+syntax_check_test "dhcpv6.syntax_check_success" "${CONFIG}" 0 -t
+syntax_check_test "dhcpv6.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1 -t
+syntax_check_test "dhcpv6.syntax_check_bad_values" "${CONFIG_BAD_VALUES}" 1 -t
+syntax_check_test "dhcpv6.syntax_check_hooks_load_fail" "${INVALID_CONFIG_HOOKS_LOAD}" 1 -T
+syntax_check_test "dhcpv6.syntax_check_hooks_callout_fail" "${INVALID_CONFIG_HOOKS_CALLOUT_FAIL}" 1 -T
+password_redact_test "dhcpv6.password_redact_test" "$(kea_dhcp_config 6)" 0
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
new file mode 100644
index 0000000..40ee751
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -0,0 +1,3904 @@
+// Copyright (C) 2011-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <config_backend/base_config_backend.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/utils.h>
+#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <util/buffer.h>
+#include <util/range_utilities.h>
+#include <util/encode/hex.h>
+
+#ifdef HAVE_MYSQL
+#include <mysql/testutils/mysql_schema.h>
+#endif
+
+#ifdef HAVE_PGSQL
+#include <pgsql/testutils/pgsql_schema.h>
+#endif
+
+#include <boost/pointer_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <dirent.h>
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::cb;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+const char* CONFIGS[] = {
+ // Configuration 0:
+ // - used in advertiseOptions
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\", "
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\""
+ " },"
+ " {"
+ " \"name\": \"subscriber-id\","
+ " \"data\": \"1234\","
+ " \"csv-format\": false"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 1:
+ // - a single subnet
+ // - MySQL host data source
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"hosts-database\": {"
+ " \"type\": \"mysql\","
+ " \"name\": \"keatest\","
+ " \"user\": \"keatest\","
+ " \"password\": \"keatest\""
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 2:
+ // - a single subnet
+ // - two global options (one enforced with always-send)
+ "{"
+ " \"interfaces-config\": { \"interfaces\": [ \"*\" ] }, "
+ " \"preferred-lifetime\": 3000, "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000, "
+ " \"subnet6\": [ {"
+ " \"id\": 1, "
+ " \"interface\": \"eth0\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\""
+ " } ], "
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\", "
+ " \"data\": \"2001:db8:1234:FFFF::1\""
+ " }, "
+ " {"
+ " \"name\": \"subscriber-id\", "
+ " \"data\": \"1234\", "
+ " \"always-send\": true"
+ " }"
+ " ]"
+ "}",
+
+ // Configuration 3:
+ // - a single subnet with one option cancelled with never-send.
+ // - two global options (one enforced with always-send)
+ "{"
+ " \"interfaces-config\": { \"interfaces\": [ \"*\" ] }, "
+ " \"preferred-lifetime\": 3000, "
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"valid-lifetime\": 4000, "
+ " \"subnet6\": [ {"
+ " \"id\": 1, "
+ " \"interface\": \"eth0\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"subscriber-id\", "
+ " \"never-send\": true"
+ " } ]"
+ " } ], "
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\", "
+ " \"data\": \"2001:db8:1234:FFFF::1\""
+ " }, "
+ " {"
+ " \"name\": \"subscriber-id\", "
+ " \"data\": \"1234\", "
+ " \"always-send\": true"
+ " }"
+ " ]"
+ "}",
+
+ // Configuration 4:
+ // - one subnet with one address pool and one prefix pool
+ // - user-contexts defined in subnet and each pool
+ "{"
+ " \"subnet6\": [ {"
+ " \"id\": 1, "
+ " \"pools\": [ {"
+ " \"pool\": \"2001:db8:1::/64\","
+ " \"user-context\": { \"value\": 42 }"
+ " } ], "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:abcd::\","
+ " \"prefix-len\": 48,"
+ " \"delegated-len\": 64,"
+ " \"user-context\": { \"type\": \"prefixes\" }"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\","
+ " \"user-context\": { \"secure\": false }"
+ " } ] "
+ "}"
+};
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Remove TLS parameters from configuration element.
+void removeTlsParameters(ConstElementPtr elem) {
+ if (elem) {
+ ElementPtr mutable_elem = boost::const_pointer_cast<Element>(elem);
+ std::vector<std::string> tls_parameters= {
+ "trust-anchor",
+ "cert-file",
+ "key-file",
+ "cipher-list"
+ };
+ for (auto const& parameter : tls_parameters) {
+ mutable_elem->remove(parameter);
+ }
+ }
+}
+
+void
+Dhcpv6SrvTest::loadConfigFile(const string& path) {
+ CfgMgr::instance().clear();
+
+ LibDHCP::clearRuntimeOptionDefs();
+
+ IfaceMgrTestConfig test_config(true);
+
+ // Do not use DHCP6_SERVER_PORT here as 0 means don't open sockets.
+ NakedDhcpv6Srv srv(0);
+ EXPECT_EQ(0, srv.server_port_);
+
+ ConfigBackendDHCPv6Mgr::instance().registerBackendFactory("mysql",
+ [](const db::DatabaseConnection::ParameterMap&) -> ConfigBackendDHCPv6Ptr {
+ return (ConfigBackendDHCPv6Ptr());
+ });
+
+ ConfigBackendDHCPv6Mgr::instance().registerBackendFactory("postgresql",
+ [](const db::DatabaseConnection::ParameterMap&) -> ConfigBackendDHCPv6Ptr {
+ return (ConfigBackendDHCPv6Ptr());
+ });
+
+ // TimerMgr uses IO service to run asynchronous timers.
+ TimerMgr::instance()->setIOService(srv.getIOService());
+
+ // CommandMgr uses IO service to run asynchronous socket operations.
+ CommandMgr::instance().setIOService(srv.getIOService());
+
+ // LeaseMgr uses IO service to run asynchronous timers.
+ LeaseMgr::setIOService(srv.getIOService());
+
+ // HostMgr uses IO service to run asynchronous timers.
+ HostMgr::setIOService(srv.getIOService());
+
+ Parser6Context parser;
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parser.parseFile(path, Parser6Context::PARSER_DHCP6));
+ ASSERT_TRUE(json);
+
+ // Check the logic next.
+ ConstElementPtr dhcp6 = json->get("Dhcp6");
+ ASSERT_TRUE(dhcp6);
+ ElementPtr mutable_config = boost::const_pointer_cast<Element>(dhcp6);
+ mutable_config->set(string("hooks-libraries"), Element::createList());
+ // Remove TLS parameters
+ ConstElementPtr hosts = dhcp6->get("hosts-database");
+ removeTlsParameters(hosts);
+ hosts = dhcp6->get("hosts-databases");
+ if (hosts) {
+ for (auto& host : hosts->listValue()) {
+ removeTlsParameters(host);
+ }
+ }
+ ASSERT_NO_THROW(Dhcpv6SrvTest::configure(dhcp6->str(), true, true, true, true));
+
+ LeaseMgrFactory::destroy();
+ HostMgr::create();
+
+ TimerMgr::instance()->unregisterTimers();
+
+ // Close the command socket (if it exists).
+ CommandMgr::instance().closeCommandSocket();
+
+ // Reset CommandMgr IO service.
+ CommandMgr::instance().setIOService(IOServicePtr());
+
+ // Reset LeaseMgr IO service.
+ LeaseMgr::setIOService(IOServicePtr());
+
+ // Reset HostMgr IO service.
+ HostMgr::setIOService(IOServicePtr());
+}
+
+/// @brief Class which handles initialization of database
+/// backend for testing configurations.
+class DBInitializer {
+ public:
+ /// @brief Constructor.
+ ///
+ /// Created database schema.
+ DBInitializer() {
+#if defined (HAVE_MYSQL)
+ db::test::createMySQLSchema();
+#endif
+#if defined (HAVE_PGSQL)
+ db::test::createPgSQLSchema();
+#endif
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Destroys database schema.
+ ~DBInitializer() {
+#if defined (HAVE_MYSQL)
+ db::test::destroyMySQLSchema();
+#endif
+#if defined (HAVE_PGSQL)
+ db::test::destroyPgSQLSchema();
+#endif
+ }
+};
+
+void
+Dhcpv6SrvTest::checkConfigFiles() {
+ DBInitializer dbi;
+ IfaceMgrTestConfig test_config(true);
+ string path = CFG_EXAMPLES;
+ vector<string> examples = {
+ "advanced.json",
+#if defined (HAVE_MYSQL) && defined (HAVE_PGSQL)
+ "all-keys.json",
+ "all-keys-netconf.json",
+ "all-options.json",
+#endif
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+#if defined (HAVE_MYSQL)
+ "config-backend.json",
+#endif
+ "dhcpv4-over-dhcpv6.json",
+ "dnr.json",
+ "duid.json",
+ "global-reservations.json",
+ "ha-hot-standby-server1-with-tls.json",
+ "ha-hot-standby-server2.json",
+ "hooks.json",
+ "iPXE.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+#if defined (HAVE_MYSQL)
+ "mysql-reservations.json",
+#endif
+#if defined (HAVE_PGSQL)
+ "pgsql-reservations.json",
+#endif
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "simple.json",
+ "softwire46.json",
+ "stateless.json",
+ "tee-times.json",
+ "with-ddns.json",
+ };
+ vector<string> files;
+ for (string example : examples) {
+ string file = path + "/" + example;
+ files.push_back(file);
+ }
+ for (const auto& file: files) {
+ string label("Checking configuration from file: ");
+ label += file;
+ SCOPED_TRACE(label);
+ loadConfigFile(file);
+ }
+}
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+namespace {
+
+// This test verifies that incoming SOLICIT can be handled properly when
+// there are no subnets configured.
+//
+// This test sends a SOLICIT and the expected response
+// is an ADVERTISE with STATUS_NoAddrsAvail and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check that we get the right NAK
+ checkNakResponse(reply, DHCPV6_ADVERTISE, 1234, STATUS_NoAddrsAvail,
+ 0, 0);
+}
+
+// This test verifies that incoming REQUEST can be handled properly when
+// there are no subnets configured.
+//
+// This test sends a REQUEST and the expected response
+// is an REPLY with STATUS_NoAddrsAvail and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a REQUEST
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
+
+ // with a hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ req->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+
+ // server-id is mandatory in REQUEST
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRequest(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoAddrsAvail,
+ 0, 0);
+}
+
+// This test verifies that incoming RENEW can be handled properly, even when
+// no subnets are configured.
+//
+// This test sends a RENEW and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding,
+ 0, 0);
+}
+
+// This test verifies that incoming RELEASE can be handled properly, even when
+// no subnets are configured.
+//
+// This test sends a RELEASE and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, ReleaseNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding, 0, 0);
+}
+
+// Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode
+// without open sockets and with sockets opened on a high port (to not require
+// root privileges).
+TEST_F(Dhcpv6SrvTest, basic) {
+ boost::scoped_ptr<Dhcpv6Srv> srv;
+
+ ASSERT_NO_THROW( {
+ // Skip opening any sockets
+ srv.reset(new NakedDhcpv6Srv(0));
+ });
+ srv.reset();
+ ASSERT_NO_THROW({
+ // open an unprivileged port
+ srv.reset(new NakedDhcpv6Srv(DHCP6_SERVER_PORT + 10000));
+ });
+}
+
+// Test checks that DUID is generated properly
+TEST_F(Dhcpv6SrvTest, DUID) {
+
+ boost::scoped_ptr<Dhcpv6Srv> srv;
+ ASSERT_NO_THROW( {
+ srv.reset(new NakedDhcpv6Srv(0));
+ });
+
+ OptionPtr srvid = srv->getServerID();
+ ASSERT_TRUE(srvid);
+
+ EXPECT_EQ(D6O_SERVERID, srvid->getType());
+
+ OutputBuffer buf(32);
+ srvid->pack(buf);
+
+ // length of the actual DUID
+ size_t len = buf.getLength() - srvid->getHeaderLen();
+
+ InputBuffer data(buf.getData(), buf.getLength());
+
+ // ignore first four bytes (standard DHCPv6 header)
+ data.readUint32();
+
+ uint16_t duid_type = data.readUint16();
+ cout << "Duid-type=" << duid_type << endl;
+ switch(duid_type) {
+ case DUID::DUID_LLT: {
+ // DUID must contain at least 6 bytes long MAC
+ // + 8 bytes of fixed header
+ EXPECT_GE(len, 14);
+
+ uint16_t hw_type = data.readUint16();
+ // there's no real way to find out "correct"
+ // hardware type
+ EXPECT_GT(hw_type, 0);
+
+ // check that timer is counted since 1.1.2000,
+ // not from 1.1.1970.
+ uint32_t seconds = data.readUint32();
+ EXPECT_LE(seconds, DUID_TIME_EPOCH);
+ // this test will start failing after 2030.
+ // Hopefully we will be at BIND12 by then.
+
+ // MAC must not be zeros
+ vector<uint8_t> mac(len-8);
+ vector<uint8_t> zeros(len-8, 0);
+ data.readVector(mac, len-8);
+ EXPECT_TRUE(mac != zeros);
+ break;
+ }
+ case DUID::DUID_EN: {
+ // there's not much we can check. Just simple
+ // check if it is not all zeros
+ vector<uint8_t> content(len-2);
+ data.readVector(content, len-2);
+ EXPECT_FALSE(isRangeZero(content.begin(), content.end()));
+ break;
+ }
+ case DUID::DUID_LL: {
+ // not supported yet
+ cout << "Test not implemented for DUID-LL." << endl;
+
+ // No failure here. There's really no way for test LL DUID. It doesn't
+ // even make sense to check if that Link Layer is actually present on
+ // a physical interface. RFC 8415 says a server should write its DUID
+ // and keep it despite hardware changes.
+ break;
+ }
+ case DUID::DUID_UUID: // not supported yet
+ default:
+ ADD_FAILURE() << "Not supported duid type=" << duid_type << endl;
+ break;
+ }
+}
+
+// This test checks if Option Request Option (ORO) is parsed correctly
+// and the requested options are actually assigned.
+TEST_F(Dhcpv6SrvTest, advertiseOptions) {
+
+ IfaceMgrTestConfig test_config(true);
+
+ ConstElementPtr x;
+ ASSERT_NO_THROW(configure(CONFIGS[0]));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_.processSolicit(ctx);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // We have not requested any options so they should not
+ // be included in the response.
+ ASSERT_FALSE(adv->getOption(D6O_SUBSCRIBER_ID));
+ ASSERT_FALSE(adv->getOption(D6O_NAME_SERVERS));
+
+ // Let's now request some options. We expect that the server
+ // will include them in its response.
+ boost::shared_ptr<OptionIntArray<uint16_t> >
+ option_oro(new OptionIntArray<uint16_t>(Option::V6, D6O_ORO));
+ // Create vector with two option codes.
+ std::vector<uint16_t> codes(2);
+ codes[0] = D6O_SUBSCRIBER_ID;
+ codes[1] = D6O_NAME_SERVERS;
+ // Pass this code to option.
+ option_oro->setValues(codes);
+ // Append ORO to SOLICIT message.
+ sol->addOption(option_oro);
+
+ // Need to process SOLICIT again after requesting new option.
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv_.earlyGHRLookup(sol, ctx2);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx2, drop);
+ ASSERT_FALSE(drop);
+ adv = srv_.processSolicit(ctx2);
+ ASSERT_TRUE(adv);
+
+ OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(tmp);
+
+ boost::shared_ptr<Option6AddrLst> reply_nameservers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(tmp);
+ ASSERT_TRUE(reply_nameservers);
+
+ Option6AddrLst::AddressContainer addrs = reply_nameservers->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_TRUE(addrs[0] == IOAddress("2001:db8:1234:FFFF::1"));
+ EXPECT_TRUE(addrs[1] == IOAddress("2001:db8:1234:FFFF::2"));
+
+ // There is a dummy option with code 1000 we requested from a server.
+ // Expect that this option is in server's response.
+ tmp = adv->getOption(D6O_SUBSCRIBER_ID);
+ ASSERT_TRUE(tmp);
+
+ // Check that the option contains valid data (from configuration).
+ std::vector<uint8_t> data = tmp->getData();
+ ASSERT_EQ(2, data.size());
+
+ const uint8_t foo_expected[] = {
+ 0x12, 0x34
+ };
+ EXPECT_EQ(0, memcmp(&data[0], foo_expected, 2));
+
+ // more checks to be implemented
+}
+
+// There are no dedicated tests for Dhcpv6Srv::handleIA_NA and Dhcpv6Srv::assignLeases
+// as they are indirectly tested in Solicit and Request tests.
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT without any hint in IA_NA.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, without any addresses)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), Lease::TYPE_NA);
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has a prefix and that prefix
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT without any hint in IA_PD.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, without any addresses)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAPREFIX
+TEST_F(Dhcpv6SrvTest, pdSolicitBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_PD, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAPrefix> prefix = checkIA_PD(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(prefix);
+
+ // Check that the assigned prefix is indeed from the configured pool
+ checkIAAddr(prefix, prefix->getAddress(), Lease::TYPE_PD);
+ EXPECT_EQ(pd_pool_->getLength(), prefix->getLength());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that ADVERTISE returns default lifetimes when
+// the client does not add an IAADDR sub option.
+TEST_F(Dhcpv6SrvTest, defaultLifetimeSolicit) {
+ NakedDhcpv6Srv srv(0);
+
+ subnet_->setPreferred(Triplet<uint32_t>(2000, 3000, 4000));
+ subnet_->setValid(Triplet<uint32_t>(3000, 4000, 5000));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Add no IAADDR sub option.
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), Lease::TYPE_NA,
+ subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that ADVERTISE returns default lifetimes when
+// the client adds an IAPREFIX sub option with zero lifetime hints.
+TEST_F(Dhcpv6SrvTest, hintZeroLifetimeSolicit) {
+ NakedDhcpv6Srv srv(0);
+
+ subnet_->setPreferred(Triplet<uint32_t>(2000, 3000, 4000));
+ subnet_->setValid(Triplet<uint32_t>(3000, 4000, 5000));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ OptionPtr iapd = generateIA(D6O_IA_PD, 234, 1500, 3000);
+ sol->addOption(iapd);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Add an IAPREFIX sub option with zero preferred and valid lifetimes.
+ OptionPtr subopt(new Option6IAPrefix(D6O_IAPREFIX,
+ IOAddress("::"),
+ 0, 0, 0));
+ iapd->addOption(subopt);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_PD was returned and that there's an address included
+ boost::shared_ptr<Option6IAPrefix> prefix = checkIA_PD(reply, 234,
+ subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(prefix);
+
+ // Check that the assigned prefix is indeed from the configured pool
+ checkIAAddr(prefix, prefix->getAddress(), Lease::TYPE_PD,
+ subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that ADVERTISE returns specified lifetimes when
+// the client adds an IAADDR sub option with in-bound lifetime hints.
+TEST_F(Dhcpv6SrvTest, hintLifetimeSolicit) {
+ NakedDhcpv6Srv srv(0);
+
+ subnet_->setPreferred(Triplet<uint32_t>(2000, 3000, 4000));
+ subnet_->setValid(Triplet<uint32_t>(3000, 4000, 5000));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ OptionPtr iana = generateIA(D6O_IA_NA, 234, 1500, 3000);
+ sol->addOption(iana);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Add an IAADDR sub option.
+ uint32_t hint_pref = 3001;
+ uint32_t hint_valid = 3999;
+ OptionPtr subopt(new Option6IAAddr(D6O_IAADDR, IOAddress("::"),
+ hint_pref, hint_valid));
+ iana->addOption(subopt);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), Lease::TYPE_NA,
+ hint_pref, hint_valid);
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that ADVERTISE returns min lifetimes when
+// the client adds an IAPREFIX sub option with too small lifetime hints.
+TEST_F(Dhcpv6SrvTest, minLifetimeSolicit) {
+ NakedDhcpv6Srv srv(0);
+
+ subnet_->setPreferred(Triplet<uint32_t>(2000, 3000, 4000));
+ subnet_->setValid(Triplet<uint32_t>(3000, 4000, 5000));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ OptionPtr iapd = generateIA(D6O_IA_PD, 234, 1500, 3000);
+ sol->addOption(iapd);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Add an IAPREFIX sub option with too small hints so min values will
+ // be returned in the ADVERTISE.
+ OptionPtr subopt(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("::"), 0,
+ 1000, 2000));
+ iapd->addOption(subopt);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_PD was returned and that there's an address included
+ boost::shared_ptr<Option6IAPrefix> prefix = checkIA_PD(reply, 234,
+ subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(prefix);
+
+ // Check that the assigned prefix is indeed from the configured pool
+ checkIAAddr(prefix, prefix->getAddress(), Lease::TYPE_PD,
+ subnet_->getPreferred().getMin(),
+ subnet_->getValid().getMin());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that ADVERTISE returns max lifetimes when
+// the client adds an IAADDR sub option with too large lifetime hints.
+TEST_F(Dhcpv6SrvTest, maxLifetimeSolicit) {
+ NakedDhcpv6Srv srv(0);
+
+ subnet_->setPreferred(Triplet<uint32_t>(2000, 3000, 4000));
+ subnet_->setValid(Triplet<uint32_t>(3000, 4000, 5000));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ OptionPtr iana = generateIA(D6O_IA_NA, 234, 1500, 3000);
+ sol->addOption(iana);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Add an IAADDR sub option with too large hints so max values will
+ // be returned in the ADVERTISE.
+ OptionPtr subopt(new Option6IAAddr(D6O_IAADDR, IOAddress("::"),
+ 5000, 6000));
+ iana->addOption(subopt);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), Lease::TYPE_NA,
+ subnet_->getPreferred().getMax(),
+ subnet_->getValid().getMax());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT with IA_NA that contains a valid hint.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that belongs to the
+// configured pool, i.e. is valid as hint)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitHint) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
+
+ // with a valid hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ sol->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // check that we've got the address we requested
+ checkIAAddr(addr, hint, Lease::TYPE_NA);
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT with IA_NA that contains an invalid hint.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that does not
+// belong to the configured pool, i.e. is valid as hint)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
+ IOAddress hint("2001:db8:1::cafe:babe");
+ ASSERT_FALSE(subnet_->inPool(Lease::TYPE_NA, hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ sol->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), Lease::TYPE_NA);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr->getAddress()));
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+/// @todo: Add a test that client sends hint that is in pool, but currently
+/// being used by a different client.
+
+// This test checks that the server is offering different addresses to different
+// clients in ADVERTISEs. Please note that ADVERTISE is not a guarantee that such
+// an address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same advertise as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. ADVERTISE is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv6SrvTest, ManySolicits) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
+ Pkt6Ptr sol3 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 3456));
+
+ sol1->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol2->setRemoteAddr(IOAddress("fe80::1223"));
+ sol3->setRemoteAddr(IOAddress("fe80::3467"));
+
+ sol1->setIface("eth0");
+ sol1->setIndex(ETH0_INDEX);
+ sol2->setIface("eth0");
+ sol2->setIndex(ETH0_INDEX);
+ sol3->setIface("eth0");
+ sol3->setIndex(ETH0_INDEX);
+
+ sol1->addOption(generateIA(D6O_IA_NA, 1, 1500, 3000));
+ sol2->addOption(generateIA(D6O_IA_NA, 2, 1500, 3000));
+ sol3->addOption(generateIA(D6O_IA_NA, 3, 1500, 3000));
+
+ // different client-id sizes
+ OptionPtr clientid1 = generateClientId(12);
+ OptionPtr clientid2 = generateClientId(14);
+ OptionPtr clientid3 = generateClientId(16);
+
+ sol1->addOption(clientid1);
+ sol2->addOption(clientid2);
+ sol3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx1;
+ bool drop = !srv.earlyGHRLookup(sol1, ctx1);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol1, ctx1, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply1 = srv.processSolicit(ctx1);
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv.earlyGHRLookup(sol2, ctx2);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol2, ctx2, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply2 = srv.processSolicit(ctx2);
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv.earlyGHRLookup(sol3, ctx3);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol3, ctx3, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply3 = srv.processSolicit(ctx3);
+
+ // check if we get response at all
+ checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
+ checkResponse(reply2, DHCPV6_ADVERTISE, 2345);
+ checkResponse(reply3, DHCPV6_ADVERTISE, 3456);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr1 = checkIA_NA(reply1, 1, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr2 = checkIA_NA(reply2, 2, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr1);
+ ASSERT_TRUE(addr2);
+ ASSERT_TRUE(addr3);
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr1, addr1->getAddress(), Lease::TYPE_NA);
+ checkIAAddr(addr2, addr2->getAddress(), Lease::TYPE_NA);
+ checkIAAddr(addr3, addr3->getAddress(), Lease::TYPE_NA);
+
+ // check DUIDs
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
+ checkClientId(reply1, clientid1);
+ checkClientId(reply2, clientid2);
+ checkClientId(reply3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1->getAddress(), addr2->getAddress());
+ EXPECT_NE(addr2->getAddress(), addr3->getAddress());
+ EXPECT_NE(addr3->getAddress(), addr1->getAddress());
+ cout << "Offered address to client1=" << addr1->getAddress() << endl;
+ cout << "Offered address to client2=" << addr2->getAddress() << endl;
+ cout << "Offered address to client3=" << addr3->getAddress() << endl;
+}
+
+// This test verifies that incoming SOLICIT can't reuse an existing lease
+// and simply return it, i.e. fake allocation ignores the cache feature.
+TEST_F(Dhcpv6SrvTest, SolicitCache) {
+ NakedDhcpv6Srv srv(0);
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+ const uint32_t pref = subnet_->getPreferred();
+ const uint32_t valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t timestamp = time(NULL) - delta;
+
+ // Generate client-id also duid_.
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool.
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ Lease6Ptr used(new Lease6(Lease::TYPE_NA, addr, duid_, iaid, pref, valid,
+ subnet_->getID()));
+ used->cltt_ = timestamp;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database.
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 100 seconds ago.
+ EXPECT_EQ(l->preferred_lft_, pref);
+ EXPECT_EQ(l->valid_lft_, valid);
+ EXPECT_EQ(l->cltt_, timestamp);
+
+ // Let's create a SOLICIT.
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> iaaddr =
+ checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(iaaddr);
+
+ // Check the address.
+ EXPECT_EQ(addr, iaaddr->getAddress());
+ EXPECT_EQ(pref, iaaddr->getPreferred());
+ EXPECT_EQ(valid, iaaddr->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can't reuse an existing lease
+// and simply return it, i.e. fake allocation ignores the cache feature.
+// Prefix variant.
+TEST_F(Dhcpv6SrvTest, pdSolicitCache) {
+ NakedDhcpv6Srv srv(0);
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress prefix("2001:db8:1:2::");
+ const uint8_t prefixlen = pd_pool_->getLength();
+ const uint32_t iaid = 234;
+ const uint32_t pref = subnet_->getPreferred();
+ const uint32_t valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t timestamp = time(NULL) - delta;
+
+ // Generate client-id also duid_.
+ OptionPtr clientid = generateClientId();
+
+ // Check that the prefix we are about to use is indeed in pool.
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+ Lease6Ptr used(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid, pref, valid,
+ subnet_->getID(), HWAddrPtr(), prefixlen));
+ used->cltt_ = timestamp;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database.
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 100 seconds ago.
+ EXPECT_EQ(l->preferred_lft_, pref);
+ EXPECT_EQ(l->valid_lft_, valid);
+ EXPECT_EQ(l->cltt_, timestamp);
+
+ // Let's create a SOLICIT.
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_PD, 234, 1500, 3000));
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_PD was returned and that there's a prefix included
+ boost::shared_ptr<Option6IAPrefix> iapref =
+ checkIA_PD(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(iapref);
+
+ // Check the prefix.
+ EXPECT_EQ(prefix, iapref->getAddress());
+ EXPECT_EQ(prefixlen, iapref->getLength());
+ EXPECT_EQ(pref, iapref->getPreferred());
+ EXPECT_EQ(valid, iapref->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a REQUEST with IA_NA that contains a valid hint.
+//
+// constructed very simple REQUEST message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that belongs to the
+// configured pool, i.e. is valid as hint)
+//
+// expected returned REPLY message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, RequestBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a REQUEST
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
+
+ // with a valid hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ req->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+
+ // server-id is mandatory in REQUEST
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRequest(req);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234,
+ subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // check that we've got the address we requested
+ checkIAAddr(addr, hint, Lease::TYPE_NA);
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ // check that the lease is really in the database
+ Lease6Ptr l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
+ EXPECT_TRUE(l);
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr->getAddress());
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that a
+// REPLY is generated, that the response has a prefix and that prefix
+// really belongs to the configured pool.
+//
+// This test sends a REQUEST with IA_PD that contains a valid hint.
+//
+// constructed very simple REQUEST message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that belongs to the
+// configured pool, i.e. is valid as hint)
+//
+// expected returned REPLY message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAPREFIX
+TEST_F(Dhcpv6SrvTest, pdRequestBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a REQUEST
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_PD, 234, 1500, 3000);
+
+ // with a valid hint
+ IOAddress hint("2001:db8:1:2:f::");
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, hint));
+ OptionPtr hint_opt(new Option6IAPrefix(D6O_IAPREFIX, hint, 64, 300, 500));
+ ia->addOption(hint_opt);
+ req->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+
+ // server-id is mandatory in REQUEST
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRequest(req);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_PD);
+ ASSERT_TRUE(tmp);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAPrefix> prf = checkIA_PD(reply, 234,
+ subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(prf);
+
+ // check that we've got the address we requested
+ checkIAAddr(prf, hint, Lease::TYPE_PD);
+ EXPECT_EQ(pd_pool_->getLength(), prf->getLength());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ // check that the lease is really in the database
+ Lease6Ptr l = checkPdLease(duid_, reply->getOption(D6O_IA_PD), prf);
+ EXPECT_TRUE(l);
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
+ prf->getAddress());
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+// This test checks that the server is offering different addresses to different
+// clients in REQUEST. Please note that ADVERTISE is not a guarantee that such
+// and address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same advertise as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. ADVERTISE is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv6SrvTest, ManyRequests) {
+ NakedDhcpv6Srv srv(0);
+
+ ASSERT_TRUE(subnet_);
+
+ Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
+ Pkt6Ptr req3 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 3456));
+
+ req1->setRemoteAddr(IOAddress("fe80::abcd"));
+ req2->setRemoteAddr(IOAddress("fe80::1223"));
+ req3->setRemoteAddr(IOAddress("fe80::3467"));
+
+ req1->setIface("eth0");
+ req1->setIndex(ETH0_INDEX);
+ req2->setIface("eth0");
+ req2->setIndex(ETH0_INDEX);
+ req3->setIface("eth0");
+ req3->setIndex(ETH0_INDEX);
+
+ req1->addOption(generateIA(D6O_IA_NA, 1, 1500, 3000));
+ req2->addOption(generateIA(D6O_IA_NA, 2, 1500, 3000));
+ req3->addOption(generateIA(D6O_IA_NA, 3, 1500, 3000));
+
+ // different client-id sizes
+ OptionPtr clientid1 = generateClientId(12);
+ OptionPtr clientid2 = generateClientId(14);
+ OptionPtr clientid3 = generateClientId(16);
+
+ req1->addOption(clientid1);
+ req2->addOption(clientid2);
+ req3->addOption(clientid3);
+
+ // server-id is mandatory in REQUEST
+ req1->addOption(srv.getServerID());
+ req2->addOption(srv.getServerID());
+ req3->addOption(srv.getServerID());
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply1 = srv.processRequest(req1);
+ Pkt6Ptr reply2 = srv.processRequest(req2);
+ Pkt6Ptr reply3 = srv.processRequest(req3);
+
+ // check if we get response at all
+ checkResponse(reply1, DHCPV6_REPLY, 1234);
+ checkResponse(reply2, DHCPV6_REPLY, 2345);
+ checkResponse(reply3, DHCPV6_REPLY, 3456);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr1 = checkIA_NA(reply1, 1, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr2 = checkIA_NA(reply2, 2, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
+ subnet_->getT2());
+
+ ASSERT_TRUE(addr1);
+ ASSERT_TRUE(addr2);
+ ASSERT_TRUE(addr3);
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr1, addr1->getAddress(), Lease::TYPE_NA);
+ checkIAAddr(addr2, addr2->getAddress(), Lease::TYPE_NA);
+ checkIAAddr(addr3, addr3->getAddress(), Lease::TYPE_NA);
+
+ // check DUIDs
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
+ checkClientId(reply1, clientid1);
+ checkClientId(reply2, clientid2);
+ checkClientId(reply3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1->getAddress(), addr2->getAddress());
+ EXPECT_NE(addr2->getAddress(), addr3->getAddress());
+ EXPECT_NE(addr3->getAddress(), addr1->getAddress());
+ cout << "Assigned address to client1=" << addr1->getAddress() << endl;
+ cout << "Assigned address to client2=" << addr2->getAddress() << endl;
+ cout << "Assigned address to client3=" << addr3->getAddress() << endl;
+}
+
+// This test verifies that incoming REQUEST can reuse an existing lease.
+TEST_F(Dhcpv6SrvTest, RequestCache) {
+ NakedDhcpv6Srv srv(0);
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+ const uint32_t pref = subnet_->getPreferred();
+ const uint32_t valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t timestamp = time(NULL) - delta;
+
+ // Generate client-id also duid_.
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool.
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ Lease6Ptr used(new Lease6(Lease::TYPE_NA, addr, duid_, iaid, pref, valid,
+ subnet_->getID()));
+ used->cltt_ = timestamp;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database.
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 100 seconds ago.
+ EXPECT_EQ(l->preferred_lft_, pref);
+ EXPECT_EQ(l->valid_lft_, valid);
+ EXPECT_EQ(l->cltt_, timestamp);
+
+ // Let's create a REQUEST.
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ req->addOption(createIA(Lease::TYPE_NA, addr, 128, iaid));
+ req->addOption(clientid);
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(req, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(req, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processRequest(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> iaaddr =
+ checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(iaaddr);
+
+ // Check the address.
+ EXPECT_EQ(addr, iaaddr->getAddress());
+ EXPECT_EQ(pref - delta, iaaddr->getPreferred());
+ EXPECT_EQ(valid - delta, iaaddr->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming REQUEST can reuse an existing lease.
+// Prefix variant.
+TEST_F(Dhcpv6SrvTest, pdRequestCache) {
+ NakedDhcpv6Srv srv(0);
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress prefix("2001:db8:1:2::");
+ const uint8_t prefixlen = pd_pool_->getLength();
+ const uint32_t iaid = 234;
+ const uint32_t pref = subnet_->getPreferred();
+ const uint32_t valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t timestamp = time(NULL) - delta;
+
+ // Generate client-id also duid_.
+ OptionPtr clientid = generateClientId();
+
+ // Check that the prefix we are about to use is indeed in pool.
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+ Lease6Ptr used(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid, pref, valid,
+ subnet_->getID(), HWAddrPtr(), prefixlen));
+ used->cltt_ = timestamp;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database.
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 100 seconds ago.
+ EXPECT_EQ(l->preferred_lft_, pref);
+ EXPECT_EQ(l->valid_lft_, valid);
+ EXPECT_EQ(l->cltt_, timestamp);
+
+ // Let's create a REQUEST.
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ req->addOption(createIA(Lease::TYPE_PD, prefix, prefixlen, iaid));
+ req->addOption(clientid);
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(req, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(req, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processRequest(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ // check that IA_PD was returned and that there's a prefix included
+ boost::shared_ptr<Option6IAPrefix> iapref =
+ checkIA_PD(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(iapref);
+
+ // Check the prefix.
+ EXPECT_EQ(prefix, iapref->getAddress());
+ EXPECT_EQ(prefixlen, iapref->getLength());
+ EXPECT_EQ(pref - delta, iapref->getPreferred());
+ EXPECT_EQ(valid - delta, iapref->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_NA that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv6SrvTest, renewBasic) {
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128);
+}
+
+// This test verifies that incoming (positive) PD RENEW can be handled properly,
+// that a REPLY is generated, that the response has a prefix and that prefix
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_PD that includes IAPREFIX
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv6SrvTest, pdRenewBasic) {
+ testRenewBasic(Lease::TYPE_PD, "2001:db8:1:2::",
+ "2001:db8:1:2::", pd_pool_->getLength());
+}
+
+// This test verifies that incoming (invalid) RENEW with an address
+// can be handled properly. This has changed with #3565. The server
+// is now able to allocate a lease in Renew if it's available.
+// Previous testRenewReject is now split into 3 tests.
+//
+// This test checks the first scenario: There is no lease at all.
+// The server will try to assign it. Since it is not used by anyone else,
+// the server will assign it. This is convenient for various types
+// of recoveries, e.g. when the server lost its database.
+TEST_F(Dhcpv6SrvTest, RenewUnknown) {
+ // False means that the lease should not be created before renewal attempt
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::abc", "2001:db8:1:1::abc",
+ 128, false);
+}
+
+// This test checks that a client that renews existing lease, but uses
+// a wrong IAID, will be processed correctly. As there is no lease for
+// this (duid, type, iaid) tuple, this is treated as a new IA, regardless
+// if the client inserted an address that is used in a different IA.
+// After #3565 was implemented, the server will attempt to assign a lease.
+// The one that client requested is already used with different IAID, so
+// it will just pick a different lease. This is the second out of three
+// scenarios tests by old RenewReject test.
+TEST_F(Dhcpv6SrvTest, RenewWrongIAID) {
+ testRenewWrongIAID(Lease::TYPE_NA, IOAddress("2001:db8:1:1::abc"));
+}
+
+// This test checks whether client A can renew an address that is currently
+// leased by client B. The server should detect that the lease belong to
+// someone else and assign a different lease. This is the third out of three
+// scenarios tests by old RenewReject test.
+TEST_F(Dhcpv6SrvTest, RenewSomeoneElsesLease) {
+ testRenewSomeoneElsesLease(Lease::TYPE_NA, IOAddress("2001:db8::1"));
+}
+
+// This test verifies that a renewal returns default lifetimes when
+// the client adds an IAPREFIX sub option with zero lifetime hints.
+TEST_F(Dhcpv6SrvTest, defaultLifetimeRenew) {
+ // Defaults are 3000 and 4000.
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128,
+ true, false, 0, 0, 3000, 4000);
+}
+
+// This test verifies that a renewal returns specified lifetimes when
+// the client adds an IAPREFIX sub option with in-bound lifetime hints.
+TEST_F(Dhcpv6SrvTest, hintLifetimeRenew) {
+ testRenewBasic(Lease::TYPE_PD, "2001:db8:1:2::",
+ "2001:db8:1:2::", pd_pool_->getLength(),
+ true, false, 2999, 4001, 2999, 4001);
+}
+
+// This test verifies that a renewal returns min lifetimes when
+// the client adds an IAADDR sub option with too small lifetime hints.
+TEST_F(Dhcpv6SrvTest, minLifetimeRenew) {
+ // Min values are 2000 and 3000.
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128,
+ true, false, 1000, 2000, 2000, 3000);
+}
+
+// This test verifies that a renewal returns max ifetimes when
+// the client adds an IAPREFIX sub option with too large lifetime hints.
+TEST_F(Dhcpv6SrvTest, maxLifetimeRenew) {
+ // Max values are 4000 and 5000.
+ testRenewBasic(Lease::TYPE_PD, "2001:db8:1:2::",
+ "2001:db8:1:2::", pd_pool_->getLength(),
+ true, false, 5000, 6000, 4000, 5000);
+}
+
+// This test is a mixed of FqdnDhcpv6SrvTest.processRequestReuseExpiredLease
+// and testRenewBasic. The idea is to force the reuse of an expired lease
+// so the allocation engine reuseExpiredLease routine is called instead
+// of the two other routines computing lease lifetimes createLease6
+// and extendLease6.
+TEST_F(Dhcpv6SrvTest, reuseExpiredBasic) {
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128, true, true);
+}
+
+// This test verifies that an expired reuse returns default lifetimes when
+// the client adds an IAADDR sub option with zero lifetime hints.
+TEST_F(Dhcpv6SrvTest, defaultLifetimeReuseExpired) {
+ // Defaults are 3000 and 4000.
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128,
+ true, true, 0, 0, 3000, 4000);
+}
+
+// This test verifies that an expired reuse returns specified lifetimes when
+// the client adds an IAADDR sub option with in-bound lifetime hints.
+TEST_F(Dhcpv6SrvTest, hintLifetimeReuseExpired) {
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128,
+ true, true, 2999, 4001, 2999, 4001);
+}
+
+// This test verifies that an expired reuse returns min lifetimes when
+// the client adds an IAADDR sub option with too small lifetime hints.
+TEST_F(Dhcpv6SrvTest, minLifetimeReuseExpired) {
+ // Min values are 2000 and 3000.
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128,
+ true, true, 1000, 2000, 2000, 3000);
+}
+
+// This test verifies that an expired reuse returns max lifetimes when
+// the client adds an IAADDR sub option with too large lifetime hints.
+TEST_F(Dhcpv6SrvTest, maxLifetimeReuseExpired) {
+ // Max values are 4000 and 5000.
+ testRenewBasic(Lease::TYPE_NA, "2001:db8:1:1::cafe:babe",
+ "2001:db8:1:1::cafe:babe", 128,
+ true, true, 5000, 6000, 4000, 5000);
+}
+
+// This test verifies that incoming RENEW can reuse an existing lease.
+TEST_F(Dhcpv6SrvTest, RenewCache) {
+ NakedDhcpv6Srv srv(0);
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+ const uint32_t pref = subnet_->getPreferred();
+ const uint32_t valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t timestamp = time(NULL) - delta;
+
+ // Generate client-id also duid_.
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool.
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ Lease6Ptr used(new Lease6(Lease::TYPE_NA, addr, duid_, iaid, pref, valid,
+ subnet_->getID()));
+ used->cltt_ = timestamp;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database.
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 100 seconds ago.
+ EXPECT_EQ(l->preferred_lft_, pref);
+ EXPECT_EQ(l->valid_lft_, valid);
+ EXPECT_EQ(l->cltt_, timestamp);
+
+ // Let's create a RENEW.
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ req->addOption(createIA(Lease::TYPE_NA, addr, 128, iaid));
+ req->addOption(clientid);
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(req, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(req, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processRenew(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> iaaddr =
+ checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(iaaddr);
+
+ // Check the address.
+ EXPECT_EQ(addr, iaaddr->getAddress());
+ EXPECT_EQ(pref - delta, iaaddr->getPreferred());
+ EXPECT_EQ(valid - delta, iaaddr->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming RENEW can reuse an existing lease.
+// Prefix variant.
+TEST_F(Dhcpv6SrvTest, pdRenewCache) {
+ NakedDhcpv6Srv srv(0);
+
+ // Enable lease reuse.
+ subnet_->setCacheThreshold(.1);
+
+ const IOAddress prefix("2001:db8:1:2::");
+ const uint8_t prefixlen = pd_pool_->getLength();
+ const uint32_t iaid = 234;
+ const uint32_t pref = subnet_->getPreferred();
+ const uint32_t valid = subnet_->getValid();
+ const int delta = 100;
+ const time_t timestamp = time(NULL) - delta;
+
+ // Generate client-id also duid_.
+ OptionPtr clientid = generateClientId();
+
+ // Check that the prefix we are about to use is indeed in pool.
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+ Lease6Ptr used(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid, pref, valid,
+ subnet_->getID(), HWAddrPtr(), prefixlen));
+ used->cltt_ = timestamp;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database.
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 100 seconds ago.
+ EXPECT_EQ(l->preferred_lft_, pref);
+ EXPECT_EQ(l->valid_lft_, valid);
+ EXPECT_EQ(l->cltt_, timestamp);
+
+ // Let's create a RENEW.
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ req->addOption(createIA(Lease::TYPE_PD, prefix, prefixlen, iaid));
+ req->addOption(clientid);
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(req, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(req, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processRenew(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ // check that IA_PD was returned and that there's a prefix included
+ boost::shared_ptr<Option6IAPrefix> iapref =
+ checkIA_PD(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(iapref);
+
+ // Check the prefix.
+ EXPECT_EQ(prefix, iapref->getAddress());
+ EXPECT_EQ(prefixlen, iapref->getLength());
+ EXPECT_EQ(pref - delta, iapref->getPreferred());
+ EXPECT_EQ(valid - delta, iapref->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming (positive) RELEASE with address can be
+// handled properly, that a REPLY is generated, that the response has status
+// code and that the lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_NA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+// - assigned-nas stats counter is properly decremented
+TEST_F(Dhcpv6SrvTest, ReleaseBasic) {
+ testReleaseBasic(Lease::TYPE_NA, IOAddress("2001:db8:1:1::cafe:babe"),
+ IOAddress("2001:db8:1:1::cafe:babe"), LEASE_AFFINITY_DISABLED);
+}
+
+// This test verifies that incoming (positive) RELEASE with address can be
+// handled properly, that a REPLY is generated, that the response has status
+// code and that the lease is expired and not removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_NA that does not include an IAADDR
+// - lease is actually expired instead of being removed from LeaseMgr
+// - assigned-nas stats counter is unchanged
+TEST_F(Dhcpv6SrvTest, ReleaseBasicNoDelete) {
+ testReleaseBasic(Lease::TYPE_NA, IOAddress("2001:db8:1:1::cafe:babe"),
+ IOAddress("2001:db8:1:1::cafe:babe"), LEASE_AFFINITY_ENABLED);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a SOLICIT does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, ReleaseBasicNoDeleteSolicit) {
+ testReleaseNoDelete(Lease::TYPE_NA, IOAddress("2001:db8:1:1::cafe:babe"),
+ DHCPV6_SOLICIT);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a REQUEST does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, ReleaseBasicNoDeleteRequest) {
+ testReleaseNoDelete(Lease::TYPE_NA, IOAddress("2001:db8:1:1::cafe:babe"),
+ DHCPV6_REQUEST);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a RENEW does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, ReleaseBasicNoDeleteRenew) {
+ testReleaseNoDelete(Lease::TYPE_NA, IOAddress("2001:db8:1:1::cafe:babe"),
+ DHCPV6_REQUEST);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a REBIND does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, ReleaseBasicNoDeleteRebind) {
+ testReleaseNoDelete(Lease::TYPE_NA, IOAddress("2001:db8:1:1::cafe:babe"),
+ DHCPV6_REBIND);
+}
+
+// This test verifies that incoming (positive) RELEASE with prefix can be
+// handled properly, that a REPLY is generated, that the response has
+// status code and that the lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_PD that does not include an IAPREFIX
+// - lease is actually removed from LeaseMgr
+// - assigned-pds stats counter is properly decremented
+TEST_F(Dhcpv6SrvTest, pdReleaseBasic) {
+ testReleaseBasic(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ IOAddress("2001:db8:1:2::"), LEASE_AFFINITY_DISABLED);
+}
+
+// This test verifies that incoming (positive) RELEASE with prefix can be
+// handled properly, that a REPLY is generated, that the response has
+// status code and that the lease is expired and not removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_PD that does not include an IAPREFIX
+// - lease is actually expired instead of being removed from LeaseMgr
+// - assigned-pds stats counter is unchanged
+TEST_F(Dhcpv6SrvTest, pdReleaseBasicNoDelete) {
+ testReleaseBasic(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ IOAddress("2001:db8:1:2::"), LEASE_AFFINITY_ENABLED);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a SOLICIT does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, pdReleaseBasicNoDeleteSolicit) {
+ testReleaseNoDelete(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ DHCPV6_REQUEST);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a REQUEST does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, pdReleaseBasicNoDeleteRequest) {
+ testReleaseNoDelete(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ DHCPV6_REQUEST);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a RENEW does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, pdReleaseBasicNoDeleteRenew) {
+ testReleaseNoDelete(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ DHCPV6_REQUEST);
+}
+
+// This test verifies that after a RELEASE which expired a lease the response
+// to a REBIND does not get zero lifetimes.
+TEST_F(Dhcpv6SrvTest, pdReleaseBasicNoDeleteRebind) {
+ testReleaseNoDelete(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ DHCPV6_REBIND);
+}
+
+// This test verifies that incoming (invalid) RELEASE with an address
+// can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_NA that includes STATUS-CODE
+// - No lease in LeaseMgr
+// - assigned-nas stats counter is properly not decremented
+TEST_F(Dhcpv6SrvTest, ReleaseReject) {
+ testReleaseReject(Lease::TYPE_NA, IOAddress("2001:db8:1:1::dead"));
+}
+
+// This test verifies that incoming (invalid) RELEASE with a prefix can be
+// handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA_PD that includes STATUS-CODE
+// - No lease in LeaseMgr
+// - assigned-pds stats counter is properly not decremented
+TEST_F(Dhcpv6SrvTest, pdReleaseReject) {
+ testReleaseReject(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"));
+}
+
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv6SrvTest, sanityCheck) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ // Set link-local sender address, so appropriate subnet can be
+ // selected for this packet.
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+
+ // client-id is optional for information-request, so
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
+
+ // empty packet, no client-id, no server-id
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ RFCViolation);
+
+ // This doesn't make much sense, but let's check it for completeness
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
+
+ OptionPtr clientid = generateClientId();
+ pkt->addOption(clientid);
+
+ // client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+
+ pkt->addOption(srv.getServerID());
+
+ // both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
+
+ // sane section ends here, let's do some negative tests as well
+
+ pkt->addOption(clientid);
+ pkt->addOption(clientid);
+
+ // with more than one client-id it should throw, no matter what
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+
+ pkt->delOption(D6O_CLIENTID);
+ pkt->delOption(D6O_CLIENTID);
+
+ // again we have only one client-id
+
+ // let's try different type of insanity - several server-ids
+ pkt->addOption(srv.getServerID());
+ pkt->addOption(srv.getServerID());
+
+ // with more than one server-id it should throw, no matter what
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+}
+
+// This test verifies that sanity checking against valid and invalid
+// client ids
+TEST_F(Dhcpv6SrvTest, sanityCheckClientId) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ // Case 1: completely empty (size 0)
+ pkt->addOption(generateBinaryOption(D6O_CLIENTID, 0));
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ RFCViolation);
+
+ // Case 2: too short (at the very least 3 bytes are needed)
+ pkt->delOption(D6O_CLIENTID);
+ pkt->addOption(generateBinaryOption(D6O_CLIENTID, 2));
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ RFCViolation);
+
+ // Case 3: the shortest DUID possible (3 bytes) is ok:
+ pkt->delOption(D6O_CLIENTID);
+ pkt->addOption(generateBinaryOption(D6O_CLIENTID, 3));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+
+ // Case 4: longest possible is 128, should be ok
+ pkt->delOption(D6O_CLIENTID);
+ pkt->addOption(generateBinaryOption(D6O_CLIENTID, DUID::MAX_DUID_LEN));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+
+ // Case 5: too long
+ pkt->delOption(D6O_CLIENTID);
+ pkt->addOption(generateBinaryOption(D6O_CLIENTID, DUID::MAX_DUID_LEN + 1));
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ RFCViolation);
+}
+
+// This test verifies that sanity checking against valid and invalid
+// server ids
+TEST_F(Dhcpv6SrvTest, sanityCheckServerId) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ // Case 1: completely empty (size 0)
+ pkt->addOption(generateBinaryOption(D6O_SERVERID, 0));
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+
+ // Case 2: too short (at the very least 3 bytes are needed)
+ pkt->delOption(D6O_SERVERID);
+ pkt->addOption(generateBinaryOption(D6O_SERVERID, 2));
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+
+ // Case 3: the shortest DUID possible (3 bytes) is ok:
+ pkt->delOption(D6O_SERVERID);
+ pkt->addOption(generateBinaryOption(D6O_SERVERID, 3));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::MANDATORY));
+
+ // Case 4: longest possible is 128, should be ok
+ pkt->delOption(D6O_SERVERID);
+ pkt->addOption(generateBinaryOption(D6O_SERVERID, DUID::MAX_DUID_LEN));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::MANDATORY));
+
+ // Case 5: too long
+ pkt->delOption(D6O_SERVERID);
+ pkt->addOption(generateBinaryOption(D6O_SERVERID, DUID::MAX_DUID_LEN + 1));
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+}
+
+// Check that the server is testing if server identifier received in the
+// query, matches server identifier used by the server.
+TEST_F(Dhcpv6SrvTest, testServerID) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ std::vector<uint8_t> bin;
+
+ // duid_llt constructed with: time = 0, macaddress = 00:00:00:00:00:00
+ // it's necessary to generate server identifier option
+ isc::util::encode::decodeHex("0001000100000000000000000000", bin);
+ // Now create server identifier option
+ OptionPtr serverid = OptionPtr(new Option(Option::V6, D6O_SERVERID, bin));
+
+ // Server identifier option is MANDATORY in Request message.
+ // Add server identifier option with different value from one that
+ // server is using.
+ req->addOption(serverid);
+
+ // Message should be dropped
+ EXPECT_FALSE(srv.testServerID(req));
+
+ // Delete server identifier option and add new one, with same value as
+ // server's server identifier.
+ req->delOption(D6O_SERVERID);
+ req->addOption(srv.getServerID());
+
+ // With proper server identifier we expect true
+ EXPECT_TRUE(srv.testServerID(req));
+
+ // server-id MUST NOT appear in Solicit, so check if server is
+ // not dropping a message without server id.
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ EXPECT_TRUE(srv.testServerID(req));
+}
+
+// Test that some messages are discarded by the server if they are sent to
+// unicast address.
+TEST_F(Dhcpv6SrvTest, testUnicast) {
+ NakedDhcpv6Srv srv(0);
+ // Explicitly list client's message types which must be discarded if
+ // sent to unicast address.
+ const uint8_t not_allowed_unicast[] = {
+ DHCPV6_SOLICIT,
+ DHCPV6_CONFIRM,
+ DHCPV6_REBIND,
+ DHCPV6_INFORMATION_REQUEST
+ };
+ // Iterate over these messages and make sure they are discarded.
+ for (int i = 0; i < sizeof(not_allowed_unicast); ++i) {
+ Pkt6Ptr msg = Pkt6Ptr(new Pkt6(not_allowed_unicast[i], 1234));
+ msg->setLocalAddr(IOAddress("2001:db8:1::1"));
+ EXPECT_FALSE(srv.testUnicast(msg))
+ << "server accepts message type "
+ << static_cast<int>(not_allowed_unicast[i])
+ << "being sent to unicast address; this message should"
+ " be discarded according to section 18.4 of RFC 8415";
+ }
+ // Explicitly list client/relay message types which are allowed to
+ // be sent to unicast.
+ const uint8_t allowed_unicast[] = {
+ DHCPV6_REQUEST,
+ DHCPV6_RENEW,
+ DHCPV6_RELEASE,
+ DHCPV6_DECLINE,
+ DHCPV6_RELAY_FORW
+ };
+ // Iterate over these messages and check that they are accepted being
+ // sent to unicast.
+ for (int i = 0; i < sizeof(allowed_unicast); ++i) {
+ Pkt6Ptr msg = Pkt6Ptr(new Pkt6(allowed_unicast[i], 1234));
+ msg->setLocalAddr(IOAddress("2001:db8:1::1"));
+ msg->addOption(srv.getServerID());
+ EXPECT_TRUE(srv.testUnicast(msg))
+ << "server doesn't accept message type "
+ << static_cast<int>(allowed_unicast[i])
+ << "being sent to unicast address";
+ }
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// source address.
+TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
+ NakedDhcpv6Srv srv(0);
+
+ auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, SubnetID(10));
+ auto subnet2 = Subnet6::create(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, SubnetID(20));
+ auto subnet3 = Subnet6::create(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, SubnetID(30));
+
+ // CASE 1: We have only one subnet defined and we received local traffic.
+ // The only available subnet used to be picked, but not anymore
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+ CfgMgr::instance().commit();
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+
+ // The clause for assuming local subnet if there is only one subnet is was
+ // removed.
+ bool drop = false;
+ EXPECT_FALSE(srv.selectSubnet(pkt, drop));
+ EXPECT_FALSE(drop);
+
+ // CASE 2: We have only one subnet defined and we received relayed traffic.
+ // We should NOT select it.
+
+ // Identical steps as in case 1, but repeated for clarity
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+ CfgMgr::instance().commit();
+ pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
+ Subnet6Ptr selected = srv.selectSubnet(pkt, drop);
+ EXPECT_FALSE(selected);
+ EXPECT_FALSE(drop);
+
+ // CASE 3: We have three subnets defined and we received local traffic.
+ // Nothing should be selected.
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_FALSE(selected);
+ EXPECT_FALSE(drop);
+
+ // CASE 4: We have three subnets defined and we received relayed traffic
+ // that came out of subnet 2. We should select subnet2 then
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+ pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet2);
+ EXPECT_FALSE(drop);
+
+ // CASE 5: We have three subnets defined and we received relayed traffic
+ // that came out of undefined subnet. We should select nothing
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+ pkt->setRemoteAddr(IOAddress("2001:db8:4::baca"));
+ EXPECT_FALSE(srv.selectSubnet(pkt, drop));
+ EXPECT_FALSE(drop);
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// network interface name.
+TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
+ NakedDhcpv6Srv srv(0);
+
+ auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, SubnetID(10));
+ auto subnet2 = Subnet6::create(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, SubnetID(20));
+ auto subnet3 = Subnet6::create(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, SubnetID(30));
+
+ subnet1->setIface("eth0");
+ subnet3->setIface("wifi1");
+
+ // CASE 1: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth0. The only available subnet should be selected
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+ CfgMgr::instance().commit();
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+
+ bool drop = false;
+ Subnet6Ptr selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet1);
+ EXPECT_FALSE(drop);
+
+ // CASE 2: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+ CfgMgr::instance().commit();
+
+ pkt->setIface("eth1");
+ pkt->setIndex(ETH1_INDEX);
+
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_FALSE(selected);
+ EXPECT_FALSE(drop);
+
+ // CASE 3: We have only 3 subnets defined, one over eth0, one remote and
+ // one over wifi1.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ EXPECT_EQ(subnet1, srv.selectSubnet(pkt, drop));
+ EXPECT_FALSE(drop);
+
+ pkt->setIface("eth3"); // no such interface
+ pkt->setIndex(3);
+ EXPECT_EQ(Subnet6Ptr(), srv.selectSubnet(pkt, drop)); // nothing selected
+ EXPECT_FALSE(drop);
+
+ pkt->setIface("wifi1");
+ pkt->setIndex(101); // arbitrary value
+ EXPECT_EQ(subnet3, srv.selectSubnet(pkt, drop));
+ EXPECT_FALSE(drop);
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// linkaddr in RELAY-FORW message
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
+ NakedDhcpv6Srv srv(0);
+
+ auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, SubnetID(10));
+ auto subnet2 = Subnet6::create(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, SubnetID(20));
+ auto subnet3 = Subnet6::create(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, SubnetID(30));
+
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+ relay.peeraddr_ = IOAddress("fe80::1");
+
+ // CASE 1: We have only one subnet defined and we received relayed traffic.
+ // The only available subnet should NOT be selected.
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+ CfgMgr::instance().commit();
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->relay_info_.push_back(relay);
+
+ bool drop = false;
+ Subnet6Ptr selected = srv.selectSubnet(pkt, drop);
+ EXPECT_FALSE(selected);
+ EXPECT_FALSE(drop);
+
+ // CASE 2: We have three subnets defined and we received relayed traffic
+ // that came out of subnet 2. We should select subnet2 then
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet2);
+ EXPECT_FALSE(drop);
+
+ // Source of the packet should have no meaning. Selection is based
+ // on linkaddr field in the relay
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::baca"));
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet2);
+ EXPECT_FALSE(drop);
+
+ // But not when this linkaddr field is not usable.
+ Pkt6::RelayInfo relay2;
+ relay2.peeraddr_ = IOAddress("fe80::1");
+ pkt->relay_info_.clear();
+ pkt->relay_info_.push_back(relay2);
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet1);
+ EXPECT_FALSE(drop);
+
+ // CASE 3: We have three subnets defined and we received relayed traffic
+ // that came out a layer 2 relay on subnet 2. We should select subnet2 then
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+ pkt->relay_info_.clear();
+ pkt->relay_info_.push_back(relay);
+ relay2.hop_count_ = 1;
+ pkt->relay_info_.push_back(relay2);
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet2);
+ EXPECT_FALSE(drop);
+
+ // The number of level 2 relay doesn't matter
+ pkt->relay_info_.clear();
+ Pkt6::RelayInfo relay20;
+ relay20.peeraddr_ = IOAddress("fe80::1");
+ pkt->relay_info_.push_back(relay20);
+ Pkt6::RelayInfo relay21;
+ relay21.peeraddr_ = IOAddress("fe80::1");
+ relay21.hop_count_ = 1;
+ pkt->relay_info_.push_back(relay21);
+ relay.hop_count_ = 2;
+ pkt->relay_info_.push_back(relay);
+ Pkt6::RelayInfo relay22;
+ relay22.peeraddr_ = IOAddress("fe80::1");
+ relay22.hop_count_ = 3;
+ pkt->relay_info_.push_back(relay22);
+ Pkt6::RelayInfo relay23;
+ relay23.peeraddr_ = IOAddress("fe80::1");
+ relay23.hop_count_ = 4;
+ pkt->relay_info_.push_back(relay23);
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet2);
+ EXPECT_FALSE(drop);
+
+ // Only the inner/last relay with a usable address matters
+ pkt->relay_info_.clear();
+ pkt->relay_info_.push_back(relay20);
+ pkt->relay_info_.push_back(relay21);
+ pkt->relay_info_.push_back(relay);
+ pkt->relay_info_.push_back(relay22);
+ Pkt6::RelayInfo relay3;
+ relay3.linkaddr_ = IOAddress("2001:db8:3::1234");
+ relay3.peeraddr_ = IOAddress("fe80::1");
+ relay3.hop_count_ = 4;
+ pkt->relay_info_.push_back(relay3);
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet3);
+ EXPECT_FALSE(drop);
+
+ // CASE 4: We have three subnets defined and we received relayed traffic
+ // that came out of undefined subnet. We should select nothing
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+ pkt->relay_info_.clear();
+ relay.hop_count_ = 0;
+ relay.linkaddr_ = IOAddress("2001:db8:4::1234");
+ pkt->relay_info_.push_back(relay);
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_FALSE(selected);
+ EXPECT_FALSE(drop);
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// interface-id option
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
+ NakedDhcpv6Srv srv(0);
+
+ auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, SubnetID(10));
+ auto subnet2 = Subnet6::create(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, SubnetID(20));
+ auto subnet3 = Subnet6::create(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, SubnetID(30));
+
+ subnet1->setInterfaceId(generateInterfaceId("relay1"));
+ subnet2->setInterfaceId(generateInterfaceId("relay2"));
+
+ // CASE 1: We have only one subnet defined and it is for interface-id "relay1"
+ // Packet came with interface-id "relay2". We should not select subnet1
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+ CfgMgr::instance().commit();
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+ relay.peeraddr_ = IOAddress("fe80::1");
+ OptionPtr opt = generateInterfaceId("relay2");
+ relay.options_.insert(make_pair(opt->getType(), opt));
+ pkt->relay_info_.push_back(relay);
+
+ // There is only one subnet configured and we are outside of that subnet
+ bool drop = false;
+ Subnet6Ptr selected = srv.selectSubnet(pkt, drop);
+ EXPECT_FALSE(selected);
+ EXPECT_FALSE(drop);
+
+ // CASE 2: We have only one subnet defined and it is for interface-id "relay2"
+ // Packet came with interface-id "relay2". We should select it
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2); // just a single subnet
+ CfgMgr::instance().commit();
+ selected = srv.selectSubnet(pkt, drop);
+ EXPECT_EQ(selected, subnet2);
+ EXPECT_FALSE(drop);
+
+ // CASE 3: We have only 3 subnets defined: one remote for interface-id "relay1",
+ // one remote for interface-id "relay2" and third local
+ // packet comes with interface-id "relay2". We should select subnet2
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+ CfgMgr::instance().commit();
+
+ EXPECT_EQ(subnet2, srv.selectSubnet(pkt, drop));
+ EXPECT_FALSE(drop);
+}
+
+// Checks if server responses are sent to the proper port.
+TEST_F(Dhcpv6SrvTest, portsClientPort) {
+ NakedDhcpv6Srv srv(0);
+
+ // Enforce a specific client port value.
+ EXPECT_EQ(0, srv.client_port_);
+ srv.client_port_ = 1234;
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = PktCaptures::captureSimpleSolicit();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // Get Advertise...
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr adv = srv.fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // This is sent back to client directly, should be port 546
+ EXPECT_EQ(srv.client_port_, adv->getRemotePort());
+}
+
+// Checks if server responses are sent to the proper port.
+TEST_F(Dhcpv6SrvTest, portsServerPort) {
+ // Create the test server in test mode.
+ NakedDhcpv6Srv srv(0);
+
+ // Enforce a specific server port value.
+ EXPECT_EQ(0, srv.server_port_);
+ srv.server_port_ = 1234;
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = PktCaptures::captureSimpleSolicit();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // Get Advertise...
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr adv = srv.fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Verify the local port: it must be the server port.
+ EXPECT_EQ(srv.server_port_, adv->getLocalPort());
+}
+
+// Checks if server responses are sent to the proper port.
+TEST_F(Dhcpv6SrvTest, portsDirectTraffic) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = PktCaptures::captureSimpleSolicit();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // Get Advertise...
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr adv = srv.fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // This is sent back to client directly, should be port 546
+ EXPECT_EQ(DHCP6_CLIENT_PORT, adv->getRemotePort());
+}
+
+// Checks if server responses are sent to the proper port.
+TEST_F(Dhcpv6SrvTest, portsRelayedTraffic) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = PktCaptures::captureRelayedSolicit();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // Get Advertise...
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr adv = srv.fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // This is sent back to relay, so port is 547
+ EXPECT_EQ(DHCP6_SERVER_PORT, adv->getRemotePort());
+}
+
+// Test that the server processes relay-source-port option correctly.
+TEST_F(Dhcpv6SrvTest, relaySourcePort) {
+ NakedDhcpv6Srv srv(0);
+
+ string config =
+ "{"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/48\" "
+ " } ],"
+ " \"valid-lifetime\": 4000"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, srv));
+
+ // Create a solicit
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pretend the packet came via one relay.
+ Pkt6::RelayInfo relay;
+ relay.msg_type_ = DHCPV6_RELAY_FORW;
+ relay.hop_count_ = 1;
+ relay.linkaddr_ = IOAddress("2001:db8::1");
+ relay.peeraddr_ = IOAddress("fe80::1");
+
+ // Set the source port
+ sol->setRemotePort(1234);
+
+ // Simulate that we have received that traffic
+ sol->pack();
+
+ // Add a relay-source-port option
+ OptionBuffer zero(2, 0);
+ OptionPtr opt(new Option(Option::V6, D6O_RELAY_SOURCE_PORT, zero));
+ relay.options_.insert(make_pair(opt->getType(), opt));
+ sol->relay_info_.push_back(relay);
+
+ // Simulate that we have received that traffic
+ sol->pack();
+ EXPECT_EQ(DHCPV6_RELAY_FORW, sol->getBuffer()[0]);
+ Pkt6Ptr query(new Pkt6(static_cast<const uint8_t*>
+ (sol->getBuffer().getData()),
+ sol->getBuffer().getLength()));
+ query->setRemoteAddr(sol->getRemoteAddr());
+ query->setRemotePort(sol->getRemotePort());
+ query->setLocalAddr(sol->getLocalAddr());
+ query->setLocalPort(sol->getLocalPort());
+ query->setIface(sol->getIface());
+ query->setIndex(sol->getIndex());
+
+ srv.fakeReceive(query);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // Check trace of processing
+ EXPECT_EQ(1234, query->getRemotePort());
+ ASSERT_EQ(1, query->relay_info_.size());
+ EXPECT_TRUE(query->getRelayOption(D6O_RELAY_SOURCE_PORT, 0));
+
+ // Get Response...
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr rsp = srv.fake_sent_.front();
+ ASSERT_TRUE(rsp);
+
+ // Check it
+ EXPECT_EQ(1234, rsp->getRemotePort());
+ EXPECT_EQ(DHCPV6_RELAY_REPL, rsp->getBuffer()[0]);
+
+ // Get Advertise
+ Pkt6Ptr adv(new Pkt6(static_cast<const uint8_t*>
+ (rsp->getBuffer().getData()),
+ rsp->getBuffer().getLength()));
+ adv->unpack();
+
+ // Check it
+ EXPECT_EQ(DHCPV6_ADVERTISE, adv->getType());
+ ASSERT_EQ(1, adv->relay_info_.size());
+ EXPECT_TRUE(adv->getRelayOption(D6O_RELAY_SOURCE_PORT, 0));
+}
+
+// Checks effect of persistency (aka always-send) flag on the ORO
+TEST_F(Dhcpv6SrvTest, prlPersistency) {
+ IfaceMgrTestConfig test_config(true);
+
+ ASSERT_NO_THROW(configure(CONFIGS[2]));
+
+ // Create a packet with enough to select the subnet and go through
+ // the SOLICIT processing
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Create and add an ORO for another option
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(D6O_SNTP_SERVERS);
+ sol->addOption(oro);
+
+ // Let the server process it and generate a response.
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response = srv_.processSolicit(ctx);
+
+ // The server should add a subscriber-id option
+ ASSERT_TRUE(response->getOption(D6O_SUBSCRIBER_ID));
+ // But no dns-servers
+ ASSERT_FALSE(response->getOption(D6O_NAME_SERVERS));
+ // Nor a sntp-servers
+ ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+
+ // Reset ORO adding dns-servers
+ sol->delOption(D6O_ORO);
+ oro->addValue(D6O_NAME_SERVERS);
+ sol->addOption(oro);
+
+ // Let the server process it again. This time the name-servers
+ // option should be present.
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv_.earlyGHRLookup(sol, ctx2);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx2, drop);
+ ASSERT_FALSE(drop);
+ response = srv_.processSolicit(ctx2);
+
+ // Processing should add a subscriber-id option
+ ASSERT_TRUE(response->getOption(D6O_SUBSCRIBER_ID));
+ // and now a dns-servers
+ ASSERT_TRUE(response->getOption(D6O_NAME_SERVERS));
+ // and still no sntp-servers
+ ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+
+ // Reset ORO adding subscriber-id
+ sol->delOption(D6O_ORO);
+ OptionUint16ArrayPtr oro2(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro2);
+ oro2->addValue(D6O_SUBSCRIBER_ID);
+ sol->addOption(oro2);
+
+ // Let the server process it again.
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv_.earlyGHRLookup(sol, ctx3);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx3, drop);
+ ASSERT_FALSE(drop);
+ response = srv_.processSolicit(ctx3);
+
+ // The subscriber-id option should be present but only once despite
+ // it is both requested and has always-send.
+ const OptionCollection& sifs = response->getOptions(D6O_SUBSCRIBER_ID);
+ ASSERT_EQ(1, sifs.size());
+ // But no dns-servers
+ ASSERT_FALSE(response->getOption(D6O_NAME_SERVERS));
+ // Nor a sntp-servers
+ ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+}
+
+// Checks effect of cancellation (aka never-send) flag.
+TEST_F(Dhcpv6SrvTest, neverSend) {
+ IfaceMgrTestConfig test_config(true);
+
+ ASSERT_NO_THROW(configure(CONFIGS[3]));
+
+ // Create a packet with enough to select the subnet and go through
+ // the SOLICIT processing
+ Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Create and add an ORO for another option
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(D6O_SNTP_SERVERS);
+ sol->addOption(oro);
+
+ // Let the server process it and generate a response.
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr response = srv_.processSolicit(ctx);
+
+ // The server should not add a subscriber-id option
+ ASSERT_FALSE(response->getOption(D6O_SUBSCRIBER_ID));
+ // And no dns-servers
+ ASSERT_FALSE(response->getOption(D6O_NAME_SERVERS));
+ // Nor a sntp-servers
+ ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+
+ // Reset ORO adding dns-servers
+ sol->delOption(D6O_ORO);
+ oro->addValue(D6O_NAME_SERVERS);
+ sol->addOption(oro);
+
+ // Let the server process it again. This time the name-servers
+ // option should be present.
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv_.earlyGHRLookup(sol, ctx2);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx2, drop);
+ ASSERT_FALSE(drop);
+ response = srv_.processSolicit(ctx2);
+
+ // Processing should not add a subscriber-id option
+ ASSERT_FALSE(response->getOption(D6O_SUBSCRIBER_ID));
+ // But now a dns-servers
+ ASSERT_TRUE(response->getOption(D6O_NAME_SERVERS));
+ // And still no sntp-servers
+ ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+
+ // Reset ORO adding subscriber-id
+ sol->delOption(D6O_ORO);
+ OptionUint16ArrayPtr oro2(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro2);
+ oro2->addValue(D6O_SUBSCRIBER_ID);
+ sol->addOption(oro2);
+
+ // Let the server process it again.
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv_.earlyGHRLookup(sol, ctx3);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx3, drop);
+ ASSERT_FALSE(drop);
+ response = srv_.processSolicit(ctx3);
+
+ // The subscriber-id option should still not be present.
+ ASSERT_FALSE(response->getOption(D6O_SUBSCRIBER_ID));
+ // And no dns-servers
+ ASSERT_FALSE(response->getOption(D6O_NAME_SERVERS));
+ // Nor a sntp-servers
+ ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+}
+
+// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
+// @todo Uncomment this test as part of #3180 work.
+// Kea code currently fails to handle docsis traffic.
+TEST_F(Dhcpv6SrvTest, docsisTraffic) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's get a traffic capture from DOCSIS3.0 modem
+ Pkt6Ptr sol = PktCaptures::captureDocsisRelayedSolicit();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // We should have an Advertise in response
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr adv = srv.fake_sent_.front();
+ ASSERT_TRUE(adv);
+}
+
+// Checks if relay IP address specified in the relay-info structure in
+// subnet6 is being used properly.
+TEST_F(Dhcpv6SrvTest, relayOverride) {
+
+ // We have 2 subnets defined. Note that both have a relay address
+ // defined. Both are not belonging to the subnets. That is
+ // important, because if the relay belongs to the subnet, there's
+ // no need to specify relay override.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"relay\": { "
+ " \"ip-address\": \"2001:db8:3::1\""
+ " }"
+ " }, "
+ " { \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"id\": 2, "
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"relay\": { "
+ " \"ip-address\": \"2001:db8:3::2\""
+ " }"
+ " } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Use this config to set up the server
+ ASSERT_NO_THROW(configure(config));
+
+ // Let's get the subnet configuration objects
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ // Let's get them for easy reference
+ Subnet6Ptr subnet1 = *subnets->begin();
+ Subnet6Ptr subnet2 = *std::next(subnets->begin());
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet2);
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Now pretend the packet came via one relay.
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:1::1");
+ relay.peeraddr_ = IOAddress("fe80::1");
+
+ sol->relay_info_.push_back(relay);
+
+ // This is just a sanity check, we're using regular method: the relay
+ // belongs to the first (2001:db8:1::/64) subnet, so it's an easy decision.
+ bool drop = false;
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+
+ // Relay belongs to the second subnet, so it should be selected.
+ sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:2::1");
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+
+ // Now let's check if the relay override for the first subnets works
+ sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:3::1");
+ EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+
+ // Now repeat that for relay matching the second subnet.
+ sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:3::2");
+ EXPECT_TRUE(subnet2 == srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+
+ // Finally, let's check that completely mismatched relay will not get us
+ // anything
+ sol->relay_info_.back().linkaddr_ = IOAddress("2001:db8:1234::1");
+ EXPECT_FALSE(srv_.selectSubnet(sol, drop));
+ EXPECT_FALSE(drop);
+}
+
+/// @brief Creates RSOO option with suboptions
+///
+/// Creates Relay-Supplied Options option that includes nested options. The
+/// codes of those nested options are specified in codes parameter. Content of
+/// the options is controlled with payload parameter. When it is zero, option
+/// code will be used (e.g. option 100 will contain repeating bytes of value 100).
+/// When non-zero is used, payload will be used. Each suboption length is always
+/// set to the arbitrarily chosen value of 10.
+///
+/// @param codes a vector of option codes to be created
+/// @param payload specified payload (0 = fill payload with repeating option code)
+/// @return RSOO with nested options
+OptionPtr createRSOO(const std::vector<uint16_t>& codes, uint8_t payload = 0) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_RSOO);
+ if (!def) {
+ isc_throw(BadValue, "Can't find RSOO definition");
+ }
+ OptionPtr rsoo_container(new OptionCustom(*def, Option::V6));
+
+ for (size_t i = 0; i < codes.size(); ++i) {
+ OptionBuffer buf(10, payload ? payload : codes[i]); // let's make the option 10 bytes long
+ rsoo_container->addOption(OptionPtr(new Option(Option::V6, codes[i], buf)));
+ }
+
+ return (rsoo_container);
+}
+
+// Test that the server processes RSOO (Relay Supplied Options option) correctly,
+// i.e. it includes in its response the options that are inserted by the relay.
+// The server must do this only for options that are RSOO-enabled.
+TEST_F(Dhcpv6SrvTest, rsoo) {
+
+ Dhcp6Client client;
+
+ string config =
+ "{"
+ " \"relay-supplied-options\": [ \"110\", \"120\", \"130\" ],"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/48\" "
+ " } ],"
+ " \"valid-lifetime\": 4000"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Now pretend the packet came via one relay.
+ Pkt6::RelayInfo relay;
+ relay.msg_type_ = DHCPV6_RELAY_FORW;
+ relay.hop_count_ = 1;
+ relay.linkaddr_ = IOAddress("2001:db8::1");
+ relay.peeraddr_ = IOAddress("fe80::1");
+ vector<uint16_t> rsoo1;
+ rsoo1.push_back(109);
+ rsoo1.push_back(110);
+ rsoo1.push_back(111);
+
+ // The relay will request echoing back 3 options: 109, 110, 111.
+ // The configuration allows echoing back only 110.
+ OptionPtr opt = createRSOO(rsoo1);
+ relay.options_.insert(make_pair(opt->getType(), opt));
+ client.relay_info_.push_back(relay);
+
+ client.doSARR();
+
+ // Option 110 should be copied to the client
+ EXPECT_FALSE(client.config_.options_.find(110) == client.config_.options_.end());
+
+ // Options 109 and 111 should not be copied (they are not RSOO-enabled)
+ EXPECT_TRUE(client.config_.options_.find(109) == client.config_.options_.end());
+ EXPECT_TRUE(client.config_.options_.find(111) == client.config_.options_.end());
+}
+
+// Test that the server processes RSOO (Relay Supplied Options option) correctly
+// when there are more relays. In particular, the following case is tested:
+// if relay1 inserts option A and B, relay2 inserts option B and C, the response
+// should include options A, B and C. The server must use instance of option B
+// that comes from the first relay, not the second one.
+TEST_F(Dhcpv6SrvTest, rsoo2relays) {
+
+ Dhcp6Client client;
+
+ string config =
+ "{"
+ " \"relay-supplied-options\": [ \"110\", \"120\", \"130\" ],"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/48\" "
+ " } ],"
+ " \"valid-lifetime\": 4000"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Now pretend the packet came via two relays.
+
+ // This situation reflects the following case:
+ // client----relay1----relay2----server
+
+ // Fabricate the first relay.
+ Pkt6::RelayInfo relay1;
+ relay1.msg_type_ = DHCPV6_RELAY_FORW;
+ relay1.hop_count_ = 1;
+ relay1.linkaddr_ = IOAddress("2001:db8::1");
+ relay1.peeraddr_ = IOAddress("fe80::1");
+ vector<uint16_t> rsoo1;
+ rsoo1.push_back(110); // The relay1 will send 2 options: 110, 120
+ rsoo1.push_back(120);
+ OptionPtr opt = createRSOO(rsoo1, 1); // use 0x1 as payload
+ relay1.options_.insert(make_pair(opt->getType(), opt));
+
+ // Now the second relay.
+ Pkt6::RelayInfo relay2;
+ relay2.msg_type_ = DHCPV6_RELAY_FORW;
+ relay2.hop_count_ = 2;
+ relay2.linkaddr_ = IOAddress("2001:db8::2");
+ relay2.peeraddr_ = IOAddress("fe80::2");
+ vector<uint16_t> rsoo2;
+ rsoo2.push_back(120); // The relay2 will send 2 options: 120, 130
+ rsoo2.push_back(130);
+ opt = createRSOO(rsoo2, 2); // use 0x2 as payload
+ relay2.options_.insert(make_pair(opt->getType(), opt));
+
+ // The relays encapsulate packet in this order: relay1, relay2,
+ // but the server decapsulates the packet in reverse order.
+ client.relay_info_.push_back(relay2);
+ client.relay_info_.push_back(relay1);
+
+ // There's a conflict here. Both relays want the server to echo back option
+ // 120. According to RFC6422, section 6:
+ //
+ // When such a conflict exists, the DHCP server MUST choose no more than
+ // one of these options to forward to the client. The DHCP server MUST
+ // NOT forward more than one of these options to the client.
+ //
+ // By default, the DHCP server MUST choose the innermost value -- the
+ // value supplied by the relay agent closest to the DHCP client -- to
+ // forward to the DHCP client.
+
+ // Let the client do his thing.
+ client.doSARR();
+
+ int count110 = 0; // Let's count how many times option 110 was echoed back
+ int count120 = 0; // Let's count how many times option 120 was echoed back
+ int count130 = 0; // Let's count how many times option 130 was echoed back
+ OptionPtr opt120;
+ for (OptionCollection::const_iterator it = client.config_.options_.begin();
+ it != client.config_.options_.end(); ++it) {
+ switch (it->second->getType()) {
+ case 110:
+ count110++;
+ break;
+ case 120:
+ count120++;
+ opt120 = it->second;
+ break;
+ case 130:
+ count130++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // We expect to have exactly one instance of each option code.
+ EXPECT_EQ(1, count110);
+ EXPECT_EQ(1, count120);
+ EXPECT_EQ(1, count130);
+
+ // Now, let's check if the proper instance of option 120 was sent. It should
+ // match the content of what the first relay had sent.
+ ASSERT_TRUE(opt120);
+ vector<uint8_t> expected(10, 1);
+ EXPECT_TRUE(expected == opt120->getData());
+}
+
+// This test verifies that the server will send the option for which it
+// has a candidate, rather than the option sent by the relay in the RSOO.
+TEST_F(Dhcpv6SrvTest, rsooOverride) {
+ Dhcp6Client client;
+ // The client will be requesting specific options.
+ client.useORO(true);
+
+ // The following configuration enables RSOO options: 110 and 120.
+ // It also configures the server with option 120 which should
+ // "override" the option 120 sent in the RSOO by the relay.
+ string config =
+ "{"
+ " \"relay-supplied-options\": [ \"110\", \"120\" ],"
+ " \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 120,"
+ " \"type\": \"binary\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"code\": 120,"
+ " \"csv-format\": false,"
+ " \"data\": \"05\""
+ " } ],"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/48\" "
+ " } ],"
+ " \"valid-lifetime\": 4000"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Fabricate the relay.
+ Pkt6::RelayInfo relay;
+ relay.msg_type_ = DHCPV6_RELAY_FORW;
+ relay.hop_count_ = 1;
+ relay.linkaddr_ = IOAddress("2001:db8::1");
+ relay.peeraddr_ = IOAddress("fe80::1");
+ vector<uint16_t> rsoo;
+ // The relay will send 2 options: 110, 120
+ rsoo.push_back(110);
+ rsoo.push_back(120);
+ // Use 0x1 as payload
+ OptionPtr opt = createRSOO(rsoo, 1);
+ relay.options_.insert(make_pair(opt->getType(), opt));
+ client.relay_info_.push_back(relay);
+
+ // Client should request option 120 in the ORO so as the server
+ // sends the configured option 120 to the client.
+ client.requestOption(120);
+ client.doSARR();
+
+ // The option 110 should be the one injected by the relay.
+ opt = client.config_.findOption(110);
+ ASSERT_TRUE(opt);
+ // We check that this is the option injected by the relay by
+ // checking option length. It should have 10 bytes long payload.
+ ASSERT_EQ(10, opt->getData().size());
+
+ // The second option should be the one configured on the server,
+ // rather than the one injected by the relay.
+ opt = client.config_.findOption(120);
+ ASSERT_TRUE(opt);
+ // It should have the size of 1.
+ ASSERT_EQ(1, opt->getData().size());
+}
+
+// Test checks if pkt6-advertise-received is bumped up correctly.
+// Note that in properly configured network the server never receives Advertise
+// messages.
+TEST_F(Dhcpv6SrvTest, receiveAdvertiseStat) {
+ testReceiveStats(DHCPV6_ADVERTISE, "pkt6-advertise-received");
+}
+
+// Test checks if pkt6-reply-received is bumped up correctly.
+// Note that in properly configured network the server never receives Reply
+// messages.
+TEST_F(Dhcpv6SrvTest, receiveReplyStat) {
+ testReceiveStats(DHCPV6_REPLY, "pkt6-reply-received");
+}
+
+// Test checks if pkt6-dhcpv4-response-received is bumped up correctly.
+// Note that in properly configured network the server never receives
+// Dhcpv4-Response messages.
+TEST_F(Dhcpv6SrvTest, receiveDhcpv4ResponseStat) {
+ testReceiveStats(DHCPV6_DHCPV4_RESPONSE, "pkt6-dhcpv4-response-received");
+}
+
+// Test checks if pkt6-unknown-received is bumped up correctly.
+TEST_F(Dhcpv6SrvTest, receiveUnknownStat) {
+ testReceiveStats(123, "pkt6-unknown-received");
+}
+
+// Test checks if pkt6-renew-received is bumped up correctly.
+TEST_F(Dhcpv6SrvTest, receiveRenewStat) {
+ testReceiveStats(DHCPV6_RENEW, "pkt6-renew-received");
+}
+
+// Test checks if pkt6-rebind-received is bumped up correctly.
+TEST_F(Dhcpv6SrvTest, receiveRebindStat) {
+ testReceiveStats(DHCPV6_REBIND, "pkt6-rebind-received");
+}
+
+// Test checks if pkt6-release-received is bumped up correctly.
+TEST_F(Dhcpv6SrvTest, receiveReleaseStat) {
+ testReceiveStats(DHCPV6_RELEASE, "pkt6-release-received");
+}
+
+// Test checks if pkt6-decline-received is bumped up correctly.
+TEST_F(Dhcpv6SrvTest, receiveDeclineStat) {
+ testReceiveStats(DHCPV6_DECLINE, "pkt6-decline-received");
+}
+
+// Test checks if pkt6-dhcpv4-query-received is bumped up correctly.
+TEST_F(Dhcpv6SrvTest, receiveDhcpv4QueryStat) {
+ testReceiveStats(DHCPV6_DHCPV4_QUERY, "pkt6-dhcpv4-query-received");
+}
+
+// Test checks if reception of a malformed packet increases pkt-parse-failed
+// and pkt6-receive-drop
+TEST_F(Dhcpv6SrvTest, receiveParseFailedStat) {
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ NakedDhcpv6Srv srv(0);
+
+ // Let's get a simple SOLICIT...
+ Pkt6Ptr pkt = PktCaptures::captureSimpleSolicit();
+
+ // And pretend its packet is only 3 bytes long.
+ pkt->data_.resize(3);
+
+ // Check that the tested statistics is initially set to 0
+ ObservationPtr pkt6_rcvd = mgr.getObservation("pkt6-received");
+ ObservationPtr parse_fail = mgr.getObservation("pkt6-parse-failed");
+ ObservationPtr recv_drop = mgr.getObservation("pkt6-receive-drop");
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(parse_fail);
+ ASSERT_TRUE(recv_drop);
+ EXPECT_EQ(0, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(0, parse_fail->getInteger().first);
+ EXPECT_EQ(0, recv_drop->getInteger().first);
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(pkt);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // All expected statistics must be present.
+ pkt6_rcvd = mgr.getObservation("pkt6-received");
+ parse_fail = mgr.getObservation("pkt6-parse-failed");
+ recv_drop = mgr.getObservation("pkt6-receive-drop");
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(parse_fail);
+ ASSERT_TRUE(recv_drop);
+
+ // They also must have expected values.
+ EXPECT_EQ(1, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(1, parse_fail->getInteger().first);
+ EXPECT_EQ(1, recv_drop->getInteger().first);
+}
+
+// This test verifies that the server is able to handle an empty DUID (client-id)
+// in incoming client message.
+TEST_F(Dhcpv6SrvTest, emptyClientId) {
+ Dhcp6Client client;
+
+ // The following configuration enables RSOO options: 110 and 120.
+ // It also configures the server with option 120 which should
+ // "override" the option 120 sent in the RSOO by the relay.
+ string config =
+ "{"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/48\" "
+ " } ],"
+ " \"valid-lifetime\": 4000"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Tell the client to not send client-id on its own.
+ client.useClientId(false);
+
+ // Instead, tell him to send this extra option, which happens to be
+ // an empty client-id.
+ OptionPtr empty_client_id(new Option(Option::V6, D6O_CLIENTID));
+ client.addExtraOption(empty_client_id);
+
+ // Let's check whether the server is able to process this packet without
+ // throwing any exceptions. We don't care whether the server sent any
+ // responses or not. The goal is to check that the server didn't throw
+ // any exceptions.
+ EXPECT_NO_THROW(client.doSARR());
+}
+
+// This test verifies that the server is able to handle an empty DUID (server-id)
+// in incoming client message.
+TEST_F(Dhcpv6SrvTest, emptyServerId) {
+ Dhcp6Client client;
+
+ // The following configuration enables RSOO options: 110 and 120.
+ // It also configures the server with option 120 which should
+ // "override" the option 120 sent in the RSOO by the relay.
+ string config =
+ "{"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/48\" "
+ " } ],"
+ " \"valid-lifetime\": 4000"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Tell the client to use this specific server-id.
+ OptionPtr empty_server_id(new Option(Option::V6, D6O_SERVERID));
+ client.useServerId(empty_server_id);
+
+ // Let's check whether the server is able to process this packet without
+ // throwing any exceptions. We don't care whether the server sent any
+ // responses or not. The goal is to check that the server didn't throw
+ // any exceptions.
+ EXPECT_NO_THROW(client.doSARR());
+}
+
+// This test verifies that the server is able to handle a too large DUID (server-id)
+// in incoming client message.
+TEST_F(Dhcpv6SrvTest, tooLongServerId) {
+ Dhcp6Client client;
+
+ // The following configuration enables RSOO options: 110 and 120.
+ // It also configures the server with option 120 which should
+ // "override" the option 120 sent in the RSOO by the relay.
+ string config =
+ "{"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/48\" "
+ " } ],"
+ " \"valid-lifetime\": 4000"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Tell the client to use this specific server-id.
+ std::vector<uint8_t> data(250, 250);
+ OptionPtr long_server_id(new Option(Option::V6, D6O_SERVERID, data));
+ client.useServerId(long_server_id);
+
+ // Let's check whether the server is able to process this packet without
+ // throwing any exceptions. We don't care whether the server sent any
+ // responses or not. The goal is to check that the server didn't throw
+ // any exceptions.
+ EXPECT_NO_THROW(client.doSARR());
+}
+
+// Checks if user-contexts are parsed properly.
+TEST_F(Dhcpv6SrvTest, userContext) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // This config has one subnet with user-context with one
+ // pool (also with context). Make sure the configuration could be accepted.
+ EXPECT_NO_THROW(configure(CONFIGS[4]));
+
+ // Now make sure the data was not lost.
+ ConstSrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+ const Subnet6Collection* subnets = cfg->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Let's get the subnet and check its context.
+ Subnet6Ptr subnet1 = (*subnets->begin());
+ ASSERT_TRUE(subnet1);
+ ASSERT_TRUE(subnet1->getContext());
+ EXPECT_EQ("{ \"secure\": false }", subnet1->getContext()->str());
+
+ // Ok, not get the address pool in it and check its context, too.
+ PoolCollection pools = subnet1->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools.size());
+ ASSERT_TRUE(pools[0]);
+ ASSERT_TRUE(pools[0]->getContext());
+ EXPECT_EQ("{ \"value\": 42 }", pools[0]->getContext()->str());
+
+ // Ok, not get the prefix pool in it and check its context, too.
+ pools = subnet1->getPools(Lease::TYPE_PD);
+ ASSERT_EQ(1, pools.size());
+ ASSERT_TRUE(pools[0]);
+ ASSERT_TRUE(pools[0]->getContext());
+ EXPECT_EQ("{ \"type\": \"prefixes\" }", pools[0]->getContext()->str());
+}
+
+// Verifies that the server will still process a packet which fails to
+// parse with a SkipRemainingOptions exception
+TEST_F(Dhcpv6SrvTest, truncatedVIVSO) {
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a SOLICIT with a VIVSO whose length is too short
+ Pkt6Ptr sol = PktCaptures::captureSolicitWithTruncatedVIVSO();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // Make sure we got an Advertise...
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr adv = srv.fake_sent_.front();
+ ASSERT_TRUE(adv);
+}
+
+// Check that T1 and T2 values are set correctly.
+TEST_F(Dhcpv6SrvTest, calculateTeeTimers) {
+ NakedDhcpv6Srv srv(0);
+
+ // Struct for describing an individual timer test scenario
+ struct TimerTest {
+ // logged test description
+ std::string description_;
+ // configured value for subnet's T1
+ Triplet<uint32_t> cfg_t1_;
+ // configured value for subnet's T1
+ Triplet<uint32_t> cfg_t2_;
+ // whether or not calculation is enabled
+ bool calculate_tee_times;
+ // configured value for subnet's t1_percent.
+ double t1_percent_;
+ // configured value for subnet's t2_percent.
+ double t2_percent_;
+ // expected value for T1 in server response.
+ // A value of 0 means server should not have sent T1.
+ uint32_t t1_exp_value_;
+ // expected value for T2 in server response.
+ // A value of 0 means server should not have sent T2.
+ uint32_t t2_exp_value_;
+ };
+
+ // Handy constants
+ Triplet<uint32_t> unspecified;
+ Triplet<uint32_t> preferred_lft(1000);
+ bool calculate_enabled = true;
+
+ // Test scenarios
+ std::vector<TimerTest> tests = {
+ // Tests with calculation disabled
+ {
+ "T1 and T2 calculated",
+ unspecified, unspecified,
+ calculate_enabled,
+ 0.4, 0.8,
+ 400, 800
+ },
+ {
+ "preferred < T1 specified < T2 specified",
+ preferred_lft + 1, preferred_lft + 2,
+ calculate_enabled,
+ 0.4, 0.8,
+ preferred_lft + 1, preferred_lft + 2
+ },
+ {
+ "T1 should be calculated, T2 specified",
+ unspecified, preferred_lft - 1,
+ calculate_enabled,
+ 0.4, 0.8,
+ 400, preferred_lft - 1
+ },
+ {
+ "T1 specified, T2 should be calculated",
+ 299, unspecified,
+ calculate_enabled,
+ 0.4, 0.8,
+ 299, 800
+ },
+ {
+ "T1 specified insane (> T2), T2 should be calculated",
+ preferred_lft - 1, unspecified,
+ calculate_enabled,
+ 0.4, 0.8,
+ 0, 800
+ },
+ // Tests with calculation disabled
+ {
+ "T1 and T2 unspecified, (no calculation)",
+ unspecified, unspecified,
+ !calculate_enabled,
+ 0.4, 0.8,
+ 0, 0
+ },
+ {
+ // cannot set T1 > 0 when T2 is 0
+ "T1 specified, T2 unspecified (no calculation)",
+ preferred_lft - 1, unspecified,
+ !calculate_enabled,
+ 0.4, 0.8,
+ //preferred_lft - 1, 0
+ 0, 0
+ },
+ {
+ "both T1 and T2 specified sane (no calculation)",
+ preferred_lft - 2, preferred_lft - 1,
+ !calculate_enabled,
+ 0.4, 0.8,
+ preferred_lft - 2, preferred_lft - 1
+ },
+ {
+ "both T1 and T2 specified equal to preferred",
+ preferred_lft, preferred_lft,
+ !calculate_enabled,
+ 0.4, 0.8,
+ // T1 must be less than T2
+ 0, preferred_lft
+ },
+ {
+ "T1 specified insane (> lease T2), T2 specified (no calculation)",
+ preferred_lft - 1, preferred_lft - 2,
+ !calculate_enabled,
+ 0.4, 0.8,
+ 0, preferred_lft - 2
+ },
+ {
+ "T1 specified insane (> lease time), T2 not specified (no calculation)",
+ preferred_lft + 1, unspecified,
+ !calculate_enabled,
+ 0.4, 0.8,
+ 0, 0
+ }
+ };
+
+ // Calculation is enabled for all the scenarios.
+ subnet_->setPreferred(preferred_lft);
+
+ // Create a discover packet to use
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+
+ // Iterate over the test scenarios.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE((*test).description_);
+ // Configure subnet for the scenario
+ subnet_->setT1((*test).cfg_t1_);
+ subnet_->setT2((*test).cfg_t2_);
+ subnet_->setCalculateTeeTimes((*test).calculate_tee_times);
+ subnet_->setT1Percent((*test).t1_percent_);
+ subnet_->setT2Percent((*test).t2_percent_);
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr reply = srv.processSolicit(ctx);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and T1 and T2 are correct.
+ checkIA_NA(reply, 234, (*test).t1_exp_value_, (*test).t2_exp_value_);
+ }
+ }
+}
+
+/// @brief Check that example files from documentation are valid (can be parsed
+/// and loaded).
+TEST_F(Dhcpv6SrvTest, checkConfigFiles) {
+ checkConfigFiles();
+}
+
+/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
+/// to call processX() methods.
+
+/// @todo: Implement proper tests for MySQL lease/host database,
+/// see ticket #4214.
+
+} // namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
new file mode 100644
index 0000000..212a4d7
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
@@ -0,0 +1,1194 @@
+// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <gtest/gtest.h>
+#include <cc/command_interpreter.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcpsrv/cfg_multi_threading.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/json_config_parser.h>
+#include <log/logger_support.h>
+#include <stats/stats_mgr.h>
+#include <util/pointer_util.h>
+#include <cstdio>
+#include <sstream>
+#include <string.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::stats;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+const char* BaseServerTest::DUID_FILE = "kea-dhcp6-serverid";
+
+BaseServerTest::BaseServerTest()
+ : original_datadir_(CfgMgr::instance().getDataDir()) {
+ CfgMgr::instance().setDataDir(TEST_DATA_BUILDDIR);
+}
+
+BaseServerTest::~BaseServerTest() {
+ // Remove test DUID file.
+ std::ostringstream s;
+ s << CfgMgr::instance().getDataDir() << "/" << DUID_FILE;
+ static_cast<void>(::remove(s.str().c_str()));
+
+ // Remove default lease file.
+ std::ostringstream s2;
+ s2 << CfgMgr::instance().getDataDir() << "/kea-leases6.csv";
+ static_cast<void>(::remove(s2.str().c_str()));
+
+ // Revert to original data directory.
+ CfgMgr::instance().setDataDir(original_datadir_);
+
+ // Revert to unit test logging in case the test reconfigured logging.
+ isc::log::initLogger();
+}
+
+Dhcpv6SrvTest::Dhcpv6SrvTest()
+ : NakedDhcpv6SrvTest(), srv_(0), multi_threading_(false) {
+ subnet_ = Subnet6::create(isc::asiolink::IOAddress("2001:db8:1::"),
+ 48, 1000, 2000, 3000, 4000, SubnetID(1));
+ subnet_->setIface("eth0");
+
+ pool_ = isc::dhcp::Pool6Ptr(new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_NA,
+ isc::asiolink::IOAddress("2001:db8:1:1::"),
+ 64));
+ subnet_->addPool(pool_);
+
+ isc::dhcp::CfgMgr::instance().clear();
+ CfgMgr::instance().setFamily(AF_INET6);
+ isc::dhcp::CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ isc::dhcp::CfgMgr::instance().commit();
+
+ // configure PD pool
+ pd_pool_ = isc::dhcp::Pool6Ptr(new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_PD,
+ isc::asiolink::IOAddress("2001:db8:1:2::"),
+ 64, 80));
+ subnet_->addPool(pd_pool_);
+
+ // Reset the thread pool.
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+}
+
+Dhcpv6SrvTest::~Dhcpv6SrvTest() {
+ isc::dhcp::CfgMgr::instance().clear();
+
+ // Reset the thread pool.
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+};
+
+// Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
+// It returns IAADDR option for each chaining with checkIAAddr method.
+boost::shared_ptr<Option6IAAddr>
+Dhcpv6SrvTest::checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2) {
+ OptionPtr tmp = rsp->getOption(D6O_IA_NA);
+ // Can't use ASSERT_TRUE() in method that returns something
+ if (!tmp) {
+ ADD_FAILURE() << "IA_NA option not present in response";
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ if (!ia) {
+ ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ if (expected_iaid != ia->getIAID()) {
+ ADD_FAILURE() << "ia->iaid: " << ia->getIAID()
+ << " is not expected value: " << expected_iaid;
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ if (expected_t1 != ia->getT1()) {
+ ADD_FAILURE() << "ia->t1: " << ia->getT1()
+ << " is not expected value: " << expected_t1;
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ if (expected_t2 != ia->getT2()) {
+ ADD_FAILURE() << "ia->t2: " << ia->getT2()
+ << " is not expected value: " << expected_t2;
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ tmp = ia->getOption(D6O_IAADDR);
+ boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+ return (addr);
+}
+
+boost::shared_ptr<Option6IAPrefix>
+Dhcpv6SrvTest::checkIA_PD(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2) {
+ OptionPtr tmp = rsp->getOption(D6O_IA_PD);
+ // Can't use ASSERT_TRUE() in method that returns something
+ if (!tmp) {
+ ADD_FAILURE() << "IA_PD option not present in response";
+ return (boost::shared_ptr<Option6IAPrefix>());
+ }
+
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ if (!ia) {
+ ADD_FAILURE() << "IA_PD cannot convert option ptr to Option6";
+ return (boost::shared_ptr<Option6IAPrefix>());
+ }
+
+ EXPECT_EQ(expected_iaid, ia->getIAID());
+ EXPECT_EQ(expected_t1, ia->getT1());
+ EXPECT_EQ(expected_t2, ia->getT2());
+
+ tmp = ia->getOption(D6O_IAPREFIX);
+ boost::shared_ptr<Option6IAPrefix> addr = boost::dynamic_pointer_cast<Option6IAPrefix>(tmp);
+ return (addr);
+}
+
+// Checks if the lease sent to client is present in the database
+// and is valid when checked against the configured subnet
+Lease6Ptr
+Dhcpv6SrvTest::checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
+ boost::shared_ptr<Option6IAAddr> addr) {
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr->getAddress());
+ if (!lease) {
+ std::cout << "Lease for " << addr->getAddress()
+ << " not found in the database backend.";
+ return (Lease6Ptr());
+ }
+
+ EXPECT_EQ(addr->getAddress(), lease->addr_);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_EQ(ia->getIAID(), lease->iaid_);
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+}
+
+isc::dhcp::Lease6Ptr
+Dhcpv6SrvTest::checkLease(const isc::dhcp::Lease6& lease) {
+ Lease6Ptr lease_db = LeaseMgrFactory::instance().getLease6(lease.type_,
+ lease.addr_);
+ if (!lease_db) {
+ return (Lease6Ptr());
+ }
+
+ EXPECT_TRUE(util::nullOrEqualValues(lease_db->hwaddr_, lease.hwaddr_));
+ EXPECT_TRUE(util::nullOrEqualValues(lease_db->duid_, lease.duid_));
+
+ return (lease_db);
+}
+
+Lease6Ptr
+Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
+ boost::shared_ptr<Option6IAPrefix> prefix){
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_pd);
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
+ prefix->getAddress());
+ if (!lease) {
+ std::cout << "PD lease for " << prefix->getAddress()
+ << " not found in the database backend.";
+ return (Lease6Ptr());
+ }
+
+ EXPECT_EQ(prefix->getAddress(), lease->addr_);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_EQ(ia->getIAID(), lease->iaid_);
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+}
+
+Pkt6Ptr
+Dhcpv6SrvTest::createMessage(uint8_t message_type, Lease::Type lease_type,
+ const IOAddress& addr, const uint8_t prefix_len,
+ const uint32_t iaid) {
+ Pkt6Ptr msg = Pkt6Ptr(new Pkt6(message_type, 1234));
+ msg->setRemoteAddr(IOAddress("fe80::abcd"));
+ msg->setIface("eth0");
+ msg->setIndex(ETH0_INDEX);
+ msg->addOption(createIA(lease_type, addr, prefix_len, iaid));
+ return (msg);
+}
+
+Option6IAPtr
+Dhcpv6SrvTest::createIA(isc::dhcp::Lease::Type lease_type,
+ const isc::asiolink::IOAddress& addr,
+ const uint8_t prefix_len, const uint32_t iaid) {
+ uint16_t code;
+ OptionPtr subopt;
+ switch (lease_type) {
+ case Lease::TYPE_NA:
+ code = D6O_IA_NA;
+ subopt.reset(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ break;
+ case Lease::TYPE_PD:
+ code = D6O_IA_PD;
+ subopt.reset(new Option6IAPrefix(D6O_IAPREFIX, addr, prefix_len,
+ 300, 500));
+ break;
+ default:
+ isc_throw(BadValue, "Invalid lease type specified "
+ << static_cast<int>(lease_type));
+ }
+
+ Option6IAPtr ia = generateIA(code, iaid, 1500, 3000);
+ ia->addOption(subopt);
+
+ return (ia);
+}
+
+void
+Dhcpv6SrvTest::testRenewBasic(Lease::Type type,
+ const std::string& existing_addr,
+ const std::string& renew_addr,
+ const uint8_t prefix_len,
+ bool insert_before_renew,
+ bool expire_before_renew,
+ uint32_t hint_pref,
+ uint32_t hint_valid,
+ uint32_t expected_pref,
+ uint32_t expected_valid) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress existing(existing_addr);
+ const IOAddress renew(renew_addr);
+ const uint32_t iaid = 234;
+
+ // To reuse an expired lease we need a subnet with a pool that
+ // consists of exactly one address. This address will get expired
+ // and then be reused.
+ if (expire_before_renew) {
+ CfgMgr::instance().clear();
+ subnet_ = Subnet6::create(IOAddress("2001:db8:1:1::"), 48,
+ 1000, 2000, 3000, 4000, subnet_->getID());
+ subnet_->setIface("eth0");
+ pool_.reset(new Pool6(Lease::TYPE_NA, existing, existing));
+ subnet_->addPool(pool_);
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ CfgMgr::instance().commit();
+ }
+
+ // Use intervals for lifetimes for lifetime tests.
+ if (hint_pref != 300 || hint_valid != 500) {
+ subnet_->setPreferred(Triplet<uint32_t>(2000, 3000, 4000));
+ subnet_->setValid(Triplet<uint32_t>(3000, 4000, 5000));
+ }
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(type, existing));
+
+ Lease6Ptr l;
+ if (insert_before_renew) {
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(type, existing, duid_, iaid, 501, 502,
+ subnet_->getID(), HWAddrPtr(), prefix_len));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ l = LeaseMgrFactory::instance().getLease6(type, existing);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+ }
+
+ if (expire_before_renew) {
+ // The lease must exist.
+ ASSERT_TRUE(l);
+
+ // Change the subnet identifier to make the allocation engine to
+ // not treat the lease as being renewed by the same client,
+ // but to treat it as expired lease to be reused.
+ ++l->subnet_id_;
+
+ // Move the cllt back in time and make sure that the lease got expired.
+ l->cltt_ = time(NULL) - 10;
+ l->valid_lft_ = 5;
+ ASSERT_TRUE(l->expired());
+ // Update the lease in the lease database.
+ LeaseMgrFactory::instance().updateLease6(l);
+ }
+
+ Pkt6Ptr req;
+ uint8_t message_type = DHCPV6_RENEW;
+ // Use a request vs a renew for getting an expired lease without
+ // extending it. i.e. not call extendLease6 after reuseExpiredLease.
+ if (expire_before_renew) {
+ message_type = DHCPV6_REQUEST;
+ }
+
+ if (hint_pref == 300 && hint_valid == 500) {
+ req = createMessage(message_type, type, IOAddress(renew_addr),
+ prefix_len, iaid);
+ } else {
+ // from createMessage
+ req.reset(new Pkt6(message_type, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+
+ // from createIA
+ uint16_t code;
+ OptionPtr subopt;
+ switch (type) {
+ case Lease::TYPE_NA:
+ code = D6O_IA_NA;
+ subopt.reset(new Option6IAAddr(D6O_IAADDR,
+ IOAddress(renew_addr),
+ hint_pref, hint_valid));
+ break;
+ case Lease::TYPE_PD:
+ code = D6O_IA_PD;
+ subopt.reset(new Option6IAPrefix(D6O_IAPREFIX,
+ IOAddress(renew_addr), prefix_len,
+ hint_pref, hint_valid));
+ break;
+ default:
+ isc_throw(BadValue, "Invalid lease type specified "
+ << static_cast<int>(type));
+ }
+
+ Option6IAPtr ia = generateIA(code, iaid, 1500, 3000);
+ ia->addOption(subopt);
+ req->addOption(ia);
+ };
+ req->addOption(clientid);
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply;
+ if (!expire_before_renew) {
+ reply = srv.processRenew(req);
+ } else {
+ reply = srv.processRequest(req);
+ }
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ switch (type) {
+ case Lease::TYPE_NA: {
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr>
+ addr_opt = checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+
+ ASSERT_TRUE(addr_opt);
+
+ // Check that we've got the address we requested
+ checkIAAddr(addr_opt, renew, Lease::TYPE_NA,
+ expected_pref, expected_valid);
+
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+ break;
+ }
+
+ case Lease::TYPE_PD: {
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAPrefix> prefix_opt
+ = checkIA_PD(reply, 234, subnet_->getT1(), subnet_->getT2());
+
+ ASSERT_TRUE(prefix_opt);
+
+ // Check that we've got the address we requested
+ checkIAAddr(prefix_opt, renew, Lease::TYPE_PD,
+ expected_pref, expected_valid);
+ EXPECT_EQ(pd_pool_->getLength(), prefix_opt->getLength());
+
+ // Check that the lease is really in the database
+ l = checkPdLease(duid_, reply->getOption(D6O_IA_PD), prefix_opt);
+ ASSERT_TRUE(l);
+ break;
+ }
+ default:
+ isc_throw(BadValue, "Invalid lease type");
+ }
+
+ // Check that preferred, valid and cltt were really updated
+ EXPECT_EQ(expected_pref ? expected_pref : subnet_->getPreferred().get(),
+ l->preferred_lft_);
+ EXPECT_EQ(expected_valid ? expected_valid : subnet_->getValid().get(),
+ l->valid_lft_);
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(type,
+ IOAddress(renew_addr));
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+void
+Dhcpv6SrvTest::testRenewWrongIAID(Lease::Type type, const IOAddress& addr) {
+
+ NakedDhcpv6Srv srv(0);
+
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ uint8_t prefix_len = (type != Lease::TYPE_PD) ? 128 : pd_pool_->getLength();
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(type, addr));
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(type, addr);
+ ASSERT_FALSE(l);
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(type, addr, duid_, valid_iaid,
+ 501, 502, subnet_->getID(),
+ HWAddrPtr(), prefix_len));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Pass it to the server and hope for a REPLY
+ // Let's create a RENEW
+ Pkt6Ptr renew = createMessage(DHCPV6_RENEW, type, IOAddress(addr), prefix_len,
+ bogus_iaid);
+ renew->addOption(clientid);
+ renew->addOption(srv.getServerID());
+
+ // The duid and address matches, but the iaid is different. The server could
+ // respond with NoBinding. However, according to
+ // draft-ietf-dhc-dhcpv6-stateful-issues-10, the server can also assign a
+ // new address. And that's what we expect here.
+ Pkt6Ptr reply = srv.processRenew(renew);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr>
+ addr_opt = checkIA_NA(reply, bogus_iaid, subnet_->getT1(), subnet_->getT2());
+
+ ASSERT_TRUE(addr_opt);
+
+ // Check that we've got the an address
+ checkIAAddr(addr_opt, addr_opt->getAddress(), Lease::TYPE_NA);
+
+ // Check that we got a different address than was in the database.
+ EXPECT_NE(addr_opt->getAddress().toText(), addr.toText());
+
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+}
+
+void
+Dhcpv6SrvTest::testRenewSomeoneElsesLease(Lease::Type type, const IOAddress& addr) {
+
+ NakedDhcpv6Srv srv(0);
+ const uint32_t valid_iaid = 234;
+ const uint32_t transid = 1234;
+
+ uint8_t prefix_len = (type != Lease::TYPE_PD) ? 128 : pd_pool_->getLength();
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Let's create a lease.
+ Lease6Ptr lease(new Lease6(type, addr, duid_, valid_iaid,
+ 501, 502, subnet_->getID(),
+ HWAddrPtr(), prefix_len));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // CASE 3: Lease belongs to a client with different client-id
+ Pkt6Ptr renew = createMessage(DHCPV6_RENEW, type, IOAddress(addr), prefix_len,
+ valid_iaid);
+ renew->addOption(generateClientId(13)); // generate different DUID (length 13)
+ renew->addOption(srv.getServerID());
+
+ // The iaid and address matches, but the duid is different.
+ // The server should not renew it, but assign something else.
+ Pkt6Ptr reply = srv.processRenew(renew);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_?? was returned and that there's proper status code
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr>
+ addr_opt = checkIA_NA(reply, valid_iaid, subnet_->getT1(), subnet_->getT2());
+
+ ASSERT_TRUE(addr_opt);
+
+ // Check that we've got the an address
+ checkIAAddr(addr_opt, addr_opt->getAddress(), Lease::TYPE_NA);
+
+ // Check that we got a different address than was in the database.
+ EXPECT_NE(addr_opt->getAddress().toText(), addr.toText());
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+}
+
+void
+Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
+ const IOAddress& release_addr,
+ const LeaseAffinity lease_affinity) {
+ if (lease_affinity == LEASE_AFFINITY_DISABLED) {
+ auto expiration_cfg = CfgMgr::instance().getCurrentCfg()->getCfgExpiration();
+ expiration_cfg->setFlushReclaimedTimerWaitTime(0);
+ expiration_cfg->setHoldReclaimedTime(0);
+ }
+ NakedDhcpv6Srv srv(0);
+
+ const uint32_t iaid = 234;
+
+ uint32_t code; // option code of the container (IA_NA or IA_PD)
+ uint8_t prefix_len;
+ if (type == Lease::TYPE_NA) {
+ code = D6O_IA_NA;
+ prefix_len = 128;
+ } else if (type == Lease::TYPE_PD) {
+ code = D6O_IA_PD;
+ prefix_len = pd_pool_->getLength();
+ } else {
+ isc_throw(BadValue, "Invalid lease type");
+ }
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(type, existing));
+
+ // Let's prepopulate the database
+ Lease6Ptr lease(new Lease6(type, existing, duid_, iaid,
+ 501, 502, subnet_->getID(),
+ HWAddrPtr(), prefix_len));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(type, existing);
+ ASSERT_TRUE(l);
+
+ // And prepopulate the stats counter
+ std::string name = StatsMgr::generateName("subnet", subnet_->getID(),
+ type == Lease::TYPE_NA ? "assigned-nas" :
+ "assigned-pds");
+ StatsMgr::instance().setValue(name, static_cast<int64_t>(1));
+
+ ObservationPtr stat = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(stat);
+ uint64_t before = stat->getInteger().first;
+
+ // Let's create a RELEASE
+ Pkt6Ptr rel = createMessage(DHCPV6_RELEASE, type, release_addr, prefix_len,
+ iaid);
+ rel->addOption(clientid);
+ rel->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(rel);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(code);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ checkIA_NAStatusCode(ia, STATUS_Success, 0, 0);
+ checkMsgStatusCode(reply, STATUS_Success);
+
+ // There should be no address returned in RELEASE (see RFC 8415, 18.3.7)
+ // There should be no prefix
+ EXPECT_FALSE(tmp->getOption(D6O_IAADDR));
+ EXPECT_FALSE(tmp->getOption(D6O_IAPREFIX));
+
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ if (lease_affinity == LEASE_AFFINITY_DISABLED) {
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(type, release_addr);
+ ASSERT_FALSE(l);
+
+ // get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(type, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_FALSE(l);
+
+ // We should have decremented the address counter
+ stat = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(stat);
+ EXPECT_EQ(0, stat->getInteger().first);
+ } else {
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(type, release_addr);
+ ASSERT_TRUE(l);
+
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+
+ // get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(type, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_TRUE(l);
+
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+
+ // We should have decremented the address counter
+ stat = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(stat);
+ EXPECT_EQ(before, stat->getInteger().first);
+ }
+}
+
+void
+Dhcpv6SrvTest::testReleaseNoDelete(Lease::Type type, const IOAddress& addr,
+ uint8_t qtype) {
+ NakedDhcpv6Srv srv(0);
+
+ const uint32_t iaid = 234;
+
+ uint8_t prefix_len = (type == Lease::TYPE_NA ? 128 : pd_pool_->getLength());
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(type, addr));
+
+ // Let's prepopulate the database
+ Lease6Ptr lease(new Lease6(type, addr, duid_, iaid,
+ 501, 502, subnet_->getID(),
+ HWAddrPtr(), prefix_len));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(type, addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr rel = createMessage(DHCPV6_RELEASE, type, addr, prefix_len, iaid);
+ rel->addOption(clientid);
+ rel->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(rel);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+ checkMsgStatusCode(reply, STATUS_Success);
+
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ // Check lease
+ l = LeaseMgrFactory::instance().getLease6(type, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_TRUE(l);
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+
+ // Create query
+ Pkt6Ptr query;
+ if (qtype != DHCPV6_SOLICIT) {
+ query = createMessage(qtype, type, addr, prefix_len, iaid);
+ query->addOption(srv.getServerID());
+ } else {
+ query = createMessage(qtype, type, IOAddress::IPV6_ZERO_ADDRESS(),
+ prefix_len, iaid);
+ }
+ query->addOption(clientid);
+
+ // Process query
+ switch (qtype) {
+ case DHCPV6_SOLICIT:
+ reply = srv.processSolicit(query);
+ break;
+ case DHCPV6_REQUEST:
+ reply = srv.processRequest(query);
+ break;
+ case DHCPV6_RENEW:
+ reply = srv.processRenew(query);
+ break;
+ case DHCPV6_REBIND:
+ reply = srv.processRebind(query);
+ break;
+ default:
+ reply.reset();
+ break;
+ }
+
+ // Check reply
+ if (qtype == DHCPV6_SOLICIT) {
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+ } else {
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+ }
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+ checkMsgStatusCode(reply, STATUS_Success);
+ if (type == Lease::TYPE_NA) {
+ Option6IAAddrPtr iaaddr = checkIA_NA(reply, iaid, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(iaaddr);
+ checkIAAddr(iaaddr, addr, type, subnet_->getPreferred(),
+ subnet_->getValid());
+ } else {
+ Option6IAPrefixPtr iapref = checkIA_PD(reply, iaid, subnet_->getT1(),
+ subnet_->getT2());
+ ASSERT_TRUE(iapref);
+ checkIAAddr(iapref, addr, type, subnet_->getPreferred(),
+ subnet_->getValid());
+ }
+
+ // Check lease
+ l = LeaseMgrFactory::instance().getLease6(type, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_TRUE(l);
+ if (qtype == DHCPV6_SOLICIT) {
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+ } else {
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+ EXPECT_EQ(l->preferred_lft_, subnet_->getPreferred());
+ }
+}
+
+void
+Dhcpv6SrvTest::testReleaseReject(Lease::Type type, const IOAddress& addr) {
+ NakedDhcpv6Srv srv(0);
+
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ uint32_t code; // option code of the container (IA_NA or IA_PD)
+ uint8_t prefix_len;
+ if (type == Lease::TYPE_NA) {
+ code = D6O_IA_NA;
+ prefix_len = 128;
+ } else if (type == Lease::TYPE_PD) {
+ code = D6O_IA_PD;
+ prefix_len = pd_pool_->getLength();
+ } else {
+ isc_throw(BadValue, "Invalid lease type");
+ }
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(type, addr));
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Pretend we have allocated 1 lease
+ std::string name = StatsMgr::generateName("subnet", subnet_->getID(),
+ type == Lease::TYPE_NA ? "assigned-nas" :
+ "assigned-pds");
+ StatsMgr::instance().setValue(name, static_cast<int64_t>(1));
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(type, addr);
+ ASSERT_FALSE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr rel = createMessage(DHCPV6_RELEASE, type, addr, prefix_len, valid_iaid);
+ rel->addOption(clientid);
+ rel->addOption(srv.getServerID());
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: No lease known to server");
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(rel);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(code);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA/IA_PD was returned and that there's status code in it
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding, 0, 0);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease6(type, addr);
+ ASSERT_FALSE(l);
+
+ // Verify we didn't decrement the stats counter
+ ObservationPtr stat = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(stat);
+ EXPECT_EQ(1, stat->getInteger().first);
+
+ // CASE 2: Lease is known and belongs to this client, but to a different IAID
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but to a different IAID");
+
+ Lease6Ptr lease(new Lease6(type, addr, duid_, valid_iaid, 501, 502,
+ subnet_->getID(), HWAddrPtr(), prefix_len));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Let's create a different RELEASE, with a bogus iaid
+ rel = createMessage(DHCPV6_RELEASE, type, addr, prefix_len, bogus_iaid);
+ rel->addOption(clientid);
+ rel->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ reply = srv.processRelease(rel);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(code);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_?? was returned and that there's proper status code
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding, 0, 0);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(type, addr);
+ ASSERT_TRUE(l);
+
+ // Verify we didn't decrement the stats counter
+ EXPECT_EQ(1, stat->getInteger().first);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ rel->delOption(D6O_CLIENTID);
+ ia = boost::dynamic_pointer_cast<Option6IA>(rel->getOption(code));
+ ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+ rel->addOption(generateClientId(13)); // generate different DUID
+ // (with length 13)
+
+ reply = srv.processRelease(rel);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(code);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_?? was returned and that there's proper status code
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding, 0, 0);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(type, addr);
+ ASSERT_TRUE(l);
+
+ // Verify we didn't decrement the stats counter
+ EXPECT_EQ(1, stat->getInteger().first);
+
+ // Finally, let's cleanup the database
+ lease = LeaseMgrFactory::instance().getLease6(type, addr);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+}
+
+void
+Dhcpv6SrvTest::testReceiveStats(uint8_t pkt_type, const std::string& stat_name) {
+
+ StatsMgr& mgr = StatsMgr::instance();
+ NakedDhcpv6Srv srv(0);
+
+ // Let's get a simple SOLICIT...
+ Pkt6Ptr pkt = PktCaptures::captureSimpleSolicit();
+
+ // And pretend it's packet of a different type
+ pkt->data_[0] = pkt_type;
+
+ // Check that the tested statistics is initially set to 0
+ ObservationPtr pkt6_rcvd = mgr.getObservation("pkt6-received");
+ ObservationPtr tested_stat = mgr.getObservation(stat_name);
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(tested_stat);
+ EXPECT_EQ(0, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(0, tested_stat->getInteger().first);
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(pkt);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // All expected statistics must be present.
+ pkt6_rcvd = mgr.getObservation("pkt6-received");
+ tested_stat = mgr.getObservation(stat_name);
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(tested_stat);
+
+ // They also must have expected values.
+ EXPECT_EQ(1, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(1, tested_stat->getInteger().first);
+}
+
+ConstElementPtr
+Dhcpv6SrvTest::configure(Dhcpv6Srv& server, ConstElementPtr config) {
+ ConstElementPtr const status(configureDhcp6Server(server, config));
+
+ // Simulate the application of MT config such as in ControlledDhcpvXSrv::processConfig().
+ CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
+
+ return status;
+}
+
+void
+Dhcpv6SrvTest::configure(const std::string& config,
+ const bool commit,
+ const bool open_sockets,
+ const bool create_managers,
+ const bool test,
+ const LeaseAffinity lease_affinity) {
+ configure(config, srv_, commit, open_sockets, create_managers, test,
+ lease_affinity);
+}
+
+void
+Dhcpv6SrvTest::configure(const std::string& config,
+ NakedDhcpv6Srv& srv,
+ const bool commit,
+ const bool open_sockets,
+ const bool create_managers,
+ const bool test,
+ const LeaseAffinity lease_affinity) {
+ setenv("KEA_LFC_EXECUTABLE", KEA_LFC_EXECUTABLE, 1);
+ MultiThreadingCriticalSection cs;
+ ConstElementPtr json;
+ try {
+ json = parseJSON(config);
+ } catch (const std::exception& ex) {
+ // Fatal failure on parsing error
+ FAIL() << "parsing failure:"
+ << "config:" << config << std::endl
+ << "error: " << ex.what();
+ }
+
+ ConstElementPtr status;
+
+ // Disable the re-detect flag
+ disableIfacesReDetect(json);
+
+ // Set up multi-threading
+ configureMultiThreading(multi_threading_, json);
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv, json, test));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = isc::config::parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode) << "configuration failed, test is broken: "
+ << comment->str();
+
+ // Use specified lease database backend.
+ if (create_managers) {
+ ASSERT_NO_THROW( {
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
+ cfg_db->setAppendedParameters("universe=6");
+ cfg_db->createManagers();
+ } );
+ }
+
+ if (lease_affinity == LEASE_AFFINITY_DISABLED) {
+ auto expiration_cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
+ expiration_cfg->setFlushReclaimedTimerWaitTime(0);
+ expiration_cfg->setHoldReclaimedTime(0);
+ }
+
+ try {
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->initAllocatorsAfterConfigure();
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Error initializing the allocators after configure: "
+ << ex.what();
+ }
+
+ try {
+ CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Error applying multi threading settings: "
+ << ex.what();
+ }
+
+ if (commit) {
+ CfgMgr::instance().commit();
+ }
+
+ // Opening sockets.
+ if (open_sockets) {
+ IfaceMgr::instance().openSockets6();
+ }
+}
+
+NakedDhcpv6SrvTest::NakedDhcpv6SrvTest()
+ : rcode_(-1) {
+ // it's ok if that fails. There should not be such a file anyway
+ static_cast<void>(remove(DUID_FILE));
+
+ const IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // There must be some interface detected
+ if (ifaces.empty()) {
+ // We can't use ASSERT in constructor
+ ADD_FAILURE() << "No interfaces detected.";
+ }
+
+ valid_iface_ = (*ifaces.begin())->getName();
+ valid_ifindex_ = (*ifaces.begin())->getIndex();
+
+ // Let's wipe all existing statistics.
+ StatsMgr::instance().removeAll();
+}
+
+NakedDhcpv6SrvTest::~NakedDhcpv6SrvTest() {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+
+ // Let's clean up if there is such a file.
+ static_cast<void>(remove(DUID_FILE));
+ isc::hooks::HooksManager::preCalloutsLibraryHandle()
+ .deregisterAllCallouts("buffer6_receive");
+ isc::hooks::HooksManager::preCalloutsLibraryHandle()
+ .deregisterAllCallouts("buffer6_send");
+ isc::hooks::HooksManager::preCalloutsLibraryHandle()
+ .deregisterAllCallouts("lease6_renew");
+ isc::hooks::HooksManager::preCalloutsLibraryHandle()
+ .deregisterAllCallouts("lease6_release");
+ isc::hooks::HooksManager::preCalloutsLibraryHandle()
+ .deregisterAllCallouts("pkt6_receive");
+ isc::hooks::HooksManager::preCalloutsLibraryHandle()
+ .deregisterAllCallouts("pkt6_send");
+ isc::hooks::HooksManager::preCalloutsLibraryHandle()
+ .deregisterAllCallouts("subnet6_select");
+}
+
+// Generate IA_NA option with specified parameters
+boost::shared_ptr<Option6IA>
+NakedDhcpv6SrvTest::generateIA(uint16_t type, uint32_t iaid, uint32_t t1,
+ uint32_t t2) {
+ boost::shared_ptr<Option6IA> ia =
+ boost::shared_ptr<Option6IA>(new Option6IA(type, iaid));
+ ia->setT1(t1);
+ ia->setT2(t2);
+ return (ia);
+}
+
+bool
+Dhcpv6SrvTest::compareOptions(const isc::dhcp::OptionPtr& option1,
+ const isc::dhcp::OptionPtr& option2) {
+ if ((!option1 && option2) || (option1 && !option2)) {
+ return (false);
+ }
+ if (!option1 && !option2) {
+ return (true);
+ }
+
+ // We could start by comparing option codes and option lengths
+ // here, but it's just a waste of time. These are tests, so they
+ // don't have to be super performant. The pack+memcmp approach
+ // verifies all in one go.
+
+ isc::util::OutputBuffer buf1(0);
+ isc::util::OutputBuffer buf2(0);
+
+ option1->pack(buf1);
+ option2->pack(buf2);
+
+ if (buf1.getLength() != buf2.getLength()) {
+ return (false);
+ }
+
+ // memcmp returns 0 when equal.
+ return (!memcmp(buf1.getData(), buf2.getData(), buf1.getLength()));
+}
+
+void
+NakedDhcpv6SrvTest::checkIA_NAStatusCode(
+ const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
+ uint16_t expected_status_code, uint32_t expected_t1, uint32_t expected_t2,
+ bool check_addr)
+{
+ // Make sure there is no address assigned. Depending on the situation,
+ // the server will either not return the address at all and sometimes
+ // it will return it with zeroed lifetimes.
+ if (check_addr) {
+ dhcp::OptionCollection options = ia->getOptions();
+ for (isc::dhcp::OptionCollection::iterator opt = options.begin();
+ opt != options.end(); ++opt) {
+ if (opt->second->getType() != D6O_IAADDR) {
+ continue;
+ }
+
+ dhcp::Option6IAAddrPtr addr =
+ boost::dynamic_pointer_cast<isc::dhcp::Option6IAAddr>(opt->second);
+ ASSERT_TRUE(addr);
+
+ EXPECT_EQ(0, addr->getPreferred());
+ EXPECT_EQ(0, addr->getValid());
+ }
+ }
+
+ // T1, T2 should NOT be zeroed. draft-ietf-dhc-dhcpv6-stateful-issues-10,
+ // section 4.4.6 says says that T1,T2 should be consistent along all
+ // provided IA options.
+ EXPECT_EQ(expected_t1, ia->getT1());
+ EXPECT_EQ(expected_t2, ia->getT2());
+
+ isc::dhcp::Option6StatusCodePtr status =
+ boost::dynamic_pointer_cast<isc::dhcp::Option6StatusCode>
+ (ia->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default
+ // behavior
+ if (expected_status_code == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+
+ if (status) {
+ // We don't have dedicated class for status code, so let's
+ // just interpret first 2 bytes as status. Remainder of the
+ // status code option content is just a text explanation
+ // what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+ status->getStatusCode());
+ }
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
new file mode 100644
index 0000000..b5c1d7b
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -0,0 +1,1010 @@
+// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file dhcp6_test_utils.h
+///
+/// @brief This file contains utility classes used for DHCPv6 server testing
+
+#ifndef DHCP6_TEST_UTILS_H
+#define DHCP6_TEST_UTILS_H
+
+#include <gtest/gtest.h>
+
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <hooks/hooks_manager.h>
+#include <util/multi_threading_mgr.h>
+
+#include <list>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic wrapper to provide strongly typed values.
+///
+/// In many cases, the test fixture class methods require providing many
+/// parameters, of which some are optional. Some of the parameters may also
+/// be implicitly converted to other types. Non-careful test implementer
+/// may often "shift by one" or swap two values on the arguments list, which
+/// will be accepted by the compiler but will result in troubles running the
+/// function. Sometimes it takes non trivial amount of debugging to find out
+/// why the particular function fails until we find that the arguments were
+/// swapped or shifted. In addition, the use of classes wrapping simple types
+/// results in better readability of the test code.
+///
+/// @tparam ValueType Type of the wrapped value.
+template<typename ValueType>
+struct SpecializedTypeWrapper {
+
+ /// @brief Constructor
+ ///
+ /// @param value Wrapped value
+ explicit SpecializedTypeWrapper(const ValueType& value)
+ : value_(value) { }
+
+ /// @brief Operator returning a wrapped value.
+ operator ValueType () const {
+ return (value_);
+ }
+
+ /// @brief Wrapped value.
+ ValueType value_;
+};
+
+/// @brief Class representing strongly typed IAID.
+struct IAID : public SpecializedTypeWrapper<uint32_t> {
+ /// @brief Constructor
+ ///
+ /// @param iaid IAID.
+ explicit IAID(const uint32_t iaid)
+ : SpecializedTypeWrapper<uint32_t>(iaid) { }
+};
+
+/// @brief Class representing strongly typed value for strict IAID checks.
+///
+/// Strict IAID checks are used to verify that the particular address has been
+/// assign to a specific IA. In many cases we don't check that because it may
+/// not be possible to predict to which IA the specific lease will be assigned.
+struct StrictIAIDChecking : public SpecializedTypeWrapper<bool> {
+ /// @brief Constructor.
+ ///
+ /// @param strict_check Boolean value indicating if strict checking should
+ /// be performed.
+ explicit StrictIAIDChecking(const bool strict_check)
+ : SpecializedTypeWrapper<bool>(strict_check) { }
+
+ /// @brief Convenience function returning an object indicating that strict
+ /// checks should be performed.
+ static const StrictIAIDChecking YES() {
+ static StrictIAIDChecking strict_check(true);
+ return (strict_check);
+ }
+
+ /// @brief Convenience function returning an object indicating that strict
+ /// checks should not be performed.
+ static StrictIAIDChecking NO() {
+ static StrictIAIDChecking strict_check(false);
+ return (strict_check);
+ }
+};
+
+/// @brief Base class for DHCPv6 server testing.
+///
+/// Currently it configures the test data path directory in
+/// the @c CfgMgr. When the object is destroyed, the original
+/// path is reverted.
+class BaseServerTest : public ::testing::Test {
+public:
+
+ /// @brief Location of a test DUID file
+ static const char* DUID_FILE;
+
+ /// @brief Constructor.
+ BaseServerTest();
+
+ /// @brief Destructor.
+ virtual ~BaseServerTest();
+
+private:
+
+ /// @brief Holds the original data directory.
+ std::string original_datadir_;
+};
+
+/// @brief "naked" Dhcpv6Srv class that exposes internal members
+class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
+public:
+ NakedDhcpv6Srv(uint16_t port) : isc::dhcp::Dhcpv6Srv(port) {
+ // Open the "memfile" database for leases
+ std::string memfile = "type=memfile universe=6 persist=false";
+ isc::dhcp::LeaseMgrFactory::create(memfile);
+ LeaseMgr::setIOService(getIOService());
+ }
+
+ /// @brief fakes packet reception
+ /// @param timeout ignored
+ ///
+ /// The method receives all packets queued in receive
+ /// queue, one after another. Once the queue is empty,
+ /// it initiates the shutdown procedure.
+ ///
+ /// See fake_received_ field for description
+ virtual isc::dhcp::Pkt6Ptr receivePacket(int /*timeout*/) {
+ // If there is anything prepared as fake incoming
+ // traffic, use it
+ if (!fake_received_.empty()) {
+ isc::dhcp::Pkt6Ptr pkt = fake_received_.front();
+ fake_received_.pop_front();
+ return (pkt);
+ }
+
+ // Make sure the server processed all packets in MT.
+ isc::util::MultiThreadingMgr::instance().getThreadPool().wait(3);
+
+ // If not, just trigger shutdown and
+ // return immediately
+ shutdown();
+ return (isc::dhcp::Pkt6Ptr());
+ }
+
+ /// @brief fake packet sending
+ ///
+ /// Pretend to send a packet, but instead just store
+ /// it in fake_send_ list where test can later inspect
+ /// server's response.
+ virtual void sendPacket(const isc::dhcp::Pkt6Ptr& pkt) {
+ isc::util::MultiThreadingLock lock(mutex_);
+ fake_sent_.push_back(pkt);
+ }
+
+ /// @brief fake receive packet from server
+ ///
+ /// The client uses this packet as a reply from the server.
+ ///
+ /// @return The received packet.
+ Pkt6Ptr receiveOneMsg() {
+ // Make sure the server processed all packets.
+ isc::util::MultiThreadingMgr::instance().getThreadPool().wait(2);
+
+ // Lock the mutex for the rest of the function.
+ isc::util::MultiThreadingLock lock(mutex_);
+
+ // Return empty pointer if server hasn't responded.
+ if (fake_sent_.empty()) {
+ return (Pkt6Ptr());
+ }
+
+ Pkt6Ptr msg = fake_sent_.front();
+ fake_sent_.pop_front();
+ return (msg);
+ }
+
+ /// @brief adds a packet to fake receive queue
+ ///
+ /// See fake_received_ field for description
+ void fakeReceive(const isc::dhcp::Pkt6Ptr& pkt) {
+ fake_received_.push_back(pkt);
+ }
+
+ virtual ~NakedDhcpv6Srv() {
+ // Close the lease database
+ isc::dhcp::LeaseMgrFactory::destroy();
+ }
+
+ /// @brief Processes incoming Solicit message.
+ ///
+ /// @param request a message received from client
+ /// @return REPLY message or null
+ Pkt6Ptr processSolicit(const Pkt6Ptr& solicit) {
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !earlyGHRLookup(solicit, ctx);
+ initContext(solicit, ctx, drop);
+ if (drop) {
+ return (Pkt6Ptr());
+ }
+ return (processSolicit(ctx));
+ }
+
+ /// @brief Processes incoming Request message.
+ ///
+ /// @param request a message received from client
+ /// @return REPLY message or null
+ Pkt6Ptr processRequest(const Pkt6Ptr& request) {
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !earlyGHRLookup(request, ctx);
+ initContext(request, ctx, drop);
+ if (drop) {
+ return (Pkt6Ptr());
+ }
+ return (processRequest(ctx));
+ }
+
+ /// @brief Processes incoming Renew message.
+ ///
+ /// @param renew a message received from client
+ /// @return REPLY message or null
+ Pkt6Ptr processRenew(const Pkt6Ptr& renew) {
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !earlyGHRLookup(renew, ctx);
+ initContext(renew, ctx, drop);
+ if (drop) {
+ return (Pkt6Ptr());
+ }
+ return (processRenew(ctx));
+ }
+
+ /// @brief Processes incoming Rebind message.
+ ///
+ /// @param rebind a message received from client
+ /// @return REPLY message or null
+ Pkt6Ptr processRebind(const Pkt6Ptr& rebind) {
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !earlyGHRLookup(rebind, ctx);
+ initContext(rebind, ctx, drop);
+ if (drop) {
+ return (Pkt6Ptr());
+ }
+ return (processRebind(ctx));
+ }
+
+ /// @brief Processes incoming Release message.
+ ///
+ /// @param release a message received from client
+ /// @return REPLY message or null
+ Pkt6Ptr processRelease(const Pkt6Ptr& release) {
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !earlyGHRLookup(release, ctx);
+ initContext(release, ctx, drop);
+ if (drop) {
+ return (Pkt6Ptr());
+ }
+ return (processRelease(ctx));
+ }
+
+ /// @brief Processes incoming Decline message.
+ ///
+ /// @param decline a message received from client
+ /// @return REPLY message or null
+ Pkt6Ptr processDecline(const Pkt6Ptr& decline) {
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !earlyGHRLookup(decline, ctx);
+ initContext(decline, ctx, drop);
+ if (drop) {
+ return (Pkt6Ptr());
+ }
+ return (processDecline(ctx));
+ }
+
+ using Dhcpv6Srv::processSolicit;
+ using Dhcpv6Srv::processRequest;
+ using Dhcpv6Srv::processRenew;
+ using Dhcpv6Srv::processRebind;
+ using Dhcpv6Srv::processConfirm;
+ using Dhcpv6Srv::processRelease;
+ using Dhcpv6Srv::processDecline;
+ using Dhcpv6Srv::processInfRequest;
+ using Dhcpv6Srv::processClientFqdn;
+ using Dhcpv6Srv::createNameChangeRequests;
+ using Dhcpv6Srv::selectSubnet;
+ using Dhcpv6Srv::testServerID;
+ using Dhcpv6Srv::testUnicast;
+ using Dhcpv6Srv::sanityCheck;
+ using Dhcpv6Srv::classifyPacket;
+ using Dhcpv6Srv::shutdown_;
+ using Dhcpv6Srv::name_change_reqs_;
+ using Dhcpv6Srv::VENDOR_CLASS_PREFIX;
+ using Dhcpv6Srv::earlyGHRLookup;
+ using Dhcpv6Srv::initContext;
+ using Dhcpv6Srv::server_port_;
+ using Dhcpv6Srv::client_port_;
+
+ /// @brief packets we pretend to receive.
+ ///
+ /// Instead of setting up sockets on interfaces that change between
+ /// OSes, it is much easier to fake packet reception. This is a list
+ /// of packets that we pretend to have received. You can schedule
+ /// new packets to be received using fakeReceive() and
+ /// NakedDhcpv6Srv::receivePacket() methods.
+ std::list<isc::dhcp::Pkt6Ptr> fake_received_;
+
+ /// @brief packets we pretend to send.
+ std::list<isc::dhcp::Pkt6Ptr> fake_sent_;
+
+ /// @brief Mutex to protect the packet buffers.
+ std::mutex mutex_;
+};
+
+/// @brief Test fixture for any tests requiring blank/empty configuration
+/// serves as base class for additional tests
+class NakedDhcpv6SrvTest : public BaseServerTest {
+public:
+
+ /// @brief Constructor
+ NakedDhcpv6SrvTest();
+
+ /// @brief Generate IA_NA or IA_PD option with specified parameters
+ ///
+ /// @param type The option type (usually 4 for IA_NA, 25 for IA_PD)
+ /// @param iaid The identity association identifier (id of IA)
+ /// @param t1 The T1 time
+ /// @param t2 The t2 time
+ /// @return The generated option
+ boost::shared_ptr<isc::dhcp::Option6IA> generateIA(uint16_t type,
+ uint32_t iaid,
+ uint32_t t1,
+ uint32_t t2);
+
+ /// @brief generates interface-id option, based on text
+ ///
+ /// @param iface_id textual representation of the interface-id content
+ ///
+ /// @return pointer to the option object
+ isc::dhcp::OptionPtr generateInterfaceId(const std::string& iface_id) {
+ isc::dhcp::OptionBuffer tmp(iface_id.begin(), iface_id.end());
+ return (isc::dhcp::OptionPtr(new isc::dhcp::Option(isc::dhcp::Option::V6,
+ D6O_INTERFACE_ID, tmp)));
+ }
+
+ /// @brief Generate binary data option
+ ///
+ /// Creates an Option in the V6 space with the given type and binary data
+ /// of the given number of bytes. The data is initialized to the values
+ /// 100 to 100 + n, where n is the desired number of bytes.
+ ///
+ /// @param type option type for the new option
+ /// @param data_size number of bytes of data to generate
+ ///
+ /// @return Pointer to the new option
+ isc::dhcp::OptionPtr generateBinaryOption(const DHCPv6OptionType type,
+ size_t data_size) {
+ if (data_size == 0) {
+ return (isc::dhcp::OptionPtr
+ (new isc::dhcp::Option(isc::dhcp::Option::V6, type)));
+
+ }
+
+ isc::dhcp::OptionBuffer data_data(data_size);
+ for (size_t i = 0; i < data_size; i++) {
+ data_data[i] = 100 + i;
+ }
+
+ return (isc::dhcp::OptionPtr
+ (new isc::dhcp::Option(isc::dhcp::Option::V6, type,
+ data_data.begin(),
+ data_data.begin() + data_size)));
+ }
+
+ // Generate client-id option
+ isc::dhcp::OptionPtr generateClientId(size_t duid_size = 32) {
+ isc::dhcp::OptionPtr opt = generateBinaryOption(D6O_CLIENTID, duid_size);
+ duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(opt->getData()));
+ return (opt);
+ }
+
+ /// Generate server-id option
+ /// @param duid_size size of the duid
+ isc::dhcp::OptionPtr generateServerId(size_t duid_size = 32) {
+ isc::dhcp::OptionPtr opt = generateBinaryOption(D6O_SERVERID, duid_size);
+ duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(opt->getData()));
+ return (opt);
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper
+ // server-id.
+ void checkServerId(const isc::dhcp::Pkt6Ptr& rsp,
+ const isc::dhcp::OptionPtr& expected_srvid) {
+ // check that server included its server-id
+ isc::dhcp::OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+ EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
+ ASSERT_EQ(tmp->len(), expected_srvid->len() );
+ EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper
+ // client-id.
+ void checkClientId(const isc::dhcp::Pkt6Ptr& rsp,
+ const isc::dhcp::OptionPtr& expected_clientid) {
+ // check that server included our own client-id
+ isc::dhcp::OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+ ASSERT_TRUE(tmp);
+ EXPECT_EQ(expected_clientid->getType(), tmp->getType());
+ ASSERT_EQ(expected_clientid->len(), tmp->len());
+
+ // check that returned client-id is valid
+ EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
+ }
+
+ // Checks if server response is a NAK
+ void checkNakResponse(const isc::dhcp::Pkt6Ptr& rsp,
+ uint8_t expected_message_type,
+ uint32_t expected_transid,
+ uint16_t expected_status_code,
+ uint32_t expected_t1, uint32_t expected_t2) {
+ // Check if we get response at all
+ checkResponse(rsp, expected_message_type, expected_transid);
+
+ // Check that IA_NA was returned
+ isc::dhcp::OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
+ ASSERT_TRUE(option_ia_na);
+
+ // check that the status is no address available
+ boost::shared_ptr<isc::dhcp::Option6IA> ia =
+ boost::dynamic_pointer_cast<isc::dhcp::Option6IA>(option_ia_na);
+ ASSERT_TRUE(ia);
+
+ checkIA_NAStatusCode(ia, expected_status_code, expected_t1,
+ expected_t2);
+ }
+
+ /// @brief Checks that the server inserted expected status code in IA_NA
+ ///
+ /// Check that the server used status code as expected, i.e. that it has
+ /// no addresses (if status code is non-zero) and that expected status
+ /// code really appears there. In some limited cases (reply to RELEASE)
+ /// it may be used to verify positive case, where IA_NA response is
+ /// expected to not include address.
+ ///
+ /// Status code indicates type of error encountered (in theory it can also
+ /// indicate success, but servers typically don't send success status
+ /// as this is the default result and it saves bandwidth)
+ /// @param ia IA_NA container to be checked
+ /// @param expected_status_code expected value in status-code option
+ /// @param expected_t1 expected T1 in IA_NA option
+ /// @param expected_t2 expected T2 in IA_NA option
+ /// @param check_addr whether to check for address with 0 lifetimes
+ void checkIA_NAStatusCode(const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
+ uint16_t expected_status_code,
+ uint32_t expected_t1,
+ uint32_t expected_t2,
+ bool check_addr = true);
+
+ void checkMsgStatusCode(const isc::dhcp::Pkt6Ptr& msg,
+ uint16_t expected_status) {
+ isc::dhcp::Option6StatusCodePtr status =
+ boost::dynamic_pointer_cast<isc::dhcp::Option6StatusCode>
+ (msg->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default
+ // behavior
+ if (expected_status == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+ if (status) {
+ // We don't have dedicated class for status code, so let's
+ // just interpret first 2 bytes as status. Remainder of the
+ // status code option content is just a text explanation
+ // what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status),
+ status->getStatusCode());
+ }
+ }
+
+ // Basic checks for generated response (message type and transaction-id).
+ void checkResponse(const isc::dhcp::Pkt6Ptr& rsp,
+ uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ virtual ~NakedDhcpv6SrvTest();
+
+ // A DUID used in most tests (typically as client-id)
+ isc::dhcp::DuidPtr duid_;
+
+ int rcode_;
+ isc::data::ConstElementPtr comment_;
+
+ // Name of a valid network interface
+ std::string valid_iface_;
+
+ // Index of a valid network interface
+ unsigned int valid_ifindex_;
+};
+
+// We need to pass one reference to the Dhcp6Client, which is defined in
+// dhcp6_client.h. That header includes this file. To avoid circular
+// dependencies, we use forward declaration here.
+class Dhcp6Client;
+
+// Provides support for tests against a preconfigured subnet6
+// extends upon NakedDhcp6SrvTest
+class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
+public:
+ /// @brief Specifies expected outcome
+ enum ExpectedResult {
+ SHOULD_PASS, // pass = accept decline, move lease to declined state.
+ SHOULD_FAIL // fail = reject the decline
+ };
+
+ /// @brief Specifies what address should the client include in its Decline
+ enum AddressInclusion {
+ VALID_ADDR, // Client will include its own, valid address
+ BOGUS_ADDR, // Client will include an address it doesn't own
+ NO_ADDR, // Client will send empty IA_NA (without address)
+ NO_IA // Client will not send IA_NA at all
+ };
+
+ /// @brief Specifies if lease affinity is enabled or disabled
+ enum LeaseAffinity {
+ LEASE_AFFINITY_ENABLED,
+ LEASE_AFFINITY_DISABLED
+ };
+
+ class Dhcpv6SrvMTTestGuard {
+ public:
+ Dhcpv6SrvMTTestGuard(Dhcpv6SrvTest& test, bool mt_enabled) : test_(test) {
+ test_.setMultiThreading(mt_enabled);
+ }
+ ~Dhcpv6SrvMTTestGuard() {
+ test_.setMultiThreading(false);
+ }
+ Dhcpv6SrvTest& test_;
+ };
+
+ /// @brief Constructor that initializes a simple default configuration
+ ///
+ /// Sets up a single subnet6 with one pool for addresses and second
+ /// pool for prefixes.
+ Dhcpv6SrvTest();
+
+ /// @brief Destructor
+ ///
+ /// Removes existing configuration.
+ ~Dhcpv6SrvTest();
+
+ /// @brief Used to configure a server for tests.
+ ///
+ /// A wrapper over configureDhcp6Server() to which any other
+ /// simulations of production code are added.
+ ///
+ /// @brief server the server being tested
+ /// @brief config the configuration the server is configured with
+ ///
+ /// @return a JSON-formatted status of the reconfiguration
+ static ConstElementPtr configure(Dhcpv6Srv& server, isc::data::ConstElementPtr config);
+
+ /// @brief Runs DHCPv6 configuration from the JSON string.
+ ///
+ /// @param config String holding server configuration in JSON format.
+ /// @param commit A boolean flag indicating if the new configuration
+ /// should be committed (if true), or not (if false).
+ /// @param open_sockets A boolean flag indicating if sockets should
+ /// be opened (if true), or not (if false).
+ /// @param create_managers A boolean flag indicating if managers should be
+ /// recreated.
+ /// @param test A boolean flag which indicates if only testing config.
+ /// @param lease_affinity A flag which indicates if lease affinity should
+ /// be enabled or disabled.
+ void configure(const std::string& config,
+ const bool commit = true,
+ const bool open_sockets = false,
+ const bool create_managers = true,
+ const bool test = false,
+ const LeaseAffinity lease_affinity = LEASE_AFFINITY_DISABLED);
+
+ /// @brief Configure the DHCPv6 server using the JSON string.
+ ///
+ /// @param config String holding server configuration in JSON format.
+ /// @param srv Server to be configured.
+ /// @param commit A boolean flag indicating if the new configuration
+ /// should be committed (if true), or not (if false).
+ /// @param open_sockets A boolean flag indicating if sockets should
+ /// be opened (if true), or not (if false).
+ /// @param create_managers A boolean flag indicating if managers should be
+ /// recreated.
+ /// @param test A boolean flag which indicates if only testing config.
+ /// @param lease_affinity A flag which indicates if lease affinity should
+ /// be enabled or disabled.
+ void configure(const std::string& config,
+ NakedDhcpv6Srv& srv,
+ const bool commit = true,
+ const bool open_sockets = false,
+ const bool create_managers = true,
+ const bool test = false,
+ const LeaseAffinity lease_affinity = LEASE_AFFINITY_DISABLED);
+
+ /// @brief Checks that server response (ADVERTISE or REPLY) contains proper
+ /// IA_NA option
+ ///
+ /// @param rsp server's response
+ /// @param expected_iaid expected IAID value
+ /// @param expected_t1 expected T1 value
+ /// @param expected_t2 expected T2 value
+ /// @return IAADDR option for easy chaining with checkIAAddr method
+ boost::shared_ptr<isc::dhcp::Option6IAAddr>
+ checkIA_NA(const isc::dhcp::Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2);
+
+ /// @brief Checks that server response (ADVERTISE or REPLY) contains proper
+ /// IA_PD option
+ ///
+ /// @param rsp server's response
+ /// @param expected_iaid expected IAID value
+ /// @param expected_t1 expected T1 value
+ /// @param expected_t2 expected T2 value
+ /// @return IAPREFIX option for easy chaining with checkIAAddr method
+ boost::shared_ptr<isc::dhcp::Option6IAPrefix>
+ checkIA_PD(const isc::dhcp::Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2);
+
+ // Check that generated IAADDR option contains expected address
+ // and lifetime values match the configured subnet
+ /// @param expected_pref check that lease preferedlifetime has the not-zero
+ /// expected value (zero value means that do not check).
+ /// @param expected_valid check that lease valid lifetime has the not-zero
+ /// expected value (zero value means that do not check).
+ void checkIAAddr(const boost::shared_ptr<isc::dhcp::Option6IAAddr>& addr,
+ const isc::asiolink::IOAddress& expected_addr,
+ isc::dhcp::Lease::Type type,
+ uint32_t expected_pref = 0,
+ uint32_t expected_valid = 0) {
+
+ // Check that the assigned address is indeed from the configured pool.
+ // Note that when comparing addresses, we compare the textual
+ // representation. IOAddress does not support being streamed to
+ // an ostream, which means it can't be used in EXPECT_EQ.
+ EXPECT_TRUE(subnet_->inPool(type, addr->getAddress()));
+ EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
+ if (subnet_->getPreferred().getMin() != subnet_->getPreferred().getMax()) {
+ EXPECT_LE(subnet_->getPreferred().getMin(), addr->getPreferred());
+ EXPECT_GE(subnet_->getPreferred().getMax(), addr->getPreferred());
+ } else {
+ EXPECT_EQ(subnet_->getPreferred(), addr->getPreferred());
+ }
+ if (subnet_->getValid().getMin() != subnet_->getValid().getMax()) {
+ EXPECT_LE(subnet_->getValid().getMin(), addr->getValid());
+ EXPECT_GE(subnet_->getValid().getMax(), addr->getValid());
+ } else {
+ EXPECT_EQ(subnet_->getValid(), addr->getValid());
+ }
+ if (expected_pref) {
+ EXPECT_EQ(expected_pref, addr->getPreferred());
+ }
+ if (expected_valid) {
+ EXPECT_EQ(expected_valid, addr->getValid());
+ }
+ }
+
+ // Checks if the lease sent to client is present in the database
+ // and is valid when checked against the configured subnet
+ isc::dhcp::Lease6Ptr
+ checkLease(const isc::dhcp::DuidPtr& duid,
+ const isc::dhcp::OptionPtr& ia_na,
+ boost::shared_ptr<isc::dhcp::Option6IAAddr> addr);
+
+ /// @brief Check if the specified lease is present in the data base.
+ ///
+ /// @param lease Lease to be searched in the database.
+ /// @return Pointer to the lease in the database.
+ isc::dhcp::Lease6Ptr checkLease(const isc::dhcp::Lease6& lease);
+
+ /// @brief Verifies received IAPrefix option
+ ///
+ /// Verifies if the received IAPrefix option matches the lease in the
+ /// database.
+ ///
+ /// @param duid client's DUID
+ /// @param ia_pd IA_PD option that contains the IAPRefix option
+ /// @param prefix pointer to the IAPREFIX option
+ /// @return corresponding IPv6 lease (if found)
+ isc::dhcp::Lease6Ptr
+ checkPdLease(const isc::dhcp::DuidPtr& duid,
+ const isc::dhcp::OptionPtr& ia_pd,
+ boost::shared_ptr<isc::dhcp::Option6IAPrefix> prefix);
+
+ /// @brief Creates a message with specified IA
+ ///
+ /// A utility function that creates a message of the specified type with
+ /// a specified container (IA_NA or IA_PD) and an address or prefix
+ /// inside it.
+ ///
+ /// @param message_type type of the message (e.g. DHCPV6_SOLICIT)
+ /// @param lease_type type of a lease (TYPE_NA or TYPE_PD)
+ /// @param addr address or prefix to use in IADDRESS or IAPREFIX options
+ /// @param prefix_len length of the prefix (used for prefixes only)
+ /// @param iaid IA identifier (used in IA_XX option)
+ /// @return created message
+ isc::dhcp::Pkt6Ptr
+ createMessage(uint8_t message_type, isc::dhcp::Lease::Type lease_type,
+ const isc::asiolink::IOAddress& addr,
+ const uint8_t prefix_len, const uint32_t iaid);
+
+ /// @brief Creates instance of IA option holding single address or prefix.
+ ///
+ /// Utility function that creates an IA option instance with a single
+ /// IPv6 address or prefix. This function is internally called by the
+ /// @c createMessage function. It may be also used to add additional
+ /// IA options to the message generated by @c createMessage (which adds
+ /// a single IA option by itself.).
+ ///
+ /// @param lease_type type of the lease (TYPE_NA or TYPE_PD).
+ /// @param addr address or prefix to use in IADDRESS or IAPREFIX options.
+ /// @param prefix_len length of the prefix (used for PD, ignored for NA).
+ /// @param iaid IA identifier.
+ ///
+ /// @return Created instance of the IA option.
+ isc::dhcp::Option6IAPtr
+ createIA(isc::dhcp::Lease::Type lease_type,
+ const isc::asiolink::IOAddress& addr,
+ const uint8_t prefix_len, const uint32_t iaid);
+
+ /// @brief Compare options
+ ///
+ /// This method compares whether options content is identical. It writes
+ /// both options to a buffer and then compares the buffers. Comparing
+ /// two different instances of an option that has identical content
+ /// will return true.
+ ///
+ /// It is safe to pass null pointers. Two null pointers are equal.
+ /// null pointer and non-null pointers are obviously non-equal.
+ ///
+ /// @param option1 pointer to the first option
+ /// @param option2
+ /// @return true, if content is identical
+ bool compareOptions(const isc::dhcp::OptionPtr& option1,
+ const isc::dhcp::OptionPtr& option2);
+
+ /// @brief Tests if the acquired lease is or is not declined.
+ ///
+ /// @param client Dhcp6Client instance
+ /// @param duid1 DUID used during lease acquisition
+ /// @param iaid1 IAID used during lease acquisition
+ /// @param duid2 DUID used during Decline exchange
+ /// @param iaid2 IAID used during Decline exchange
+ /// @param addr_type specify what sort of address the client should
+ /// include (its own, a bogus one or no address at all)
+ /// @param expected_result SHOULD_PASS if the lease is expected to
+ /// be successfully declined, or SHOULD_FAIL if the lease is expected
+ /// to not be declined.
+ /// @param config_index specifies which config index should be used:
+ /// 0 - memfile, 1 - mysql, 2 - pgsql
+ void acquireAndDecline(Dhcp6Client& client,
+ const std::string& duid1,
+ const uint32_t iaid1,
+ const std::string& duid2,
+ const uint32_t iaid2,
+ AddressInclusion addr_type,
+ ExpectedResult expected_result,
+ uint8_t config_index = 0);
+
+ /// @brief Performs basic (positive) RENEW test
+ ///
+ /// See renewBasic and pdRenewBasic tests for detailed explanation.
+ /// In essence the test attempts to perform a successful RENEW scenario.
+ ///
+ /// This method does not throw, but uses gtest macros to signify failures.
+ ///
+ /// @param type type (TYPE_NA or TYPE_PD)
+ /// @param existing_addr address to be preinserted into the database
+ /// @param renew_addr address being sent in RENEW
+ /// @param prefix_len length of the prefix (128 for addresses)
+ /// @param insert_before_renew should the lease be inserted into the database
+ /// before we try renewal?
+ /// @param expire_before_renew should the lease be expired before we try
+ /// renewal?
+ /// @param hint_pref preferred lifetime hint (default is 300)
+ /// @param hint_valid valid lifetime hint (default is 500)
+ /// @param expected_pref expected preferred lifetime (zero means not check)
+ /// @param expected_valid expected valid lifetime (zero means not check)
+ void
+ testRenewBasic(isc::dhcp::Lease::Type type,
+ const std::string& existing_addr,
+ const std::string& renew_addr, const uint8_t prefix_len,
+ bool insert_before_renew = true,
+ bool expire_before_renew = false,
+ uint32_t hint_pref = 300, uint32_t hint_valid = 500,
+ uint32_t expected_pref = 0, uint32_t expected_valid = 0);
+
+ /// @brief Checks if RENEW with invalid IAID is processed correctly.
+ ///
+ /// @param type lease type (currently only IA_NA is supported)
+ /// @param addr address to be renewed
+ void
+ testRenewWrongIAID(isc::dhcp::Lease::Type type,
+ const asiolink::IOAddress& addr);
+
+ /// @brief Checks if client A can renew address used by client B
+ ///
+ /// @param type lease type (currently only IA_NA is supported)
+ /// @param addr address to be renewed
+ void
+ testRenewSomeoneElsesLease(isc::dhcp::Lease::Type type,
+ const asiolink::IOAddress& addr);
+
+ /// @brief Performs basic (positive) RELEASE test
+ ///
+ /// See releaseBasic and pdReleaseBasic tests for detailed explanation.
+ /// In essence the test attempts to perform a successful RELEASE scenario.
+ ///
+ /// This method does not throw, but uses gtest macros to signify failures.
+ ///
+ /// @param type type (TYPE_NA or TYPE_PD)
+ /// @param existing address to be preinserted into the database
+ /// @param release_addr address being sent in RELEASE
+ /// @param lease_affinity A flag which indicates if lease affinity should
+ /// be enabled or disabled.
+ void
+ testReleaseBasic(isc::dhcp::Lease::Type type,
+ const isc::asiolink::IOAddress& existing,
+ const isc::asiolink::IOAddress& release_addr,
+ const LeaseAffinity lease_affinity);
+
+ /// @brief Checks that reassignment of a released-expired lease
+ /// does not lead to zero lifetimes.
+ ///
+ /// This method does not throw, but uses gtest macros to signify failures.
+ ///
+ /// @param type lease type (TYPE_NA or TYPE_PD).
+ /// @param addr lease address.
+ /// @param qtype query type.
+ void
+ testReleaseNoDelete(isc::dhcp::Lease::Type type,
+ const isc::asiolink::IOAddress& addr,
+ uint8_t qtype);
+
+ /// @brief Performs negative RELEASE test
+ ///
+ /// See releaseReject and pdReleaseReject tests for detailed
+ /// explanation. In essence the test attempts to perform couple
+ /// failed RELEASE scenarios.
+ ///
+ /// This method does not throw, but uses gtest macros to signify failures.
+ ///
+ /// @param type type (TYPE_NA or TYPE_PD)
+ /// @param addr address being sent in RELEASE
+ void
+ testReleaseReject(isc::dhcp::Lease::Type type,
+ const isc::asiolink::IOAddress& addr);
+
+ /// @brief simulates reception of a packet of specified type and checks statistic
+ ///
+ /// @param pkt_type reception of a packet of this type will be simulated
+ /// @param stat_name this statistic is expected to be set to 1
+ void testReceiveStats(uint8_t pkt_type, const std::string& stat_name);
+
+ /// @brief Set multi-threading mode.
+ void setMultiThreading(bool enabled) {
+ multi_threading_ = enabled;
+ }
+
+ /// @brief Check if example files contain valid configuration.
+ void checkConfigFiles();
+
+ /// @brief Check if the server configuration stored in file is valid.
+ ///
+ /// @param path The path to the configuration file.
+ void loadConfigFile(const std::string& path);
+
+ /// A subnet used in most tests.
+ isc::dhcp::Subnet6Ptr subnet_;
+
+ /// A normal, non-temporary pool used in most tests.
+ isc::dhcp::Pool6Ptr pool_;
+
+ /// A prefix pool used in most tests.
+ isc::dhcp::Pool6Ptr pd_pool_;
+
+ /// @brief Server object under test.
+ NakedDhcpv6Srv srv_;
+
+ /// @brief The multi-threading flag.
+ bool multi_threading_;
+};
+
+/// @brief Patch the server config to add interface-config/re-detect=false
+/// @param json the server config
+inline void
+disableIfacesReDetect(isc::data::ConstElementPtr json) {
+ isc::data::ConstElementPtr ifaces_cfg = json->get("interfaces-config");
+ if (ifaces_cfg) {
+ isc::data::ElementPtr mutable_cfg =
+ boost::const_pointer_cast<isc::data::Element>(ifaces_cfg);
+ mutable_cfg->set("re-detect", isc::data::Element::create(false));
+ }
+}
+
+/// @brief Patch the server config to add multi-threading/enable-multi-threading
+/// @param json the server config
+inline void
+configureMultiThreading(bool enabled, isc::data::ConstElementPtr json) {
+ isc::data::ConstElementPtr multi_threading = json->get("multi-threading");
+ if (!multi_threading) {
+ isc::data::ElementPtr mutable_cfg =
+ boost::const_pointer_cast<isc::data::Element>(json);
+ multi_threading = isc::data::Element::createMap();
+ mutable_cfg->set("multi-threading", multi_threading);
+ }
+
+ isc::data::ElementPtr mutable_cfg =
+ boost::const_pointer_cast<isc::data::Element>(multi_threading);
+ if (enabled) {
+ mutable_cfg->set("enable-multi-threading", isc::data::Element::create(true));
+ mutable_cfg->set("thread-pool-size", isc::data::Element::create(4));
+ mutable_cfg->set("packet-queue-size", isc::data::Element::create(4));
+ } else {
+ mutable_cfg->set("enable-multi-threading", isc::data::Element::create(false));
+ }
+}
+
+/// @brief Runs parser in JSON mode, useful for parser testing
+///
+/// @param in string to be parsed
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ElementPtr
+parseJSON(const std::string& in) {
+ isc::dhcp::Parser6Context ctx;
+ return (ctx.parseString(in, isc::dhcp::Parser6Context::PARSER_JSON));
+}
+
+/// @brief Runs parser in Dhcp6 mode
+///
+/// This is a simplified Dhcp6 mode, so no outer { } and "Dhcp6" is
+/// needed. This format is used by most of the tests.
+///
+/// @param in string to be parsed
+/// @param verbose display the exception message when it fails
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ElementPtr
+parseDHCP6(const std::string& in, bool verbose = false) {
+ try {
+ isc::dhcp::Parser6Context ctx;
+ isc::data::ElementPtr json;
+ json = ctx.parseString(in, isc::dhcp::Parser6Context::SUBPARSER_DHCP6);
+ disableIfacesReDetect(json);
+ return (json);
+ }
+ catch (const std::exception& ex) {
+ if (verbose) {
+ std::cout << "EXCEPTION: " << ex.what() << std::endl;
+ }
+ throw;
+ }
+}
+
+/// @brief Runs parser in option definition mode
+///
+/// This function parses specified text as JSON that defines option definitions.
+///
+/// @param in string to be parsed
+/// @param verbose display the exception message when it fails
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ElementPtr
+parseOPTION_DEFS(const std::string& in, bool verbose = false) {
+ try {
+ isc::dhcp::Parser6Context ctx;
+ return (ctx.parseString(in, isc::dhcp::Parser6Context::PARSER_OPTION_DEFS));
+ }
+ catch (const std::exception& ex) {
+ if (verbose) {
+ std::cout << "EXCEPTION: " << ex.what() << std::endl;
+ }
+ throw;
+ }
+}
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // DHCP6_TEST_UTILS_H
diff --git a/src/bin/dhcp6/tests/dhcp6_unittests.cc b/src/bin/dhcp6/tests/dhcp6_unittests.cc
new file mode 100644
index 0000000..a98f513
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_unittests.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
+ int result = RUN_ALL_TESTS();
+
+ return result;
+}
diff --git a/src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc b/src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc
new file mode 100644
index 0000000..ce7e077
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc
@@ -0,0 +1,318 @@
+// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_filter6_test_stub.h>
+#include <dhcp6/dhcp6to4_ipc.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
+#include <stats/stats_mgr.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/library_handle.h>
+
+#include <gtest/gtest.h>
+#include <stdint.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+using namespace isc::hooks;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Port number used in tests.
+const uint16_t TEST_PORT = 32000;
+
+/// @brief Define short name for the test IPC.
+typedef Dhcp4o6TestIpc TestIpc;
+
+/// @brief Test fixture class for DHCPv4 endpoint of DHCPv4o6 IPC.
+class Dhcp6to4IpcTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Configures IPC to use a test port. It also provides a fake
+ /// configuration of interfaces and opens IPv6 sockets.
+ Dhcp6to4IpcTest()
+ : iface_mgr_test_config_(true) {
+ IfaceMgr::instance().openSockets6();
+ configurePort(TEST_PORT);
+ // Install buffer6_send_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
+ registerCallout("buffer6_send", buffer6_send_callout));
+ // Let's wipe all existing statistics.
+ StatsMgr::instance().removeAll();
+
+ // Reset the flag which we expect to be set in the callout.
+ callback_pkt_options_copy_ = false;
+ }
+
+ /// @brief Destructor
+ ///
+ /// Various cleanups.
+ virtual ~Dhcp6to4IpcTest() {
+ Dhcp6to4Ipc::client_port = 0;
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer6_send");
+ callback_pkt_.reset();
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ std::cerr << "(fixture dtor) unloadLibraries failed" << std::endl;
+ }
+ }
+
+ /// @brief Configure DHCP4o6 port.
+ ///
+ /// @param port New port.
+ void configurePort(const uint16_t port);
+
+ /// @brief Creates an instance of the DHCPv4o6 Message option.
+ ///
+ /// The option will contain an empty DHCPREQUEST message, with
+ /// just the Message Type option inside and nothing else.
+ ///
+ /// @return Pointer to the instance of the DHCPv4-query Message option.
+ OptionPtr createDHCPv4MsgOption() const;
+
+ /// @brief Handler for the buffer6_send hook
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_send_callout(CalloutHandle& callout_handle) {
+ callout_handle.getArgument("response6", callback_pkt_);
+ if (callback_pkt_) {
+ callback_pkt_options_copy_ = callback_pkt_->isCopyRetrievedOptions();
+ }
+ return (0);
+ }
+
+ /// @brief Response Pkt6 shared pointer returned in the callout
+ static Pkt6Ptr callback_pkt_;
+
+ /// Flag indicating if copying retrieved options was enabled for
+ /// a received packet during callout execution.
+ static bool callback_pkt_options_copy_;
+
+private:
+
+ /// @brief Provides fake configuration of interfaces.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+Pkt6Ptr Dhcp6to4IpcTest::callback_pkt_;
+bool Dhcp6to4IpcTest::callback_pkt_options_copy_;
+
+void
+Dhcp6to4IpcTest::configurePort(const uint16_t port) {
+ CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port);
+}
+
+OptionPtr
+Dhcp6to4IpcTest::createDHCPv4MsgOption() const {
+ // Create the DHCPv4 message.
+ Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
+ // Make a wire representation of the DHCPv4 message.
+ pkt->pack();
+ OutputBuffer& output_buffer = pkt->getBuffer();
+ const uint8_t* data = static_cast<const uint8_t*>(output_buffer.getData());
+ OptionBuffer option_buffer(data, data + output_buffer.getLength());
+
+ // Create the DHCPv4 Message option holding the created message.
+ OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer));
+ return (opt_msg);
+}
+
+// This test verifies that the IPC returns an error when trying to bind
+// to the out of range port.
+TEST_F(Dhcp6to4IpcTest, invalidPortError) {
+ // Create instance of the IPC endpoint under test with out-of-range port.
+ configurePort(65535);
+ Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance();
+ EXPECT_THROW(ipc.open(), isc::OutOfRange);
+}
+
+// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
+// receive messages.
+TEST_F(Dhcp6to4IpcTest, receive) {
+ // Create instance of the IPC endpoint under test.
+ Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance();
+ // Create instance of the IPC endpoint being used as a source of messages.
+ TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+ // Reset the IPC.
+ ASSERT_NO_THROW(ipc.close());
+
+ // Open both endpoints.
+ ASSERT_NO_THROW(ipc.open());
+ ASSERT_NO_THROW(src_ipc.open());
+
+ // Get statistics
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt6_snd = mgr.getObservation("pkt6-sent");
+ ObservationPtr d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+ EXPECT_FALSE(pkt6_snd);
+ EXPECT_FALSE(d4_resp);
+
+ // Create message to be sent over IPC.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_RESPONSE, 1234));
+ pkt->addOption(createDHCPv4MsgOption());
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Reset the callout cached packet
+ Dhcp6to4IpcTest::callback_pkt_.reset();
+
+ // Send and wait up to 1 second to receive it.
+ ASSERT_NO_THROW(src_ipc.send(pkt));
+ ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+ // Make sure that the received packet was configured to return copy of
+ // retrieved options within a callout.
+ EXPECT_TRUE(callback_pkt_options_copy_);
+
+ // Get the forwarded packet from the callout
+ Pkt6Ptr forwarded = Dhcp6to4IpcTest::callback_pkt_;
+ ASSERT_TRUE(forwarded);
+
+ // Verify the packet received.
+ EXPECT_EQ(DHCP6_CLIENT_PORT, forwarded->getRemotePort());
+ EXPECT_EQ(forwarded->getType(), pkt->getType());
+ EXPECT_TRUE(forwarded->getOption(D6O_DHCPV4_MSG));
+ EXPECT_EQ("eth0", forwarded->getIface());
+ EXPECT_EQ(ETH0_INDEX, forwarded->getIndex());
+ EXPECT_EQ("2001:db8:1::123", forwarded->getRemoteAddr().toText());
+
+ // Verify statistics
+ pkt6_snd = mgr.getObservation("pkt6-sent");
+ d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+ ASSERT_TRUE(pkt6_snd);
+ ASSERT_TRUE(d4_resp);
+ EXPECT_EQ(1, pkt6_snd->getInteger().first);
+ EXPECT_EQ(1, d4_resp->getInteger().first);
+}
+
+// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
+// receive relayed messages.
+// This is currently not supported: it is a known defect addressed by #4296.
+TEST_F(Dhcp6to4IpcTest, DISABLED_receiveRelayed) {
+ // Create instance of the IPC endpoint under test.
+ Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance();
+ // Create instance of the IPC endpoint being used as a source of messages.
+ TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+ // Reset the IPC.
+ ASSERT_NO_THROW(ipc.close());
+
+ // Open both endpoints.
+ ASSERT_NO_THROW(ipc.open());
+ ASSERT_NO_THROW(src_ipc.open());
+
+ // Get statistics
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt6_snd = mgr.getObservation("pkt6-sent");
+ ObservationPtr d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+ EXPECT_FALSE(pkt6_snd);
+ EXPECT_FALSE(d4_resp);
+
+ // Create relayed message to be sent over IPC.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_RESPONSE, 1234));
+ pkt->addOption(createDHCPv4MsgOption());
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("3000:1::1");
+ relay.peeraddr_ = IOAddress("fe80::1");
+ relay.msg_type_ = DHCPV6_RELAY_FORW;
+ relay.hop_count_ = 1;
+ pkt->relay_info_.push_back(relay);
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Reset the callout cached packet
+ Dhcp6to4IpcTest::callback_pkt_.reset();
+
+ // Send and wait up to 1 second to receive it.
+ ASSERT_NO_THROW(src_ipc.send(pkt));
+ ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+ // Get the forwarded packet from the callout
+ Pkt6Ptr forwarded = Dhcp6to4IpcTest::callback_pkt_;
+ ASSERT_TRUE(forwarded);
+
+ // Verify the packet received.
+ EXPECT_EQ(DHCP6_CLIENT_PORT, forwarded->getRemotePort());
+ EXPECT_EQ(forwarded->getType(), pkt->getType());
+ EXPECT_TRUE(forwarded->getOption(D6O_DHCPV4_MSG));
+ EXPECT_EQ("eth0", forwarded->getIface());
+ EXPECT_EQ(ETH0_INDEX, forwarded->getIndex());
+ EXPECT_EQ("2001:db8:1::123", forwarded->getRemoteAddr().toText());
+ EXPECT_EQ(DHCP6_CLIENT_PORT, forwarded->getRemotePort());
+
+ // Verify statistics
+ pkt6_snd = mgr.getObservation("pkt6-sent");
+ d4_resp = mgr.getObservation("pkt6-dhcpv4-response-sent");
+ ASSERT_TRUE(pkt6_snd);
+ ASSERT_TRUE(d4_resp);
+ EXPECT_EQ(1, pkt6_snd->getInteger().first);
+ EXPECT_EQ(1, d4_resp->getInteger().first);
+}
+
+// This test verifies the client port is enforced also with DHCP4o6.
+TEST_F(Dhcp6to4IpcTest, clientPort) {
+ // Create instance of the IPC endpoint under test.
+ Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance();
+ // Set the client port.
+ ipc.client_port = 1234;
+ // Create instance of the IPC endpoint being used as a source of messages.
+ TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+ // Reset the IPC.
+ ASSERT_NO_THROW(ipc.close());
+
+ // Open both endpoints.
+ ASSERT_NO_THROW(ipc.open());
+ ASSERT_NO_THROW(src_ipc.open());
+
+ // Create message to be sent over IPC.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_RESPONSE, 1234));
+ pkt->addOption(createDHCPv4MsgOption());
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Reset the callout cached packet
+ Dhcp6to4IpcTest::callback_pkt_.reset();
+
+ // Send and wait up to 1 second to receive it.
+ ASSERT_NO_THROW(src_ipc.send(pkt));
+ ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+ // Make sure that the received packet was configured to return copy of
+ // retrieved options within a callout.
+ EXPECT_TRUE(callback_pkt_options_copy_);
+
+ // Get the forwarded packet from the callout
+ Pkt6Ptr forwarded = Dhcp6to4IpcTest::callback_pkt_;
+ ASSERT_TRUE(forwarded);
+
+ // Verify the packet received.
+ EXPECT_EQ(ipc.client_port, forwarded->getRemotePort());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/fqdn_unittest.cc b/src/bin/dhcp6/tests/fqdn_unittest.cc
new file mode 100644
index 0000000..2c0fc1c
--- /dev/null
+++ b/src/bin/dhcp6/tests/fqdn_unittest.cc
@@ -0,0 +1,2177 @@
+// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp::test;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
+using namespace isc::util;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief A test fixture class for testing DHCPv6 Client FQDN Option handling.
+class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
+public:
+ // Bit Constants for turning on and off DDNS configuration options.
+ // (Defined here as these are only meaningful to this class.)
+ static const uint16_t OVERRIDE_NO_UPDATE = 1;
+ static const uint16_t OVERRIDE_CLIENT_UPDATE = 2;
+ static const uint16_t REPLACE_CLIENT_NAME = 4;
+
+ // Enum used to specify whether a client (packet) should include
+ // the hostname option
+ enum ClientNameFlag {
+ CLIENT_NAME_PRESENT,
+ CLIENT_NAME_NOT_PRESENT
+ };
+
+ // Enum used to specify whether the server should replace/supply
+ // the hostname or not
+ enum ReplacementFlag {
+ NAME_REPLACED,
+ NAME_NOT_REPLACED
+ };
+
+ // Type used to indicate whether or not forward updates are expected
+ struct ExpFwd {
+ ExpFwd(bool exp_fwd) : value_(exp_fwd){};
+ bool value_;
+ };
+
+ // Type used to indicate whether or not reverse updates are expected
+ struct ExpRev {
+ ExpRev(bool exp_rev) : value_(exp_rev){};
+ bool value_;
+ };
+
+ /// @brief Constructor
+ FqdnDhcpv6SrvTest()
+ : Dhcpv6SrvTest(), srv_(new NakedDhcpv6Srv(0)),
+ d2_mgr_(CfgMgr::instance().getD2ClientMgr()),
+ iface_mgr_test_config_(true) {
+ // generateClientId assigns DUID to duid_.
+ generateClientId();
+ lease_.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ duid_, 1234, 501, 502, 1, HWAddrPtr()));
+ // Config DDNS to be enabled, all controls off
+ enableD2();
+ }
+
+ /// @brief Destructor
+ virtual ~FqdnDhcpv6SrvTest() {
+ // Default constructor creates a config with DHCP-DDNS updates
+ // disabled.
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(cfg);
+ }
+
+ /// @brief Sets the server's DDNS configuration to ddns updates disabled.
+ void disableD2() {
+ // Default constructor creates a config with DHCP-DDNS updates
+ // disabled.
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(cfg);
+ }
+
+ /// @brief Enables DHCP-DDNS updates with the given options enabled.
+ ///
+ /// Replaces the current D2ClientConfiguration with a configuration
+ /// which as updates enabled and the control options set based upon
+ /// the bit mask of options.
+ ///
+ /// @param mask Bit mask of configuration options that should be enabled.
+ void enableD2(const uint16_t mask = 0) {
+ D2ClientConfigPtr cfg;
+
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("::1"), 53001,
+ isc::asiolink::IOAddress("::"), 0,
+ 1024, dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+
+ ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
+
+ // Now we'll set the DDNS parameters at the subnet level.
+ // These should get fetched when getDdnsParams() is invoked.
+ ASSERT_TRUE(subnet_) << "enableD2 called without subnet_ set";
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(mask & OVERRIDE_NO_UPDATE);
+ subnet_->setDdnsOverrideClientUpdate(mask & OVERRIDE_CLIENT_UPDATE);
+ subnet_->setDdnsReplaceClientNameMode((mask & REPLACE_CLIENT_NAME) ?
+ D2ClientConfig::RCM_WHEN_PRESENT
+ : D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("myhost");
+ subnet_->setDdnsQualifyingSuffix("example.com");
+ subnet_->setHostnameCharSet("[^A-Za-z0-9-]");
+ subnet_->setHostnameCharReplacement("x");
+ subnet_->setDdnsUseConflictResolution(true);
+
+ ASSERT_NO_THROW(srv_->startD2());
+ }
+
+ // Fetch DDNS parameter set scoped to the current subnet_.
+ DdnsParamsPtr getDdnsParams() {
+ ConstElementPtr cfg = CfgMgr::instance().getCurrentCfg()->toElement();
+ if (!subnet_) {
+ ADD_FAILURE() << "getDdnsParams() - subnet_ is empty!";
+ return (DdnsParamsPtr(new DdnsParams()));
+ }
+
+ return(CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_));
+ }
+
+ /// @brief Construct the DHCPv6 Client FQDN option using flags and
+ /// domain-name.
+ ///
+ /// @param flags Flags to be set for the created option.
+ /// @param fqdn_name A name which should be stored in the option.
+ /// @param fqdn_type A type of the name carried by the option: partial
+ /// or fully qualified.
+ ///
+ /// @return A pointer to the created option.
+ Option6ClientFqdnPtr
+ createClientFqdn(const uint8_t flags,
+ const std::string& fqdn_name,
+ const Option6ClientFqdn::DomainNameType fqdn_type) {
+ return (Option6ClientFqdnPtr(new Option6ClientFqdn(flags,
+ fqdn_name,
+ fqdn_type)));
+ }
+
+ /// @brief Create a message with or without DHCPv6 Client FQDN Option.
+ ///
+ /// @param msg_type A type of the DHCPv6 message to be created.
+ /// @param fqdn_flags Flags to be carried in the FQDN option.
+ /// @param fqdn_domain_name A name to be carried in the FQDN option.
+ /// @param fqdn_type A type of the name carried by the option: partial
+ /// or fully qualified.
+ /// @param include_oro A boolean value which indicates whether the ORO
+ /// option should be added to the message (if true).
+ /// @param srvid server id to be stored in the message.
+ ///
+ /// @return An object representing the created message.
+ Pkt6Ptr generateMessage(uint8_t msg_type,
+ const uint8_t fqdn_flags,
+ const std::string& fqdn_domain_name,
+ const Option6ClientFqdn::DomainNameType
+ fqdn_type,
+ const bool include_oro,
+ OptionPtr srvid = OptionPtr()) {
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ Option6IAPtr ia = generateIA(D6O_IA_NA, 234, 1500, 3000);
+ if (msg_type != DHCPV6_REPLY) {
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ pkt->addOption(ia);
+ }
+
+ OptionPtr clientid = generateClientId();
+ pkt->addOption(clientid);
+ if (srvid && (msg_type != DHCPV6_SOLICIT)) {
+ pkt->addOption(srvid);
+ }
+
+ pkt->addOption(createClientFqdn(fqdn_flags, fqdn_domain_name,
+ fqdn_type));
+
+ if (include_oro) {
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6,
+ D6O_ORO));
+ oro->addValue(D6O_CLIENT_FQDN);
+ pkt->addOption(oro);
+ }
+
+ return (pkt);
+ }
+
+ /// @brief Creates instance of the DHCPv6 message with client id and
+ /// server id.
+ ///
+ /// @param msg_type A type of the message to be created.
+ ///
+ /// @return An object representing the created message.
+ Pkt6Ptr generateMessageWithIds(const uint8_t msg_type) {
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ // Generate client-id.
+ OptionPtr opt_clientid = generateClientId();
+ pkt->addOption(opt_clientid);
+
+ if (msg_type != DHCPV6_SOLICIT) {
+ // Generate server-id.
+ pkt->addOption(srv_->getServerID());
+ }
+
+ return (pkt);
+ }
+
+ /// @brief Returns an instance of the option carrying FQDN.
+ ///
+ /// @param pkt A message holding FQDN option to be returned.
+ ///
+ /// @return An object representing DHCPv6 Client FQDN option.
+ Option6ClientFqdnPtr getClientFqdnOption(const Pkt6Ptr& pkt) {
+ return (boost::dynamic_pointer_cast<Option6ClientFqdn>
+ (pkt->getOption(D6O_CLIENT_FQDN)));
+ }
+
+ /// @brief Adds IA option to the message.
+ ///
+ /// Added option holds an address.
+ ///
+ /// @param iaid IAID
+ /// @param pkt A DHCPv6 message to which the IA option should be added.
+ void addIA(const uint32_t iaid, const IOAddress& addr, Pkt6Ptr& pkt) {
+ Option6IAPtr opt_ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+ Option6IAAddrPtr opt_iaaddr(new Option6IAAddr(D6O_IAADDR, addr,
+ 300, 500));
+ opt_ia->addOption(opt_iaaddr);
+ pkt->addOption(opt_ia);
+ }
+
+ /// @brief Adds IA option to the message.
+ ///
+ /// Added option holds status code.
+ ///
+ /// @param iaid IAID
+ /// @param status_code Status code
+ /// @param pkt A DHCPv6 message to which the option should be added.
+ void addIA(const uint32_t iaid, const uint16_t status_code, Pkt6Ptr& pkt) {
+ Option6IAPtr opt_ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+ addStatusCode(status_code, "", opt_ia);
+ pkt->addOption(opt_ia);
+ }
+
+ /// @brief Creates status code with the specified code and message.
+ ///
+ /// @param code A status code.
+ /// @param msg A string representation of the message to be added to the
+ /// Status Code option.
+ ///
+ /// @return An object representing the Status Code option.
+ Option6StatusCodePtr createStatusCode(const uint16_t code,
+ const std::string& msg) {
+ Option6StatusCodePtr opt_status(new Option6StatusCode(code, msg));
+ return (opt_status);
+ }
+
+ /// @brief Adds Status Code option to the IA.
+ ///
+ /// @param code A status code value.
+ /// @param msg A string representation of the message to be added to the
+ /// Status Code option.
+ void addStatusCode(const uint16_t code, const std::string& msg,
+ Option6IAPtr& opt_ia) {
+ opt_ia->addOption(createStatusCode(code, msg));
+ }
+
+ /// @brief Verifies if the DHCPv6 server processes DHCPv6 Client FQDN option
+ /// as expected and subsequent interpretation of this processing when
+ /// creating NCRs, if any should be created.
+ ///
+ /// This function simulates generation of the client's message holding FQDN.
+ /// It then calls the server's @c Dhcpv6Srv::processClientFqdn option to
+ /// generate server's response to the FQDN. This function returns the FQDN
+ /// which should be appended to the server's response to the client.
+ /// This function verifies that the FQDN option returned is correct
+ /// Optionally, this function then proceeds to call createNameChangeRequests
+ /// to verify if that method interprets the FQDN information properly.
+ ///
+ /// @param msg_type A type of the client's message.
+ /// @param in_flags A value of flags field to be set for the FQDN carried
+ /// in the client's message.
+ /// @param in_domain_name A domain name to be carried in the client's FQDN
+ /// option.
+ /// @param in_domain_type A type of the domain name to be carried in the
+ /// client's FQDM option (partial or fully qualified).
+ /// @param exp_flags A value of flags expected in the FQDN sent by a server.
+ /// @param exp_domain_name A domain name expected in the FQDN sent by a
+ /// server.
+ /// @param create_ncr_check if true, calls createNameChangeRequests method
+ /// and tests the outcome.
+ /// @param exp_fwd indicates whether or not a forward change is expected
+ /// @param exp_fwd indicates whether or not a reverse change is expected
+ void testFqdn(const uint16_t msg_type,
+ const uint8_t in_flags,
+ const std::string& in_domain_name,
+ const Option6ClientFqdn::DomainNameType in_domain_type,
+ const uint8_t exp_flags,
+ const std::string& exp_domain_name,
+ const bool create_ncr_check,
+ const ExpFwd& exp_fwd = ExpFwd(true),
+ const ExpRev& exp_rev = ExpRev(true)) {
+
+ Pkt6Ptr question = generateMessage(msg_type,
+ in_flags,
+ in_domain_name,
+ in_domain_type,
+ true);
+
+ ASSERT_TRUE(getClientFqdnOption(question));
+
+ Pkt6Ptr answer = generateMessageWithIds(msg_type == DHCPV6_SOLICIT
+ ? DHCPV6_ADVERTISE :
+ DHCPV6_REPLY);
+
+ // Create three IAs, each having different address.
+ addIA(1234, IOAddress("2001:db8:1::1"), answer);
+
+ AllocEngine::ClientContext6 ctx;
+ // Set the selected subnet so ddns params get returned correctly.
+ ctx.subnet_ = subnet_;
+
+ ASSERT_NO_THROW(srv_->processClientFqdn(question, answer, ctx));
+ Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(answ_fqdn);
+
+ const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0;
+ const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0;
+ const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0;
+
+ EXPECT_EQ(flag_n, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ(flag_s, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_EQ(flag_o, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_O));
+
+ EXPECT_EQ(exp_domain_name, answ_fqdn->getDomainName());
+ // If server is configured to generate full FQDN for a client, and/or
+ // client sent empty FQDN the expected result of the processing by
+ // processClientFqdn is an empty, partial FQDN. This is an indication
+ // for the code which performs lease allocation that the FQDN has to
+ // be generated from the lease address.
+ if (exp_domain_name.empty()) {
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL,
+ answ_fqdn->getDomainNameType());
+
+ } else {
+ EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
+
+ }
+
+ if (create_ncr_check) {
+ // Context flags are normally set during lease allocation. Since that
+ // hasn't occurred we'll set them here to match the expected values.
+ // Call createNameChangeRequests
+ ctx.fwd_dns_update_ = exp_fwd.value_;
+ ctx.rev_dns_update_ = exp_rev.value_;
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx));
+ if (exp_fwd.value_ || exp_rev.value_) {
+ // Should have created 1 NCR.
+ NameChangeRequestPtr ncr;
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
+ ASSERT_TRUE(ncr);
+ EXPECT_EQ(dhcp_ddns::CHG_ADD, ncr->getChangeType());
+ EXPECT_EQ(exp_fwd.value_, ncr->isForwardChange());
+ EXPECT_EQ(exp_rev.value_, ncr->isReverseChange());
+ ASSERT_NO_THROW(d2_mgr_.runReadyIO());
+ } else {
+ // Should not have created any NCRs.
+ EXPECT_EQ(0, d2_mgr_.getQueueSize());
+ }
+ }
+ }
+
+ // Test that the server processes the FQDN option (or lack thereof)
+ // in a client request correctly, according to the replace-client-name
+ // mode configuration parameter.
+ //
+ // @param mode - value to use for replace-client-name mode
+ //
+ // @param client_name_flag - specifies whether or not the client request
+ // should contain a hostname option
+ // @param exp_replacement_flag - specifies whether or not the server is
+ // expected to replace (or supply) the FQDN/name in its response
+ void testReplaceClientNameMode(const char* mode,
+ enum ClientNameFlag client_name_flag,
+ enum ReplacementFlag exp_replacement_flag) {
+ // Configuration "template" with a replaceable mode parameter
+ const char* config_template =
+ "{ \"interfaces-config\": { \n"
+ " \"interfaces\": [ \"eth0\" ] \n"
+ "}, \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"interface\": \"eth0\" \n"
+ " } ], \n"
+ "\"dhcp-ddns\": { \n"
+ "\"enable-updates\": true, \n"
+ "\"qualifying-suffix\": \"fake-suffix.isc.org.\", \n"
+ "\"replace-client-name\": \"%s\" \n"
+ "}} \n";
+
+ // Create the configuration and configure the server
+ char config_buf[1024];
+ snprintf(config_buf, 1024, config_template, mode);
+ configure(config_buf, *srv_);
+
+ // Build our client packet
+ Pkt6Ptr query;
+ if (client_name_flag == CLIENT_NAME_PRESENT) {
+ query = generateMessage(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+ "my.example.com.", Option6ClientFqdn::FULL,
+ true);
+ } else {
+ query = generateMessageWithIds(DHCPV6_SOLICIT);
+ }
+
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_->earlyGHRLookup(query, ctx);
+ ASSERT_FALSE(drop);
+ srv_->initContext(query, ctx, drop);
+
+ ASSERT_FALSE(drop);
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_ADVERTISE);
+
+ ASSERT_NO_THROW(srv_->processClientFqdn(query, answer, ctx));
+
+ Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+
+ // Verify the contents (or lack thereof) of the FQDN
+ if (exp_replacement_flag == NAME_REPLACED) {
+ ASSERT_TRUE(answ_fqdn);
+ EXPECT_TRUE(answ_fqdn->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL,
+ answ_fqdn->getDomainNameType());
+ } else {
+ if (client_name_flag == CLIENT_NAME_PRESENT) {
+ ASSERT_TRUE(answ_fqdn);
+ EXPECT_EQ("my.example.com.", answ_fqdn->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL,
+ answ_fqdn->getDomainNameType());
+ } else {
+ ASSERT_FALSE(answ_fqdn);
+ }
+ }
+ }
+
+ /// @brief Tests that the client's message holding an FQDN is processed
+ /// and that lease is acquired.
+ ///
+ /// @param msg_type A type of the client's message.
+ /// @param hostname A domain name in the client's FQDN.
+ /// @param client_flags A bitmask of the client FQDN flags
+ /// @param include_oro A boolean value which indicates whether the ORO
+ /// option should be included in the client's message (if true) or not
+ /// (if false). In the former case, the function will expect that server
+ /// responds with the FQDN option. In the latter case, the function expects
+ /// that the server doesn't respond with the FQDN.
+ void testProcessMessage(const uint8_t msg_type,
+ const std::string& hostname,
+ const std::string& exp_hostname,
+ const uint8_t client_flags =
+ Option6ClientFqdn::FLAG_S,
+ const IOAddress& expected_address = IOAddress("2001:db8:1:1::dead:beef"),
+ const bool include_oro = true) {
+ // Create a message of a specified type, add server id and
+ // FQDN option.
+ OptionPtr srvid = srv_->getServerID();
+ // Set the appropriate FQDN type. It must be partial if hostname is
+ // empty.
+ Option6ClientFqdn::DomainNameType fqdn_type = (hostname.empty() ?
+ Option6ClientFqdn::PARTIAL : Option6ClientFqdn::FULL);
+ Pkt6Ptr req = generateMessage(msg_type, client_flags,
+ hostname, fqdn_type, include_oro, srvid);
+
+ // For different client's message types we have to invoke different
+ // functions to generate response.
+ Pkt6Ptr reply;
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_->earlyGHRLookup(req, ctx);
+ ASSERT_FALSE(drop);
+ srv_->initContext(req, ctx, drop);
+
+ ASSERT_FALSE(drop);
+ if (msg_type == DHCPV6_SOLICIT) {
+ ASSERT_NO_THROW(reply = srv_->processSolicit(ctx));
+
+ } else if (msg_type == DHCPV6_REQUEST) {
+ ASSERT_NO_THROW(reply = srv_->processRequest(ctx));
+
+ } else if (msg_type == DHCPV6_RENEW) {
+ ASSERT_NO_THROW(reply = srv_->processRenew(ctx));
+
+ } else if (msg_type == DHCPV6_RELEASE) {
+ // For Release no lease will be acquired so we have to leave
+ // function here.
+ ASSERT_NO_THROW(reply = srv_->processRelease(ctx));
+ return;
+ } else {
+ // We are not interested in testing other message types.
+ return;
+ }
+
+ // For Solicit, we will get different message type obviously.
+ if (msg_type == DHCPV6_SOLICIT) {
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ } else {
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+ }
+
+ // Check verify that IA_NA is correct.
+ Option6IAAddrPtr addr =
+ checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // Check that we have got the address we requested.
+ checkIAAddr(addr, expected_address,
+ Lease::TYPE_NA);
+
+ if (msg_type != DHCPV6_SOLICIT) {
+ // Check that the lease exists.
+ Lease6Ptr lease =
+ checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(exp_hostname, lease->hostname_);
+ }
+
+ // The Client FQDN option should be always present in the server's
+ // response, regardless if requested using ORO or not.
+ Option6ClientFqdnPtr fqdn;
+ ASSERT_TRUE(fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(reply->getOption(D6O_CLIENT_FQDN)));
+ EXPECT_EQ(exp_hostname, fqdn->getDomainName());
+ }
+
+ /// @brief Verify that NameChangeRequest holds valid values.
+ ///
+ /// This function picks first NameChangeRequest from the internal server's
+ /// queue and checks that it holds valid parameters. The NameChangeRequest
+ /// is removed from the queue.
+ ///
+ /// @param type An expected type of the NameChangeRequest (Add or Remove).
+ /// @param reverse An expected setting of the reverse update flag.
+ /// @param forward An expected setting of the forward update flag.
+ /// @param addr A string representation of the IPv6 address held in the
+ /// NameChangeRequest.
+ /// @param dhcid An expected DHCID value. Ignored if blank.
+ /// @note This value is the value that is produced by
+ /// dhcp_ddns::D2Dhcid::createDigest() with the appropriate arguments. This
+ /// method uses encryption tools to produce the value which cannot be
+ /// easily duplicated by hand. It is more or less necessary to generate
+ /// these values programmatically and place them here. Should the
+ /// underlying implementation of createDigest() change these test values
+ /// will likely need to be updated as well.
+ /// @param expires The cltt of the lease associated with the
+ /// NameChangeRequest, and used to calculate NCR expires value.
+ /// @param valid_lft the valid lifetime of the lease associated with the
+ /// NameChangeRequest.
+ /// @param fqdn The expected string value of the FQDN, if blank the
+ /// check is skipped
+ /// @param exp_use_cr expected value of NCR::conflict_resolution_
+ /// @param ddns_ttl_percent expected value of ddns_ttl_percent used for
+ /// the NCR
+ void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
+ const bool reverse, const bool forward,
+ const std::string& addr,
+ const std::string& dhcid,
+ const uint64_t expires,
+ const uint16_t valid_lft,
+ const std::string& fqdn = "",
+ const bool exp_use_cr = true,
+ util::Optional<double> exp_ddns_ttl_percent
+ = util::Optional<double>()) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
+ ASSERT_TRUE(ncr);
+
+ EXPECT_EQ(type, ncr->getChangeType());
+ EXPECT_EQ(forward, ncr->isForwardChange());
+ EXPECT_EQ(reverse, ncr->isReverseChange());
+ EXPECT_EQ(addr, ncr->getIpAddress());
+ if (!dhcid.empty()) {
+ EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
+ }
+
+ uint32_t ttl = calculateDdnsTtl(valid_lft, exp_ddns_ttl_percent);
+ if (expires != 0) {
+ EXPECT_EQ(expires + ttl, ncr->getLeaseExpiresOn());
+ }
+
+ EXPECT_EQ(ttl, ncr->getLeaseLength());
+
+ EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
+
+ if (! fqdn.empty()) {
+ EXPECT_EQ(fqdn, ncr->getFqdn());
+ }
+
+ EXPECT_EQ(exp_use_cr, ncr->useConflictResolution());
+
+ // Process the message off the queue
+ ASSERT_NO_THROW(d2_mgr_.runReadyIO());
+ }
+
+ /// @brief Updates inherited subnet and pool members
+ ///
+ /// Hack added to set subnet_ and pool_ members that are buried into lower
+ /// level tests such as checkLease(), so one can use "configure" functionality
+ /// rather than hand-building configured objects
+ /// @param subnet_idx Element index of the desired subnet
+ /// @param pool_idx Element index of the desired pool within the desired subnet
+ /// @param type lease type of desired pool
+ ///
+ void setSubnetAndPool(int subnet_idx, int pool_idx, Lease::Type type) {
+ ConstCfgSubnets6Ptr subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
+ ASSERT_TRUE(subnets);
+ const Subnet6Collection* subnet_col = subnets->getAll();
+ ASSERT_EQ(subnet_idx + 1, subnet_col->size());
+ auto subnet_it = subnet_col->begin();
+ // std::advance is not available for this iterator.
+ for (int i = 0; i < subnet_idx; ++i) {
+ subnet_it = std::next(subnet_it);
+ }
+ subnet_ = *subnet_it;
+ ASSERT_TRUE(subnet_);
+
+ const PoolCollection& pool_col = subnet_->getPools(type);
+ ASSERT_EQ(pool_idx + 1, pool_col.size());
+ PoolPtr pool = (subnet_->getPools(type)).at(pool_idx);
+ ASSERT_TRUE(pool);
+ pool_ = boost::dynamic_pointer_cast<Pool6>(pool);
+ ASSERT_TRUE(pool);
+ }
+
+ /// Pointer to Dhcpv6Srv that is used in tests
+ boost::scoped_ptr<NakedDhcpv6Srv> srv_;
+
+ // Reference to D2ClientMgr singleton
+ D2ClientMgr& d2_mgr_;
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+ // Holds a lease used by a test.
+ Lease6Ptr lease_;
+};
+
+// A set of tests verifying server's behavior when it receives the DHCPv6
+// Client Fqdn Option.
+
+// Test server's response when client requests that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com.", true, ExpFwd(true), ExpRev(true));
+}
+
+// Test server's response when client provides partial domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "myhost",
+ Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com.", true, ExpFwd(true), ExpRev(true));
+}
+
+// Test server's response when client provides empty domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S, "", false);
+}
+
+// Test server's response when client requests no DNS update.
+TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_N,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
+ "myhost.example.com.", true, ExpFwd(false), ExpRev(false));
+}
+
+// Test server's response when client requests no DNS update and
+// override-no-updates is true.
+TEST_F(FqdnDhcpv6SrvTest, overrideNoUpdate) {
+ enableD2(OVERRIDE_NO_UPDATE);
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_N,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL,
+ (Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O),
+ "myhost.example.com.", true, ExpFwd(true), ExpRev(true));
+}
+
+// Test server's response when client requests that server delegates the AAAA
+// update to the client
+TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdate) {
+ testFqdn(DHCPV6_SOLICIT, 0, "myhost.example.com.",
+ Option6ClientFqdn::FULL,
+ 0,
+ "myhost.example.com.", true, ExpFwd(false), ExpRev(true));
+}
+
+// Test server's response when client requests that server delegates the AAAA
+// update to the client and this delegation is not allowed.
+TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
+ enableD2(OVERRIDE_CLIENT_UPDATE);
+ testFqdn(DHCPV6_SOLICIT, 0, "myhost.example.com.",
+ Option6ClientFqdn::FULL,
+ Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
+ "myhost.example.com.", true, ExpFwd(true), ExpRev(true));
+}
+
+// Test that exception is thrown if supplied NULL answer packet when
+// creating NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
+ Pkt6Ptr answer;
+
+ AllocEngine::ClientContext6 ctx;
+ ctx.subnet_ = subnet_;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ EXPECT_THROW(srv_->createNameChangeRequests(answer, ctx),
+ isc::Unexpected);
+}
+
+// Test that exception is thrown if supplied answer from the server
+// contains no DUID when creating NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
+ Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234));
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
+
+ AllocEngine::ClientContext6 ctx;
+ ctx.subnet_ = subnet_;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ EXPECT_THROW(srv_->createNameChangeRequests(answer, ctx), isc::Unexpected);
+}
+
+// Test no NameChangeRequests if Client FQDN is not added to the server's
+// response.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
+
+ AllocEngine::ClientContext6 ctx;
+ ctx.subnet_ = subnet_;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx));
+
+ // There should be no new NameChangeRequests.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that NameChangeRequests are not generated if an answer message
+// contains no addresses.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
+
+ // Add Client FQDN option.
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
+ AllocEngine::ClientContext6 ctx;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx));
+
+ // We didn't add any IAs, so there should be no NameChangeRequests in the
+ // queue.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that exactly one NameChangeRequest is created as a result of processing
+// the answer message which holds 3 IAs and when FQDN is specified.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
+
+ // Create three IAs, each having different address.
+ addIA(1234, IOAddress("2001:db8:1::1"), answer);
+ addIA(2345, IOAddress("2001:db8:1::2"), answer);
+ addIA(3456, IOAddress("2001:db8:1::3"), answer);
+
+ // Use domain name in upper case. It should be converted to lower-case
+ // before DHCID is calculated. So, we should get the same result as if
+ // we typed domain name in lower-case.
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "MYHOST.EXAMPLE.COM",
+ Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
+
+ // Create NameChangeRequest for the first allocated address.
+ AllocEngine::ClientContext6 ctx;
+ ctx.subnet_ = subnet_;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx));
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ // Verify that NameChangeRequest is correct.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 500);
+}
+
+// Verify that conflict resolution is turned off when the
+// subnet has it disabled.
+TEST_F(FqdnDhcpv6SrvTest, noConflictResolution) {
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
+
+ // Create three IAs, each having different address.
+ addIA(1234, IOAddress("2001:db8:1::1"), answer);
+
+ // Use domain name in upper case. It should be converted to lower-case
+ // before DHCID is calculated. So, we should get the same result as if
+ // we typed domain name in lower-case.
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "MYHOST.EXAMPLE.COM",
+ Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
+
+ // Create NameChangeRequest for the first allocated address.
+ AllocEngine::ClientContext6 ctx;
+ subnet_->setDdnsUseConflictResolution(false);
+ ctx.subnet_ = subnet_;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx));
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ // Verify that NameChangeRequest is correct.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 500, "", false);
+}
+
+// Checks that NameChangeRequests to add entries are not
+// created when ddns updates are disabled.
+TEST_F(FqdnDhcpv6SrvTest, noAddRequestsWhenDisabled) {
+ // Disable DDNS updates.
+ disableD2();
+
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
+
+ // Create three IAs, each having different address.
+ addIA(1234, IOAddress("2001:db8:1::1"), answer);
+
+ // Use domain name in upper case. It should be converted to lower-case
+ // before DHCID is calculated. So, we should get the same result as if
+ // we typed domain name in lower-case.
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "MYHOST.EXAMPLE.COM",
+ Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
+
+ // An attempt to send a NCR would throw.
+ AllocEngine::ClientContext6 ctx;
+ ctx.subnet_ = subnet_;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx));
+}
+
+// Test creation of the NameChangeRequest to remove both forward and reverse
+// mapping for the given lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
+ lease_->fqdn_fwd_ = true;
+ lease_->fqdn_rev_ = true;
+ // Part of the domain name is in upper case, to test that it gets converted
+ // to lower case before DHCID is computed. So, we should get the same DHCID
+ // as if we typed domain-name in lower case.
+ lease_->hostname_ = "MYHOST.example.com.";
+
+ ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_));
+
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ lease_->cltt_, lease_->valid_lft_);
+}
+
+// Checks that calling queueNCR would not result in error if DDNS updates are
+// disabled.
+TEST_F(FqdnDhcpv6SrvTest, noRemovalsWhenDisabled) {
+ // Disable DDNS updates.
+ disableD2();
+
+ lease_->fqdn_fwd_ = true;
+ lease_->fqdn_rev_ = true;
+ // Part of the domain name is in upper case, to test that it gets converted
+ // to lower case before DHCID is computed. So, we should get the same DHCID
+ // as if we typed domain-name in lower case.
+ lease_->hostname_ = "MYHOST.example.com.";
+
+ // When DDNS is disabled an attempt to send a request should not throw, but
+ // nothing is generated. Unfortunately, we can't see if anything get
+ // generated because getting anything from the queue when DDNS is disabled
+ // would result in exception.
+ ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_));
+}
+
+// Test creation of the NameChangeRequest to remove reverse mapping for the
+// given lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
+ lease_->fqdn_fwd_ = false;
+ lease_->fqdn_rev_ = true;
+ lease_->hostname_ = "myhost.example.com.";
+
+ ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_));
+
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, false,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ lease_->cltt_, lease_->valid_lft_);
+}
+
+// Test that NameChangeRequest to remove DNS records is not generated when
+// neither forward nor reverse DNS update has been performed for a lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoUpdate) {
+ lease_->fqdn_fwd_ = false;
+ lease_->fqdn_rev_ = false;
+
+ ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_));
+
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that NameChangeRequest is not generated if the hostname hasn't been
+// specified for a lease for which forward and reverse mapping has been set.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) {
+ lease_->fqdn_fwd_ = true;
+ lease_->fqdn_rev_ = true;
+ lease_->hostname_ = "";
+
+ Pkt6Ptr pkt(new Pkt6(DHCPREQUEST, 1234));
+ ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_));
+
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that NameChangeRequest is not generated if the invalid hostname has
+// been specified for a lease for which forward and reverse mapping has been
+// set.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestWrongHostname) {
+ lease_->fqdn_fwd_ = true;
+ lease_->fqdn_rev_ = true;
+ lease_->hostname_ = "myhost..example.com.";
+
+ ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_));
+
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that Advertise message generated in a response to the Solicit will
+// not result in generation if the NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
+ // Create a Solicit message with FQDN option and generate server's
+ // response using processSolicit function.
+ testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com",
+ "myhost.example.com.");
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that client may send two requests, each carrying FQDN option with
+// a different domain-name. Server should use existing lease for the second
+// request but modify the DNS entries for the lease according to the contents
+// of the FQDN sent in the second request.
+TEST_F(FqdnDhcpv6SrvTest, processTwoRequestsDiffFqdn) {
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+
+ // The lease should have been recorded in the database.
+ lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(lease_);
+
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // Client may send another request message with a new domain-name. In this
+ // case the same lease will be returned. The existing DNS entry needs to
+ // be replaced with a new one. Server should determine that the different
+ // FQDN has been already added to the DNS. As a result, the old DNS
+ // entries should be removed and the entries for the new domain-name
+ // should be added. Therefore, we expect two NameChangeRequests. One to
+ // remove the existing entries, one to add new entries.
+ testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com",
+ "otherhost.example.com.");
+ ASSERT_EQ(2, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ lease_->cltt_, lease_->valid_lft_);
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201D422AA463306223D269B6CB7AFE7AAD265FC"
+ "EA97F93623019B2E0D14E5323D5A",
+ 0, lease_->valid_lft_);
+}
+
+// Test that client may send two requests, each carrying FQDN option with
+// the same domain-name. Server should use existing lease for the second
+// request and not modify the DNS entries.
+TEST_F(FqdnDhcpv6SrvTest, processTwoRequestsSameFqdn) {
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+
+ // The lease should have been recorded in the database.
+ lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(lease_);
+
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // Client may send another request message with a same domain-name. In this
+ // case the same lease will be returned. The existing DNS entry should be
+ // left alone, so we expect no NameChangeRequests queued..
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that NameChangeRequest is not generated when Solicit message is sent.
+// The Solicit is here sent after a lease has been allocated for a client.
+// The Solicit conveys a different hostname which would trigger updates to
+// DNS if the Request was sent instead of Soicit. The code should differentiate
+// behavior depending whether Solicit or Request is sent.
+TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // When the returning client sends Solicit the code should never generate
+ // NameChangeRequest and preserve existing DNS entries for the client.
+ // The NameChangeRequest should only be generated when a client sends
+ // Request or Renew.
+ testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com",
+ "otherhost.example.com.");
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Test that client may send Request followed by the Renew, both holding
+// FQDN options, but each option holding different domain-name. The Renew
+// should result in generation of the two NameChangeRequests, one to remove
+// DNS entry added previously when Request was processed, another one to
+// add a new entry for the FQDN held in the Renew.
+/// @todo: Fix will be available on trac3677
+TEST_F(FqdnDhcpv6SrvTest, processRequestRenewDiffFqdn) {
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+ // The lease should have been recorded in the database.
+ lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(lease_);
+
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // Client may send Renew message with a new domain-name. In this
+ // case the same lease will be returned. The existing DNS entry needs to
+ // be replaced with a new one. Server should determine that the different
+ // FQDN has been already added to the DNS. As a result, the old DNS
+ // entries should be removed and the entries for the new domain-name
+ // should be added. Therefore, we expect two NameChangeRequests. One to
+ // remove the existing entries, one to add new entries.
+ testProcessMessage(DHCPV6_RENEW, "otherhost.example.com",
+ "otherhost.example.com.");
+ ASSERT_EQ(2, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ lease_->cltt_, lease_->valid_lft_);
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201D422AA463306223D269B6CB7AFE7AAD265FC"
+ "EA97F93623019B2E0D14E5323D5A",
+ 0, lease_->valid_lft_);
+}
+
+// Test that client may send Request followed by the Renew, both holding
+// FQDN options, but each option holding different domain-name. The Renew
+// should result in generation of the two NameChangeRequests, one to remove
+// DNS entry added previously when Request was processed, another one to
+// add a new entry for the FQDN held in the Renew.
+TEST_F(FqdnDhcpv6SrvTest, processRequestRenewSameFqdn) {
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+
+ // Client may send Renew message with a same domain-name. In this
+ // case the same lease will be returned. No DNS updates should be
+ // required, so the NCR queue should be empty.
+ testProcessMessage(DHCPV6_RENEW, "myhost.example.com",
+ "myhost.example.com.");
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Tests that renewals using the same domain name but differing values for
+// the directional update flags result in NCRs or not, accordingly.
+// If the new leases's flags are the same as the previous lease's flags,
+// then no requests should be generated. If at lease one of the new lease's
+// flags differ from the previous lease, then:
+// A: A removal NCR should be created based on the previous leases's flags
+// if at least one of those flags are true
+// B: An add NCR should be created based on the new lease's flags, if at
+// least one of those flags are true
+TEST_F(FqdnDhcpv6SrvTest, processRequestRenewFqdnFlags) {
+ // Create a Request message with FQDN option but with N flag = 1, which
+ // means no updates should be done. This should result in no NCRs.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", Option6ClientFqdn::FLAG_N);
+ // Queue should be empty.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+
+ // Now renew with Both N and S = 0. This means the server should only
+ // do reverse updates and should result in a reverse-only NCR.
+ testProcessMessage(DHCPV6_RENEW, "myhost.example.com",
+ "myhost.example.com.", 0);
+ // We should a only have reverse-only ADD, no remove.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, false,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // Renew again with the same flags, this should not generate any NCRs.
+ testProcessMessage(DHCPV6_RENEW, "myhost.example.com",
+ "myhost.example.com.", 0);
+ // Queue should be empty.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+
+ // Renew with both N and S flags = 0. This tells the server to update
+ // both directions, which should change forward flag to true. This should
+ // generate a reverse only remove and a dual add.
+ testProcessMessage(DHCPV6_RENEW, "myhost.example.com",
+ "myhost.example.com.", Option6ClientFqdn::FLAG_S);
+
+ // We need the lease for the expiration value.
+ lease_ = LeaseMgrFactory::
+ instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(lease_);
+
+ // We should have two NCRs, one remove and one add.
+ ASSERT_EQ(2, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, false,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ lease_->cltt_, lease_->valid_lft_);
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, lease_->valid_lft_);
+
+ // Lastly, we renew with the N flag = 1 (which means no updates) so we
+ // should have a dual direction remove NCR but NO add NCR.
+ testProcessMessage(DHCPV6_RENEW, "myhost.example.com",
+ "myhost.example.com.", Option6ClientFqdn::FLAG_N);
+ // We should only have the removal NCR.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ lease_->cltt_, lease_->valid_lft_);
+}
+
+TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
+ CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setFlushReclaimedTimerWaitTime(0);
+ CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setHoldReclaimedTime(0);
+
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+
+ // The lease should have been recorded in the database.
+ lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(lease_);
+
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, lease_->valid_lft_);
+
+ // Client may send Release message. In this case the lease should be
+ // removed and all existing DNS entries for this lease should also
+ // be removed. Therefore, we expect that single NameChangeRequest to
+ // remove DNS entries is generated.
+ testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com",
+ "otherhost.example.com.");
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ lease_->cltt_, lease_->valid_lft_);
+}
+
+TEST_F(FqdnDhcpv6SrvTest, processRequestReleaseNoDelete) {
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+
+ // The lease should have been recorded in the database.
+ lease_ = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(lease_);
+
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, lease_->valid_lft_);
+
+ // Client may send Release message. In this case the lease should be
+ // expired and no NameChangeRequest to remove DNS entries is generated.
+ testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com",
+ "otherhost.example.com.");
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+}
+
+// Checks that the server include DHCPv6 Client FQDN option in its
+// response even when client doesn't request this option using ORO.
+TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
+ // The last parameter disables use of the ORO to request FQDN option
+ // In this case, we expect that the FQDN option will not be included
+ // in the server's response. The testProcessMessage will check that.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", Option6ClientFqdn::FLAG_S,
+ IOAddress("2001:db8:1:1::dead:beef"), false);
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+}
+
+// Checks that FQDN is generated from an ip address, when client sends an empty
+// FQDN.
+TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
+ testProcessMessage(DHCPV6_REQUEST, "",
+ "myhost-2001-db8-1-1--dead-beef.example.com.",
+ Option6ClientFqdn::FLAG_S,
+ IOAddress("2001:db8:1:1::dead:beef"), false);
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201C905E54BE12DE6AF92ADE72752B9F362"
+ "13B5A8BC9D217548CD739B4CF31AFB1B",
+ 0, 4000);
+}
+
+// Checks that when the server reuses expired lease, the NameChangeRequest
+// is generated to remove the DNS mapping for the expired lease and second
+// NameChangeRequest to add a DNS mapping for a new lease.
+TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
+ // This address will be used throughout the test.
+ IOAddress addr("2001:db8:1:1::dead:beef");
+ // We are going to configure a subnet with a pool that consists of
+ // exactly one address. This address will be handed out to the
+ // client, will get expired and then be reused.
+ CfgMgr::instance().clear();
+ subnet_ = Subnet6::create(IOAddress("2001:db8:1:1::"),
+ 56, 1, 2, 3, 4, SubnetID(10));
+ subnet_->setIface("eth0");
+ subnet_->setDdnsSendUpdates(true);
+
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr));
+ subnet_->addPool(pool_);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ CfgMgr::instance().commit();
+
+ // Enable D2.
+ enableD2();
+
+ // Allocate a lease.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.");
+ // Test that the appropriate NameChangeRequest has been generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ // Get the lease acquired.
+ Lease6Ptr lease =
+ LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(lease);
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, lease_->valid_lft_);
+ // One of the following: IAID, DUID or subnet identifier has to be changed
+ // because otherwise the allocation engine will treat the lease as
+ // being renewed by the same client. If we at least change subnet identifier
+ // the lease will be treated as expired lease to be reused.
+ ++lease->subnet_id_;
+
+ // Move the cllt back in time and make sure that the lease got expired.
+ lease->cltt_ = time(NULL) - 10;
+ lease->valid_lft_ = 5;
+ ASSERT_TRUE(lease->expired());
+ // Change the hostname so as the name change request for removing existing
+ // DNS mapping is generated.
+ lease->hostname_ = "otherhost.example.com.";
+ // Update the lease in the lease database.
+ LeaseMgrFactory::instance().updateLease6(lease);
+
+ // Simulate another lease acquisition. Since, our pool consists of
+ // exactly one address and this address is used by the lease in the
+ // lease database, it is guaranteed that the allocation engine will
+ // reuse this lease.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.",
+ "myhost.example.com.");
+ ASSERT_EQ(2, d2_mgr_.getQueueSize());
+ // The first name change request generated, should remove a DNS
+ // mapping for an expired lease.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201D422AA463306223D269B6CB7AFE7AAD2"
+ "65FCEA97F93623019B2E0D14E5323D5A",
+ lease->cltt_, lease->valid_lft_);
+ // The second name change request should add a DNS mapping for
+ // a new lease.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52", 0, 4);
+}
+
+TEST_F(FqdnDhcpv6SrvTest, processClientDelegation) {
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", 0);
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, false,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+}
+
+// Verify that the host reservation is found and used. Lease host name and
+// FQDN should be the reservation hostname suffixed by the qualifying suffix.
+TEST_F(FqdnDhcpv6SrvTest, hostnameReservationSuffix) {
+ isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+ string config_str = "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+ " \"interface\" : \"eth0\" , "
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"" + duid_->toText() + "\","
+ " \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+ " \"hostname\": \"alice\""
+ " }"
+ " ]"
+ " } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"qualifying-suffix\" : \"example.com\" }"
+ "}";
+
+ configure(config_str);
+
+ // Update subnet_ and pool_ members after config
+ setSubnetAndPool(0, 0, Lease::TYPE_NA);
+
+ ASSERT_NO_THROW(srv_->startD2());
+
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Verify that the host reservation is found and lease name/FQDN are
+ // formed properly from the host name and qualifying suffix.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "alice.example.com.", 1, IOAddress("2001:db8:1:1::babe"));
+
+ // Verify that NameChangeRequest is correct.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::babe",
+ "000201E2EB74FB53A5778E74AFD43870ECA5"
+ "4150B1F52B0CFED434802DA1259D6D3CA4",
+ 0, 4000, "alice.example.com.");
+}
+
+// Verify that the host reservation is found and used, rather than dynamic
+// Address. Lease host name and FQDN should be the reservation hostname
+// without a qualifying suffix.
+TEST_F(FqdnDhcpv6SrvTest, hostnameReservationNoSuffix) {
+ isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+ string config_str = "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+ " \"interface\" : \"eth0\" , "
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"" + duid_->toText() + "\","
+ " \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+ " \"hostname\": \"alice.example.com\""
+ " }"
+ " ]"
+ " } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"qualifying-suffix\" : \"\" }"
+ "}";
+
+ configure(config_str);
+ // Update subnet_ and pool_ members after config
+ setSubnetAndPool(0, 0, Lease::TYPE_NA);
+
+ ASSERT_NO_THROW(srv_->startD2());
+
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "alice.example.com.", 1,
+ IOAddress("2001:db8:1:1::babe"));
+
+ // Verify that NameChangeRequest is correct.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::babe",
+ "000201E2EB74FB53A5778E74AFD43870ECA5"
+ "4150B1F52B0CFED434802DA1259D6D3CA4",
+ 0, 4000, "alice.example.com.");
+}
+
+TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) {
+ isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+ string config_str = "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+ " \"interface\" : \"eth0\" , "
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"" + duid_->toText() + "\","
+ " \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+ " \"hostname\": \"alice\""
+ " }"
+ " ]"
+ " } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : false, "
+ " \"qualifying-suffix\" : \"disabled.example.com\" }"
+ "}";
+
+ configure(config_str);
+
+ // Update subnet_ and pool_ members after config
+ setSubnetAndPool(0, 0, Lease::TYPE_NA);
+
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "alice.disabled.example.com.", 0,
+ IOAddress("2001:db8:1:1::babe"));
+}
+
+// Verifies that the replace-client-name behavior is correct for each of
+// the supported modes.
+TEST_F(FqdnDhcpv6SrvTest, replaceClientNameModeTest) {
+ isc::dhcp::test::IfaceMgrTestConfig test_config(true);
+
+ testReplaceClientNameMode("never",
+ CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
+
+ testReplaceClientNameMode("never",
+ CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
+
+ testReplaceClientNameMode("always",
+ CLIENT_NAME_NOT_PRESENT, NAME_REPLACED);
+ testReplaceClientNameMode("always",
+ CLIENT_NAME_PRESENT, NAME_REPLACED);
+
+ testReplaceClientNameMode("when-present",
+ CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
+ testReplaceClientNameMode("when-present",
+ CLIENT_NAME_PRESENT, NAME_REPLACED);
+
+ testReplaceClientNameMode("when-not-present",
+ CLIENT_NAME_NOT_PRESENT, NAME_REPLACED);
+ testReplaceClientNameMode("when-not-present",
+ CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
+}
+
+// Verifies that setting hostname-char-set sanitizes FQDN option
+// values received from clients.
+TEST_F(FqdnDhcpv6SrvTest, sanitizeFqdn) {
+ // Verify a full FQDN with no invalid chars is left alone
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com.", false);
+
+ // Verify that a partial FQDN with no invalid chars is left alone
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+ "myhost",
+ Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com.", false);
+
+ // Verify that a full FQDN with invalid chars is cleaned.
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+ "m%y*host.example.com",
+ Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
+ "mxyxhost.example.com.", false);
+
+ // Verify that a partial FQDN with invalid chars is cleaned.
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+ "m%y*host",
+ Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+ "mxyxhost.example.com.", false);
+
+ // Verify that a full FQDN with nul chars is cleaned.
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
+ std::string("m\000yhost.exa\000mple.com", 20),
+ Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
+ "mxyhost.exaxmple.com.", false);
+}
+
+// Verifies that scoped ddns-parameter handling.
+// Specifically that D2 can be enabled with sending updates
+// disabled globally, and enabled at the subnet level.
+TEST_F(FqdnDhcpv6SrvTest, ddnsScopeTest) {
+ std::string config_str =
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"preferred-lifetime\": 3000,\n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000,\n"
+ "\"ddns-send-updates\": false,\n"
+ "\"subnet6\": [ {\n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8:1::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db8:2::/48\",\n"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],\n"
+ " \"interface\": \"eth1\",\n"
+ " \"ddns-send-updates\": true\n"
+ " } ],\n"
+ "\"valid-lifetime\": 4000,\n"
+ " \"dhcp-ddns\" : {\n"
+ " \"enable-updates\" : true\n"
+ " }\n"
+ "}";
+
+ Dhcp6Client client1;
+ client1.setInterface("eth0");
+
+ // Load a configuration with D2 enabled
+ ASSERT_NO_FATAL_FAILURE(configure(config_str, *client1.getServer()));
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client1.getServer()->startD2());
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client1.useFQDN(Option6ClientFqdn::FLAG_S, "one.example.org.",
+ Option6ClientFqdn::FULL));
+
+ // Now send the DHCPREQUEST with including the FQDN option.
+ ASSERT_NO_THROW(client1.doSARR());
+ Pkt6Ptr resp = client1.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ Option6ClientFqdnPtr fqdn;
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("one.example.org.", fqdn->getDomainName());
+
+ // ddns-send-updates for subnet 1 should be off, so we should NOT have an NRC.
+ ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+
+ // Now let's try with a client on subnet 2.
+ Dhcp6Client client2;
+ client2.setInterface("eth1");
+ client2.requestAddress();
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client2.useFQDN(Option6ClientFqdn::FLAG_S, "two.example.org.",
+ Option6ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(client2.doSARR());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("two.example.org.", fqdn->getDomainName());
+
+ Subnet6Ptr subnet = (CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getSubnet(2));
+ ASSERT_TRUE(subnet);
+ DdnsParamsPtr p = (CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet));
+ ASSERT_TRUE(p->getEnableUpdates());
+
+ // ddns-send-updates for subnet 2 are enabled, verify the NCR is correct.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:2::1",
+ "", 0, 4000);
+}
+
+// Verifies that the DDNS parameters used for a lease in subnet in
+// a shared-network belong to lease's subnet. This checks that we
+// get the right results even when the allocation engine changes the
+// subnet chosen. The configuration is two 1-address pool subnets in
+// a shared-network. The first will do a SARR, which consumes the first
+// pool. This should cause the allocation engine to dynamically select
+// the second subnet for the second client. The subnets define different
+// values for qualifying suffixes, thus making it simple to verify
+// the appropriate subnet parameters are used. Both clients then
+// renew their leases.
+TEST_F(FqdnDhcpv6SrvTest, ddnsSharedNetworkTest) {
+ std::string config_str =
+ "{ \"interfaces-config\": { \n"
+ " \"interfaces\": [ \"*\" ] \n"
+ "}, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"shared-networks\": [ \n"
+ "{ \n"
+ "\"name\": \"frog\", \n"
+ "\"interface\": \"eth0\", \n"
+ "\"subnet6\": [ { \n"
+ "\"id\": 1, \n"
+ "\"subnet\": \"2001:db8:1::/64\", \n"
+ "\"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::1\" } ], \n"
+ "\"interface\": \"eth0\", \n"
+ "\"ddns-qualifying-suffix\": \"one.example.com.\" \n"
+ " }, \n"
+ " { \n"
+ "\"id\": 2, \n"
+ "\"subnet\": \"2001:db8:2::/64\", \n"
+ "\"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::1\" } ], \n"
+ "\"interface\": \"eth0\", \n"
+ "\"ddns-qualifying-suffix\": \"two.example.com.\" \n"
+ " } ] \n"
+ "} ], \n"
+ "\"ddns-send-updates\": true, \n"
+ "\"dhcp-ddns\" : { \n"
+ " \"enable-updates\" : true \n"
+ " } \n"
+ "}";
+
+ Dhcp6Client client1;
+ client1.setInterface("eth0");
+ client1.requestAddress();
+
+ // Load a configuration with D2 enabled
+ ASSERT_NO_FATAL_FAILURE(configure(config_str, *client1.getServer()));
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client1.getServer()->startD2());
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client1.useFQDN(Option6ClientFqdn::FLAG_S, "client1",
+ Option6ClientFqdn::PARTIAL));
+
+ // Now do a SARR.
+ ASSERT_NO_THROW(client1.doSARR());
+ Pkt6Ptr resp = client1.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ Option6ClientFqdnPtr fqdn;
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("client1.one.example.com.", fqdn->getDomainName());
+
+ // ddns-send-updates for subnet 1 are enabled, verify the NCR is correct.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1::1",
+ "", 0, 4000, "client1.one.example.com.");
+
+ // Make sure the lease hostname and fqdn flags are correct.
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client1.one.example.com.", lease->hostname_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+
+ // Now let's try with a different client. Subnet 1 is full so we should get an
+ // address from subnet 2.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth0");
+ client2.requestAddress();
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client2.useFQDN(Option6ClientFqdn::FLAG_S, "client2",
+ Option6ClientFqdn::PARTIAL));
+
+ // Do a SARR.
+ ASSERT_NO_THROW(client2.doSARR());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("client2.two.example.com.", fqdn->getDomainName());
+
+ // ddns-send-updates for subnet 2 are enabled, verify the NCR is correct.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:2::1",
+ "", 0, 4000, "client2.two.example.com.");
+
+ // Make sure the lease hostname and fqdn flags are correct.
+ lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client2.two.example.com.", lease->hostname_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+
+ // Now let's check Renewals
+ // First we'll renew a client2.
+ ASSERT_NO_THROW(client2.doRenew());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("client2.two.example.com.", fqdn->getDomainName());
+
+ // ddns-send-updates for subnet 2 are enabled, but currently a renew/rebind does
+ // not update, unless the FQDN or flags change.
+ ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+
+ // Make sure the lease hostname is still correct.
+ lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client2.two.example.com.", lease->hostname_);
+
+ // Next we'll renew client1
+ ASSERT_NO_THROW(client1.doRenew());
+ resp = client1.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("client1.one.example.com.", fqdn->getDomainName());
+
+ // ddns-send-updates for subnet 1 are enabled, but currently a renew/rebind does
+ // not update, unless the FQDN or flags change.
+ ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+
+ // Make sure the lease hostname and fqdn flags are correct.
+ lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client1.one.example.com.", lease->hostname_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+}
+
+// Verifies lease and NCR content (or lack of NCRs) are correct when
+// subnets in a shared-network define different values for send-ddns-updates
+// This checks that we get the right results even when the allocation engine
+// changes the subnet chosen. The configuration is two 1-address pool subnets in
+// a shared-network. The first client will do a SARR, which consumes the first
+// pool. This should cause the allocation engine to dynamically select
+// the second subnet for the second client.
+TEST_F(FqdnDhcpv6SrvTest, ddnsSharedNetworkTest2) {
+ std::string config_str =
+ "{ \"interfaces-config\": { \n"
+ " \"interfaces\": [ \"*\" ] \n"
+ "}, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"shared-networks\": [ \n"
+ "{ \n"
+ "\"name\": \"frog\", \n"
+ "\"interface\": \"eth0\", \n"
+ "\"subnet6\": [ { \n"
+ "\"id\": 1, \n"
+ "\"subnet\": \"2001:db8:1::/64\", \n"
+ "\"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::1\" } ], \n"
+ "\"interface\": \"eth0\", \n"
+ "\"ddns-qualifying-suffix\": \"one.example.com.\", \n"
+ "\"ddns-send-updates\": true \n"
+ " }, \n"
+ " { \n"
+ "\"id\": 2, \n"
+ "\"subnet\": \"2001:db8:2::/64\", \n"
+ "\"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::1\" } ], \n"
+ "\"interface\": \"eth0\", \n"
+ "\"ddns-qualifying-suffix\": \"two.example.com.\", \n"
+ "\"ddns-send-updates\": false \n"
+ " } ] \n"
+ "} ], \n"
+ "\"dhcp-ddns\" : { \n"
+ " \"enable-updates\" : true \n"
+ " } \n"
+ "}";
+
+ Dhcp6Client client1;
+ client1.setInterface("eth0");
+ client1.requestAddress();
+
+ // Load a configuration with D2 enabled
+ ASSERT_NO_FATAL_FAILURE(configure(config_str, *client1.getServer()));
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client1.getServer()->startD2());
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client1.useFQDN(Option6ClientFqdn::FLAG_S, "client1",
+ Option6ClientFqdn::PARTIAL));
+
+ // Now do a SARR.
+ ASSERT_NO_THROW(client1.doSARR());
+ Pkt6Ptr resp = client1.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ Option6ClientFqdnPtr fqdn;
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("client1.one.example.com.", fqdn->getDomainName());
+
+ // ddns-send-updates for subnet 1 are enabled, verify the NCR is correct.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, "2001:db8:1::1",
+ "", 0, 4000, "client1.one.example.com.");
+
+ // Make sure the lease hostname and fdqn flags are correct.
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client1.one.example.com.", lease->hostname_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+
+ // Now let's try with a different client. Subnet 1 is full so we should get an
+ // address from subnet 2.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth0");
+ client2.requestAddress();
+
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client2.useFQDN(Option6ClientFqdn::FLAG_S, "client2",
+ Option6ClientFqdn::PARTIAL));
+
+ // Do a SARR.
+ ASSERT_NO_THROW(client2.doSARR());
+ resp = client2.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPV6_REPLY, static_cast<int>(resp->getType()));
+
+ // Check that the response FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(resp->getOption(D6O_CLIENT_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("client2.two.example.com.", fqdn->getDomainName());
+
+ // ddns-send-updates for subnet 2 are disabled, verify there is no NCR.
+ ASSERT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+
+ // Make sure the lease hostname and fdqn flags are correct.
+ lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("client2.two.example.com.", lease->hostname_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+}
+
+// Verifies that renews only generate NCRs if the situation dictates
+// that it should. It checks:
+//
+// -# enable-updates true or false
+// -# update-on-renew true or false
+// -# Whether or not the FQDN has changed between old and new lease
+TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
+ std::string fqdn1 = "one.example.com.";
+ std::string fqdn2 = "two.example.com.";
+ struct Scenario {
+ std::string description_;
+ bool send_updates_;
+ bool update_on_renew_;
+ std::string old_fqdn_;
+ std::string new_fqdn_;
+ size_t remove_;
+ size_t add_;
+ };
+
+ // Mnemonic constants.
+ const bool send_updates = true;
+ const bool update_on_renew = true;
+ const size_t remove = 1;
+ const size_t add = 1;
+
+ const std::vector<Scenario> scenarios = {
+ {
+ "#1 update-on-renew false, no change in fqdn",
+ send_updates, !update_on_renew, fqdn1, fqdn1, !remove, !add
+ },
+ {
+ "#2 update-on-renew is false, change in fqdn",
+ send_updates, !update_on_renew, fqdn1, fqdn2, remove, add
+ },
+ {
+ "#3 update-on-renew is true, no change in fqdn",
+ send_updates, update_on_renew, fqdn1, fqdn1, remove, add
+ },
+ {
+ "#4 update-on-renew is true, change in fqdn",
+ send_updates, update_on_renew, fqdn1, fqdn2, remove, add
+ },
+ // All prior scenarios test with send-updates true. We really
+ // only need one with it false.
+ {
+ "#5 send-updates false, update-on-renew is true, change in fqdn",
+ !send_updates, update_on_renew, fqdn1, fqdn2, !remove, !add
+ }
+ };
+
+ enableD2();
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+
+ // Iterate over test scenarios.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_); {
+ // Make sure the lease does not exist.
+ ASSERT_FALSE(LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef")));
+ // Set and verify DDNS params flags
+ subnet_->setDdnsSendUpdates(scenario.send_updates_);
+ subnet_->setDdnsUpdateOnRenew(scenario.update_on_renew_);
+
+ ASSERT_EQ(scenario.send_updates_, getDdnsParams()->getEnableUpdates());
+ ASSERT_EQ(scenario.update_on_renew_, getDdnsParams()->getUpdateOnRenew());
+
+ // Create the "old" lease
+ testProcessMessage(DHCPV6_REQUEST, scenario.old_fqdn_, scenario.old_fqdn_);
+
+ // The lease should have been recorded in the database.
+ Lease6Ptr old_lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(old_lease);
+
+ if (!scenario.send_updates_ || scenario.old_fqdn_.empty()) {
+ // We should not have an NCR.
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+ } else {
+ // We should have an NCR add.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ old_lease->addr_.toText(), "",
+ 0, old_lease->valid_lft_,
+ old_lease->hostname_);
+ }
+
+ // Now let's renew (or create) the lease.
+ testProcessMessage(DHCPV6_RENEW, scenario.new_fqdn_, scenario.new_fqdn_);
+
+ // The lease should have been recorded in the database.
+ Lease6Ptr new_lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1:1::dead:beef"));
+ ASSERT_TRUE(new_lease);
+
+ // Verify queue count is correct.
+ ASSERT_EQ((scenario.remove_ + scenario.add_), d2_mgr_.getQueueSize());
+
+ // If we expect a remove, check it.
+ if (scenario.remove_ > 0) {
+ // Verify NCR content
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ old_lease->addr_.toText(), "",
+ 0, old_lease->valid_lft_,
+ old_lease->hostname_);
+ }
+
+ // If we expect an add, check it.
+ if (scenario.add_ > 0) {
+ // Verify NCR content
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ new_lease->addr_.toText(), "",
+ 0, new_lease->valid_lft_,
+ new_lease->hostname_);
+ }
+
+ // Now delete the lease.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(new_lease));
+ ASSERT_TRUE(deleted);
+ }
+ }
+}
+
+// Verify that when specified ddns-ttl-percent is used to calculate
+// the lease length in an NCR.
+TEST_F(FqdnDhcpv6SrvTest, ddnsTtlPercent) {
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY);
+
+ // Create an IA.
+ addIA(1234, IOAddress("2001:db8:1::1"), answer);
+
+ // Use domain name in upper case. It should be converted to lower-case
+ // before DHCID is calculated. So, we should get the same result as if
+ // we typed domain name in lower-case.
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "MYHOST.EXAMPLE.COM",
+ Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
+
+ // Create NameChangeRequest for the first allocated address.
+ AllocEngine::ClientContext6 ctx;
+ subnet_->setDdnsUseConflictResolution(false);
+ subnet_->setDdnsTtlPercent(Optional<double>(0.10));
+ ctx.subnet_ = subnet_;
+ ctx.fwd_dns_update_ = ctx.rev_dns_update_ = true;
+ ASSERT_NO_THROW(srv_->createNameChangeRequests(answer, ctx));
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ // Verify that NameChangeRequest is correct.
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 500, "", false, subnet_->getDdnsTtlPercent());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/get_config_unittest.cc b/src/bin/dhcp6/tests/get_config_unittest.cc
new file mode 100644
index 0000000..c22f4a7
--- /dev/null
+++ b/src/bin/dhcp6/tests/get_config_unittest.cc
@@ -0,0 +1,11137 @@
+// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
+#include <testutils/user_context_utils.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/get_config_unittest.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <list>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+
+namespace {
+
+/// @name How to fill configurations
+///
+/// Copy get_config_unittest.cc.skel into get_config_unittest.cc
+///
+/// For the extracted configurations define the EXTRACT_CONFIG and
+/// recompile this file. Run dhcp6_unittests on Dhcp6ParserTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp6_unittests --gtest_filter="Dhcp6Parser*" > /dev/null 2> x
+/// @endcode
+///
+/// Update EXTRACTED_CONFIGS with the file content
+///
+/// When configurations have been extracted the corresponding unparsed
+/// configurations must be generated. To do that define GENERATE_ACTION
+/// and recompile this file. Run dhcp6_unittests on Dhcp6GetConfigTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp6_unittests --gtest_filter="Dhcp6GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update UNPARSED_CONFIGS with the file content, recompile this file
+/// without EXTRACT_CONFIG and GENERATE_ACTION.
+///
+/// @note Check for failures at each step!
+/// @note The tests of this file do not check if configs returned
+/// by @ref isc::dhcp::CfgToElement::ToElement() are complete.
+/// This has to be done manually.
+///
+///@{
+/// @brief extracted configurations
+const char* EXTRACTED_CONFIGS[] = {
+ // CONFIGURATION 0
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [ ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 1
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"max-preferred-lifetime\": 4000,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-preferred-lifetime\": 2000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 2
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1024,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 100,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:3::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 34,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:4::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 3
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 2,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 3,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:3::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 4,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:4::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 4
+"{\n"
+" \"compatibility\": {\n"
+" \"lenient-option-parsing\": true\n"
+" },\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 5
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"max-preferred-lifetime\": 4000,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-preferred-lifetime\": 2000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 4,\n"
+" \"max-valid-lifetime\": 5,\n"
+" \"min-preferred-lifetime\": 2,\n"
+" \"min-valid-lifetime\": 3,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3,\n"
+" \"rebind-timer\": 2,\n"
+" \"renew-timer\": 1,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 6
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"interface\": \"eth0\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 7
+"{\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"interface-id\": \"foobar\",\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 8
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/96\"\n"
+" },\n"
+" {\n"
+" \"pool\": \"2001:db8:1:0:abcd::/112\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 2,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:2::1 - 2001:db8:2::ff\"\n"
+" },\n"
+" {\n"
+" \"pool\": \"2001:db8:2::300 - 2001:db8:2::3ff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 9
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 10
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 128,\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 64\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 11
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"excluded-prefix\": \"3000:0:0:0:1000::\",\n"
+" \"excluded-prefix-len\": 72,\n"
+" \"prefix\": \"3000::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 12
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 80,\n"
+" \"prefix\": \"2001:db8:1:01::\",\n"
+" \"prefix-len\": 72\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 88,\n"
+" \"prefix\": \"2001:db8:1:02::\",\n"
+" \"prefix-len\": 72\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 96,\n"
+" \"prefix\": \"3000:1:03::\",\n"
+" \"prefix-len\": 72\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1:04::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/40\",\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 13
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 64\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 14
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv6-address\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 15
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"uint16, ipv4-address, ipv6-address, string\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"record\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 16
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"code\": 101,\n"
+" \"name\": \"foo-2\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 17
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": true,\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 18
+"{\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"sub-opts-space\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 19
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\"\n"
+" },\n"
+" {\n"
+" \"data\": \"01\",\n"
+" \"name\": \"preference\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 20
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\"\n"
+" },\n"
+" {\n"
+" \"data\": \"01\",\n"
+" \"name\": \"preference\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 21
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\"\n"
+" },\n"
+" {\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 38,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 22
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" },\n"
+" {\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 110,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"code\": 111,\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 23
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"11\",\n"
+" \"name\": \"base-option\"\n"
+" },\n"
+" {\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" },\n"
+" {\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"isc\",\n"
+" \"name\": \"base-option\",\n"
+" \"space\": \"dhcp6\",\n"
+" \"type\": \"uint8\"\n"
+" },\n"
+" {\n"
+" \"code\": 110,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"code\": 111,\n"
+" \"name\": \"foo2\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 24
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"0102030405060708090A\",\n"
+" \"name\": \"subscriber-id\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 2,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"FFFEFDFCFB\",\n"
+" \"name\": \"user-class\"\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 25
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"112233445566\",\n"
+" \"name\": \"subscriber-id\"\n"
+" }\n"
+" ],\n"
+" \"prefix\": \"3000::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"aabbccddee\",\n"
+" \"name\": \"user-class\"\n"
+" }\n"
+" ],\n"
+" \"prefix\": \"3001::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"0102030405060708090A\",\n"
+" \"name\": \"subscriber-id\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"2001:db8:1::10 - 2001:db8:1::100\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"FFFEFDFCFB\",\n"
+" \"name\": \"user-class\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"2001:db8:1::300 - 2001:db8:1::400\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 26
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"option-one\",\n"
+" \"space\": \"vendor-4491\"\n"
+" },\n"
+" {\n"
+" \"code\": 100,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"option-two\",\n"
+" \"space\": \"vendor-1234\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 27
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"data\": \"this is a string vendor-opt\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-4491\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"vendor-4491\",\n"
+" \"type\": \"string\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 28
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"eth0\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 29
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"eth0\", \"eth1\", \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 30
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-address\": \"2001:db8:1::abcd\"\n"
+" },\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 31
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ \"2001:db9::abcd\", \"2001:db9::abce\" ]\n"
+" },\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 32
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"id\": 2,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"id\": 3,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:3::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 4,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:4::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 33
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" },\n"
+" {\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:0::/40\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 34
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:2::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:3::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8:4::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 35
+"{\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": true,\n"
+" \"max-queue-size\": 2048,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"3001::2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"3001::1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 36
+"{\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": true,\n"
+" \"generated-prefix\": \"test.prefix\",\n"
+" \"hostname-char-replacement\": \"x\",\n"
+" \"hostname-char-set\": \"[^A-Z]\",\n"
+" \"max-queue-size\": 2048,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"override-client-update\": true,\n"
+" \"override-no-update\": true,\n"
+" \"qualifying-suffix\": \"test.suffix.\",\n"
+" \"replace-client-name\": \"when-present\",\n"
+" \"sender-ip\": \"3001::2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"3001::1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 37
+"{\n"
+" \"ddns-generated-prefix\": \"global.prefix\",\n"
+" \"ddns-override-client-update\": true,\n"
+" \"ddns-override-no-update\": true,\n"
+" \"ddns-qualifying-suffix\": \"global.suffix.\",\n"
+" \"ddns-replace-client-name\": \"always\",\n"
+" \"ddns-send-updates\": false,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": true,\n"
+" \"generated-prefix\": \"d2.prefix\",\n"
+" \"hostname-char-replacement\": \"z\",\n"
+" \"hostname-char-set\": \"[^0-9]\",\n"
+" \"max-queue-size\": 2048,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"override-client-update\": false,\n"
+" \"override-no-update\": false,\n"
+" \"qualifying-suffix\": \"d2.suffix.\",\n"
+" \"replace-client-name\": \"when-present\",\n"
+" \"sender-ip\": \"3001::2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"3001::1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"hostname-char-replacement\": \"x\",\n"
+" \"hostname-char-set\": \"[^A-Z]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 38
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 123,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"reservations\": [ ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 234,\n"
+" \"pools\": [ ],\n"
+" \"reservations\": [\n"
+" {\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0A\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"2001:db8:2::1111\",\n"
+" \"name\": \"dns-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"11\",\n"
+" \"name\": \"preference\"\n"
+" }\n"
+" ]\n"
+" },\n"
+" {\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"2001:db8:2::abbc\",\n"
+" \"name\": \"dns-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"25\",\n"
+" \"name\": \"preference\"\n"
+" }\n"
+" ]\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 542,\n"
+" \"pools\": [ ],\n"
+" \"reservations\": [\n"
+" {\n"
+" \"duid\": \"0A:09:08:07:06:05:04:03:02:01\",\n"
+" \"hostname\": \"\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"2001:db8:3::3333\",\n"
+" \"name\": \"dns-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"33\",\n"
+" \"name\": \"preference\"\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ \"2001:db8:3:2::/96\" ]\n"
+" },\n"
+" {\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"06:05:04:03:02:01\",\n"
+" \"prefixes\": [ \"2001:db8:3:1::/96\" ]\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:3::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 39
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 234,\n"
+" \"pools\": [ ],\n"
+" \"reservations\": [\n"
+" {\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0A\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"11\",\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ]\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 40
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"mac-sources\": [ \"rfc6939\", \"rfc4649\", \"rfc4580\" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [ ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 41
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"mac-sources\": [ \"client-link-addr-option\", \"remote-id\", \"subscriber-id\" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [ ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 42
+"{\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"subnet\": \"2001:db8:1::/48\"\n"
+" },\n"
+" {\n"
+" \"id\": 2,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:2::/64\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"subnet\": \"2001:db8:2::/48\"\n"
+" },\n"
+" {\n"
+" \"id\": 3,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:3::/64\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": false,\n"
+" \"subnet\": \"2001:db8:3::/48\"\n"
+" },\n"
+" {\n"
+" \"id\": 4,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:4::/64\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": false,\n"
+" \"subnet\": \"2001:db8:4::/48\"\n"
+" },\n"
+" {\n"
+" \"id\": 5,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:5::/64\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:5::/48\"\n"
+" },\n"
+" {\n"
+" \"id\": 6,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:6::/64\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"subnet\": \"2001:db8:6::/48\"\n"
+" },\n"
+" {\n"
+" \"id\": 7,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:7::/64\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"subnet\": \"2001:db8:7::/48\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 43
+"{\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"subnet\": \"2001:db8:1::/48\"\n"
+" },\n"
+" {\n"
+" \"id\": 2,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:2::/64\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:2::/48\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 44
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"dns-servers\", \"remote-id\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [ ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 45
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet6\": [ ]\n"
+" }\n",
+ // CONFIGURATION 46
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet6\": [ ]\n"
+" }\n",
+ // CONFIGURATION 47
+"{\n"
+" \"decline-probation-period\": 12345,\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet6\": [ ]\n"
+" }\n",
+ // CONFIGURATION 48
+"{\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 35,\n"
+" \"hold-reclaimed-time\": 1800,\n"
+" \"max-reclaim-leases\": 50,\n"
+" \"max-reclaim-time\": 100,\n"
+" \"reclaim-timer-wait-time\": 20,\n"
+" \"unwarned-reclaim-cycles\": 10\n"
+" },\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet6\": [ ]\n"
+" }\n",
+ // CONFIGURATION 49
+"{\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"name\": \"one\"\n"
+" },\n"
+" {\n"
+" \"name\": \"two\"\n"
+" },\n"
+" {\n"
+" \"name\": \"three\"\n"
+" }\n"
+" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 50
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8::/64\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/32\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 51
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8::/64\",\n"
+" \"user-context\": { }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/32\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 52
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8::/64\",\n"
+" \"user-context\": {\n"
+" \"lw4over6-bind-prefix-len\": 56,\n"
+" \"lw4over6-sharing-ratio\": 64,\n"
+" \"lw4over6-sysports-exclude\": true,\n"
+" \"lw4over6-v4-pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/32\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 53
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:: - 2001:db8::ffff:ffff:ffff:ffff\",\n"
+" \"user-context\": {\n"
+" \"lw4over6-bind-prefix-len\": 56,\n"
+" \"lw4over6-sharing-ratio\": 64,\n"
+" \"lw4over6-sysports-exclude\": true,\n"
+" \"lw4over6-v4-pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/32\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 54
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8::\",\n"
+" \"prefix-len\": 56\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/32\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 55
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8::\",\n"
+" \"prefix-len\": 56,\n"
+" \"user-context\": { }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/32\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 56
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db8::\",\n"
+" \"prefix-len\": 56,\n"
+" \"user-context\": {\n"
+" \"lw4over6-bind-prefix-len\": 56,\n"
+" \"lw4over6-sharing-ratio\": 64,\n"
+" \"lw4over6-sysports-exclude\": true,\n"
+" \"lw4over6-v4-pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8::/32\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 57
+"{\n"
+" \"hosts-databases\": [\n"
+" {\n"
+" \"name\": \"keatest1\",\n"
+" \"password\": \"keatest\",\n"
+" \"type\": \"mysql\",\n"
+" \"user\": \"keatest\"\n"
+" },\n"
+" {\n"
+" \"name\": \"keatest2\",\n"
+" \"password\": \"keatest\",\n"
+" \"type\": \"mysql\",\n"
+" \"user\": \"keatest\"\n"
+" }\n"
+" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 58
+"{\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"name\": \"all\",\n"
+" \"test\": \"'' == ''\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"match all\"\n"
+" }\n"
+" },\n"
+" {\n"
+" \"name\": \"none\"\n"
+" },\n"
+" {\n"
+" \"name\": \"both\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"a comment\",\n"
+" \"version\": 1\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"control-socket\": {\n"
+" \"socket-name\": \"/tmp/kea6-ctrl-socket\",\n"
+" \"socket-type\": \"unix\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"Indirect comment\"\n"
+" }\n"
+" },\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"user-context\": {\n"
+" \"comment\": \"No dynamic DNS\"\n"
+" }\n"
+" },\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false,\n"
+" \"user-context\": {\n"
+" \"comment\": \"Use wildcard\"\n"
+" }\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"Set option value\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"code\": 100,\n"
+" \"name\": \"foo\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv6-address\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"An option definition\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"server-id\": {\n"
+" \"type\": \"LL\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"DHCPv6 specific\"\n"
+" }\n"
+" },\n"
+" \"shared-networks\": [\n"
+" {\n"
+" \"name\": \"foo\",\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 100,\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"prefix\": \"2001:db2::\",\n"
+" \"prefix-len\": 48,\n"
+" \"user-context\": {\n"
+" \"comment\": \"A prefix pool\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db1::/64\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"A pool\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"reservations\": [\n"
+" {\n"
+" \"hostname\": \"foo.example.com\",\n"
+" \"hw-address\": \"AA:BB:CC:DD:EE:FF\",\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"example.com\",\n"
+" \"name\": \"domain-search\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"An option in a reservation\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"user-context\": {\n"
+" \"comment\": \"A host reservation\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db1::/48\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"A subnet\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"user-context\": {\n"
+" \"comment\": \"A shared network\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"user-context\": {\n"
+" \"comment\": \"A DHCPv6 server\"\n"
+" }\n"
+" }\n",
+ // CONFIGURATION 59
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0A\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"2001:db8:2::1111\",\n"
+" \"name\": \"dns-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"11\",\n"
+" \"name\": \"preference\"\n"
+" }\n"
+" ]\n"
+" },\n"
+" {\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"data\": \"2001:db8:2::abbc\",\n"
+" \"name\": \"dns-servers\"\n"
+" },\n"
+" {\n"
+" \"data\": \"25\",\n"
+" \"name\": \"preference\"\n"
+" }\n"
+" ]\n"
+" }\n"
+" ],\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 123,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"reservations\": [ ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 234,\n"
+" \"pools\": [ ],\n"
+" \"subnet\": \"2001:db8:2::/64\"\n"
+" },\n"
+" {\n"
+" \"id\": 542,\n"
+" \"pools\": [ ],\n"
+" \"subnet\": \"2001:db8:3::/64\"\n"
+" }\n"
+" ],\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 60
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"renew-timer\": 1000,\n"
+" \"statistic-default-sample-age\": 5,\n"
+" \"statistic-default-sample-count\": 10,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 61
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet6\": [ ]\n"
+" }\n",
+ // CONFIGURATION 62
+"{\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 1024,\n"
+" \"thread-pool-size\": 48\n"
+" },\n"
+" \"subnet6\": [ ]\n"
+" }\n",
+ // CONFIGURATION 63
+"{\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"max-preferred-lifetime\": 6000,\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-preferred-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 1000,\n"
+" \"name\": \"one\",\n"
+" \"preferred-lifetime\": 5000,\n"
+" \"valid-lifetime\": 2000\n"
+" },\n"
+" {\n"
+" \"name\": \"two\"\n"
+" }\n"
+" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ]\n"
+" }\n",
+ // CONFIGURATION 64
+"{\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"max-preferred-lifetime\": 6000,\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-preferred-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 1000,\n"
+" \"name\": \"one\",\n"
+" \"preferred-lifetime\": 5000,\n"
+" \"template-test\": \"''\",\n"
+" \"valid-lifetime\": 2000\n"
+" },\n"
+" {\n"
+" \"name\": \"two\",\n"
+" \"template-test\": \"''\"\n"
+" }\n"
+" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"id\": 1,\n"
+" \"pools\": [\n"
+" {\n"
+" \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"subnet\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ]\n"
+" }\n"
+};
+
+/// @brief unparsed configurations
+const char* UNPARSED_CONFIGS[] = {
+ // CONFIGURATION 0
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 1
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"max-preferred-lifetime\": 4000,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-preferred-lifetime\": 2000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 4000,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-preferred-lifetime\": 2000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 2
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:3::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 34,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:4::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 100,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1024,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 3
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 2,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 3,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:3::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 4,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:4::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 4
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"compatibility\": {\n"
+" \"lenient-option-parsing\": true\n"
+" },\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 5
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"max-preferred-lifetime\": 4000,\n"
+" \"max-valid-lifetime\": 5000,\n"
+" \"min-preferred-lifetime\": 2000,\n"
+" \"min-valid-lifetime\": 3000,\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 4,\n"
+" \"max-valid-lifetime\": 5,\n"
+" \"min-preferred-lifetime\": 2,\n"
+" \"min-valid-lifetime\": 3,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 6
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"interface\": \"eth0\",\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 7
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"interface-id\": \"foobar\",\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 8
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/96\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1:0:abcd::/112\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 2,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::1-2001:db8:2::ff\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::300/120\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 9
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 10
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 128,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 64\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 11
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"excluded-prefix\": \"3000::1000:0:0:0\",\n"
+" \"excluded-prefix-len\": 72,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"3000::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 12
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 80,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:1:1::\",\n"
+" \"prefix-len\": 72\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 88,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:1:2::\",\n"
+" \"prefix-len\": 72\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 96,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"3000:1:3::\",\n"
+" \"prefix-len\": 72\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/40\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 13
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 64\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 14
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv6-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 15
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"uint16, ipv4-address, ipv6-address, string\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"record\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 16
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 101,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo-2\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 17
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": true,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 18
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"sub-opts-space\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 19
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 7,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"preference\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 20
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 7,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"01\",\n"
+" \"name\": \"preference\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 21
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 38,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 22
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 110,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"isc\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 111,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 110,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 111,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo2\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 23
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"11\",\n"
+" \"name\": \"base-option\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 110,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"1234\",\n"
+" \"name\": \"foo\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"isc\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 111,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"192.168.2.1\",\n"
+" \"name\": \"foo2\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"isc\",\n"
+" \"name\": \"base-option\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"dhcp6\",\n"
+" \"type\": \"uint8\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 110,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" },\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 111,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo2\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv4-address\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 24
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"0102030405060708090A\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 2,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 15,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"FFFEFDFCFB\",\n"
+" \"name\": \"user-class\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 25
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"112233445566\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"prefix\": \"3000::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 15,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"AABBCCDDEE\",\n"
+" \"name\": \"user-class\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"prefix\": \"3001::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"0102030405060708090A\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"2001:db8:1::10-2001:db8:1::100\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 15,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"FFFEFDFCFB\",\n"
+" \"name\": \"user-class\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"pool\": \"2001:db8:1::300-2001:db8:1::400\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 26
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"1234\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"vendor-1234\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"vendor-4491\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 27
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"this is a string vendor-opt\",\n"
+" \"name\": \"foo\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"vendor-4491\"\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"vendor-4491\",\n"
+" \"type\": \"string\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 28
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"eth0\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 29
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\", \"eth0\", \"eth1\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 30
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ \"2001:db8:1::abcd\" ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 31
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ \"2001:db9::abcd\", \"2001:db9::abce\" ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 32
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"client-class\": \"alpha\",\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"client-class\": \"beta\",\n"
+" \"id\": 2,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"client-class\": \"gamma\",\n"
+" \"id\": 3,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:3::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 4,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:4::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 33
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/80\"\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:3::/80\"\n"
+" },\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:4::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/40\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 34
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"client-class\": \"alpha\",\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:1::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"beta\",\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:2::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"client-class\": \"gamma\",\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:3::\",\n"
+" \"prefix-len\": 48\n"
+" },\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8:4::\",\n"
+" \"prefix-len\": 48\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 35
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": true,\n"
+" \"max-queue-size\": 2048,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"3001::2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"3001::1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 36
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"test.prefix\",\n"
+" \"ddns-override-client-update\": true,\n"
+" \"ddns-override-no-update\": true,\n"
+" \"ddns-qualifying-suffix\": \"test.suffix.\",\n"
+" \"ddns-replace-client-name\": \"when-present\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": true,\n"
+" \"max-queue-size\": 2048,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"3001::2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"3001::1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"x\",\n"
+" \"hostname-char-set\": \"[^A-Z]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 37
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"global.prefix\",\n"
+" \"ddns-override-client-update\": true,\n"
+" \"ddns-override-no-update\": true,\n"
+" \"ddns-qualifying-suffix\": \"global.suffix.\",\n"
+" \"ddns-replace-client-name\": \"always\",\n"
+" \"ddns-send-updates\": false,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": true,\n"
+" \"max-queue-size\": 2048,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"3001::2\",\n"
+" \"sender-port\": 778,\n"
+" \"server-ip\": \"3001::1\",\n"
+" \"server-port\": 777\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"x\",\n"
+" \"hostname-char-set\": \"[^A-Z]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 38
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 123,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 234,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"2001:db8:2::abbc\",\n"
+" \"name\": \"dns-servers\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 7,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"25\",\n"
+" \"name\": \"preference\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ ]\n"
+" },\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0a\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"2001:db8:2::1111\",\n"
+" \"name\": \"dns-servers\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 7,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"11\",\n"
+" \"name\": \"preference\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ ]\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 542,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"06:05:04:03:02:01\",\n"
+" \"ip-addresses\": [ ],\n"
+" \"option-data\": [ ],\n"
+" \"prefixes\": [ \"2001:db8:3:1::/96\" ]\n"
+" },\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"0a:09:08:07:06:05:04:03:02:01\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-addresses\": [ ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"2001:db8:3::3333\",\n"
+" \"name\": \"dns-servers\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 7,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"33\",\n"
+" \"name\": \"preference\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ \"2001:db8:3:2::/96\" ]\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:3::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 39
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"uint32\"\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 234,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0a\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 100,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"11\",\n"
+" \"name\": \"foo\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"isc\"\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ ]\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 40
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"client-link-addr-option\", \"remote-id\", \"subscriber-id\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 41
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"client-link-addr-option\", \"remote-id\", \"subscriber-id\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 42
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 2,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 3,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:3::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": false,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:3::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 4,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:4::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": false,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:4::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 5,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:5::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:5::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 6,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:6::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:6::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 7,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:7::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"reservations-global\": true,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:7::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 43
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": true,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 2,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:2::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 44
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"23\", \"37\", \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 45
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 46
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 47
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 12345,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 48
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 35,\n"
+" \"hold-reclaimed-time\": 1800,\n"
+" \"max-reclaim-leases\": 50,\n"
+" \"max-reclaim-time\": 100,\n"
+" \"reclaim-timer-wait-time\": 20,\n"
+" \"unwarned-reclaim-cycles\": 10\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 49
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"name\": \"one\",\n"
+" \"option-data\": [ ]\n"
+" },\n"
+" {\n"
+" \"name\": \"two\",\n"
+" \"option-data\": [ ]\n"
+" },\n"
+" {\n"
+" \"name\": \"three\",\n"
+" \"option-data\": [ ]\n"
+" }\n"
+" ],\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 50
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8::/64\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/32\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 51
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8::/64\",\n"
+" \"user-context\": { }\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/32\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 52
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8::/64\",\n"
+" \"user-context\": {\n"
+" \"lw4over6-bind-prefix-len\": 56,\n"
+" \"lw4over6-sharing-ratio\": 64,\n"
+" \"lw4over6-sysports-exclude\": true,\n"
+" \"lw4over6-v4-pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/32\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 53
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8::/64\",\n"
+" \"user-context\": {\n"
+" \"lw4over6-bind-prefix-len\": 56,\n"
+" \"lw4over6-sharing-ratio\": 64,\n"
+" \"lw4over6-sysports-exclude\": true,\n"
+" \"lw4over6-v4-pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/32\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 54
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8::\",\n"
+" \"prefix-len\": 56\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/32\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 55
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8::\",\n"
+" \"prefix-len\": 56,\n"
+" \"user-context\": { }\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/32\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 56
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db8::\",\n"
+" \"prefix-len\": 56,\n"
+" \"user-context\": {\n"
+" \"lw4over6-bind-prefix-len\": 56,\n"
+" \"lw4over6-sharing-ratio\": 64,\n"
+" \"lw4over6-sysports-exclude\": true,\n"
+" \"lw4over6-v4-pool\": \"192.0.2.0/24\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8::/32\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 57
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"hosts-databases\": [\n"
+" {\n"
+" \"name\": \"keatest1\",\n"
+" \"password\": \"keatest\",\n"
+" \"type\": \"mysql\",\n"
+" \"user\": \"keatest\"\n"
+" },\n"
+" {\n"
+" \"name\": \"keatest2\",\n"
+" \"password\": \"keatest\",\n"
+" \"type\": \"mysql\",\n"
+" \"user\": \"keatest\"\n"
+" }\n"
+" ],\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 58
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"name\": \"all\",\n"
+" \"option-data\": [ ],\n"
+" \"test\": \"'' == ''\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"match all\"\n"
+" }\n"
+" },\n"
+" {\n"
+" \"name\": \"none\",\n"
+" \"option-data\": [ ]\n"
+" },\n"
+" {\n"
+" \"name\": \"both\",\n"
+" \"option-data\": [ ],\n"
+" \"user-context\": {\n"
+" \"comment\": \"a comment\",\n"
+" \"version\": 1\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"control-socket\": {\n"
+" \"socket-name\": \"/tmp/kea6-ctrl-socket\",\n"
+" \"socket-type\": \"unix\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"Indirect comment\"\n"
+" }\n"
+" },\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001,\n"
+" \"user-context\": {\n"
+" \"comment\": \"No dynamic DNS\"\n"
+" }\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false,\n"
+" \"user-context\": {\n"
+" \"comment\": \"Use wildcard\"\n"
+" }\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 38,\n"
+" \"csv-format\": false,\n"
+" \"data\": \"ABCDEF0105\",\n"
+" \"name\": \"subscriber-id\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"Set option value\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"option-def\": [\n"
+" {\n"
+" \"array\": false,\n"
+" \"code\": 100,\n"
+" \"encapsulate\": \"\",\n"
+" \"name\": \"foo\",\n"
+" \"record-types\": \"\",\n"
+" \"space\": \"isc\",\n"
+" \"type\": \"ipv6-address\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"An option definition\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LL\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"DHCPv6 specific\"\n"
+" }\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"max-valid-lifetime\": 7200,\n"
+" \"min-valid-lifetime\": 7200,\n"
+" \"name\": \"foo\",\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"rapid-commit\": false,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 100,\n"
+" \"max-valid-lifetime\": 7200,\n"
+" \"min-valid-lifetime\": 7200,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [\n"
+" {\n"
+" \"delegated-len\": 64,\n"
+" \"option-data\": [ ],\n"
+" \"prefix\": \"2001:db2::\",\n"
+" \"prefix-len\": 48,\n"
+" \"user-context\": {\n"
+" \"comment\": \"A prefix pool\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db1::/64\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"A pool\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"foo.example.com\",\n"
+" \"hw-address\": \"aa:bb:cc:dd:ee:ff\",\n"
+" \"ip-addresses\": [ ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 24,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"example.com\",\n"
+" \"name\": \"domain-search\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\",\n"
+" \"user-context\": {\n"
+" \"comment\": \"An option in a reservation\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ ],\n"
+" \"user-context\": {\n"
+" \"comment\": \"A host reservation\"\n"
+" }\n"
+" }\n"
+" ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db1::/48\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"user-context\": {\n"
+" \"comment\": \"A subnet\"\n"
+" },\n"
+" \"valid-lifetime\": 7200\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"user-context\": {\n"
+" \"comment\": \"A shared network\"\n"
+" },\n"
+" \"valid-lifetime\": 7200\n"
+" }\n"
+" ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"user-context\": {\n"
+" \"comment\": \"A DHCPv6 server\"\n"
+" },\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 59
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"hostname\": \"\",\n"
+" \"hw-address\": \"01:02:03:04:05:06\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"2001:db8:2::abbc\",\n"
+" \"name\": \"dns-servers\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 7,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"25\",\n"
+" \"name\": \"preference\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ ]\n"
+" },\n"
+" {\n"
+" \"client-classes\": [ ],\n"
+" \"duid\": \"01:02:03:04:05:06:07:08:09:0a\",\n"
+" \"hostname\": \"\",\n"
+" \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
+" \"option-data\": [\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 23,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"2001:db8:2::1111\",\n"
+" \"name\": \"dns-servers\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" },\n"
+" {\n"
+" \"always-send\": false,\n"
+" \"code\": 7,\n"
+" \"csv-format\": true,\n"
+" \"data\": \"11\",\n"
+" \"name\": \"preference\",\n"
+" \"never-send\": false,\n"
+" \"space\": \"dhcp6\"\n"
+" }\n"
+" ],\n"
+" \"prefixes\": [ ]\n"
+" }\n"
+" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 123,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::/80\"\n"
+" }\n"
+" ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 234,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:2::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" },\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 542,\n"
+" \"max-preferred-lifetime\": 3000,\n"
+" \"max-valid-lifetime\": 4000,\n"
+" \"min-preferred-lifetime\": 3000,\n"
+" \"min-valid-lifetime\": 4000,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [ ],\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rapid-commit\": false,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:3::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 60
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"preferred-lifetime\": 3000,\n"
+" \"rebind-timer\": 2000,\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"renew-timer\": 1000,\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 5,\n"
+" \"statistic-default-sample-count\": 10,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 4000\n"
+" }\n",
+ // CONFIGURATION 61
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 62
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 1024,\n"
+" \"thread-pool-size\": 48\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [ ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 63
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"max-preferred-lifetime\": 6000,\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-preferred-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 1000,\n"
+" \"name\": \"one\",\n"
+" \"option-data\": [ ],\n"
+" \"preferred-lifetime\": 5000,\n"
+" \"valid-lifetime\": 2000\n"
+" },\n"
+" {\n"
+" \"name\": \"two\",\n"
+" \"option-data\": [ ]\n"
+" }\n"
+" ],\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 7200,\n"
+" \"min-valid-lifetime\": 7200,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"rapid-commit\": false,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n",
+ // CONFIGURATION 64
+"{\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"client-classes\": [\n"
+" {\n"
+" \"max-preferred-lifetime\": 6000,\n"
+" \"max-valid-lifetime\": 3000,\n"
+" \"min-preferred-lifetime\": 4000,\n"
+" \"min-valid-lifetime\": 1000,\n"
+" \"name\": \"one\",\n"
+" \"option-data\": [ ],\n"
+" \"preferred-lifetime\": 5000,\n"
+" \"template-test\": \"''\",\n"
+" \"valid-lifetime\": 2000\n"
+" },\n"
+" {\n"
+" \"name\": \"two\",\n"
+" \"option-data\": [ ],\n"
+" \"template-test\": \"''\"\n"
+" }\n"
+" ],\n"
+" \"ddns-generated-prefix\": \"myhost\",\n"
+" \"ddns-override-client-update\": false,\n"
+" \"ddns-override-no-update\": false,\n"
+" \"ddns-qualifying-suffix\": \"\",\n"
+" \"ddns-replace-client-name\": \"never\",\n"
+" \"ddns-send-updates\": true,\n"
+" \"ddns-update-on-renew\": false,\n"
+" \"ddns-use-conflict-resolution\": true,\n"
+" \"decline-probation-period\": 86400,\n"
+" \"dhcp-ddns\": {\n"
+" \"enable-updates\": false,\n"
+" \"max-queue-size\": 1024,\n"
+" \"ncr-format\": \"JSON\",\n"
+" \"ncr-protocol\": \"UDP\",\n"
+" \"sender-ip\": \"0.0.0.0\",\n"
+" \"sender-port\": 0,\n"
+" \"server-ip\": \"127.0.0.1\",\n"
+" \"server-port\": 53001\n"
+" },\n"
+" \"dhcp-queue-control\": {\n"
+" \"capacity\": 64,\n"
+" \"enable-queue\": false,\n"
+" \"queue-type\": \"kea-ring6\"\n"
+" },\n"
+" \"dhcp4o6-port\": 0,\n"
+" \"early-global-reservations-lookup\": false,\n"
+" \"expired-leases-processing\": {\n"
+" \"flush-reclaimed-timer-wait-time\": 25,\n"
+" \"hold-reclaimed-time\": 3600,\n"
+" \"max-reclaim-leases\": 100,\n"
+" \"max-reclaim-time\": 250,\n"
+" \"reclaim-timer-wait-time\": 10,\n"
+" \"unwarned-reclaim-cycles\": 5\n"
+" },\n"
+" \"hooks-libraries\": [ ],\n"
+" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
+" \"hostname-char-replacement\": \"\",\n"
+" \"hostname-char-set\": \"[^A-Za-z0-9.-]\",\n"
+" \"interfaces-config\": {\n"
+" \"interfaces\": [ \"*\" ],\n"
+" \"re-detect\": false\n"
+" },\n"
+" \"ip-reservations-unique\": true,\n"
+" \"lease-database\": {\n"
+" \"type\": \"memfile\"\n"
+" },\n"
+" \"mac-sources\": [ \"any\" ],\n"
+" \"multi-threading\": {\n"
+" \"enable-multi-threading\": true,\n"
+" \"packet-queue-size\": 64,\n"
+" \"thread-pool-size\": 0\n"
+" },\n"
+" \"option-data\": [ ],\n"
+" \"option-def\": [ ],\n"
+" \"parked-packet-limit\": 256,\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"relay-supplied-options\": [ \"65\" ],\n"
+" \"reservations-global\": false,\n"
+" \"reservations-in-subnet\": true,\n"
+" \"reservations-lookup-first\": false,\n"
+" \"reservations-out-of-pool\": false,\n"
+" \"sanity-checks\": {\n"
+" \"extended-info-checks\": \"fix\",\n"
+" \"lease-checks\": \"warn\"\n"
+" },\n"
+" \"server-id\": {\n"
+" \"enterprise-id\": 0,\n"
+" \"htype\": 0,\n"
+" \"identifier\": \"\",\n"
+" \"persist\": true,\n"
+" \"time\": 0,\n"
+" \"type\": \"LLT\"\n"
+" },\n"
+" \"server-tag\": \"\",\n"
+" \"shared-networks\": [ ],\n"
+" \"statistic-default-sample-age\": 0,\n"
+" \"statistic-default-sample-count\": 20,\n"
+" \"store-extended-info\": false,\n"
+" \"subnet6\": [\n"
+" {\n"
+" \"allocator\": \"iterative\",\n"
+" \"calculate-tee-times\": true,\n"
+" \"id\": 1,\n"
+" \"max-valid-lifetime\": 7200,\n"
+" \"min-valid-lifetime\": 7200,\n"
+" \"option-data\": [ ],\n"
+" \"pd-allocator\": \"iterative\",\n"
+" \"pd-pools\": [ ],\n"
+" \"pools\": [\n"
+" {\n"
+" \"option-data\": [ ],\n"
+" \"pool\": \"2001:db8:1::1-2001:db8:1::ffff\"\n"
+" }\n"
+" ],\n"
+" \"rapid-commit\": false,\n"
+" \"relay\": {\n"
+" \"ip-addresses\": [ ]\n"
+" },\n"
+" \"reservations\": [ ],\n"
+" \"store-extended-info\": false,\n"
+" \"subnet\": \"2001:db8:1::/64\",\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n"
+" ],\n"
+" \"t1-percent\": 0.5,\n"
+" \"t2-percent\": 0.8,\n"
+" \"valid-lifetime\": 7200\n"
+" }\n"
+};
+
+/// @brief the number of configurations
+const size_t max_config_counter = sizeof(EXTRACTED_CONFIGS) / sizeof(char*);
+///@}
+
+/// @brief the extraction counter
+///
+/// < 0 means do not extract, >= 0 means extract on extractConfig() calls
+/// and increment
+#ifdef EXTRACT_CONFIG
+int extract_count = 0;
+#else
+int extract_count = -1;
+#endif
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+static_assert(max_config_counter == sizeof(UNPARSED_CONFIGS) / sizeof(char*),
+ "unparsed configurations must be generated");
+#endif
+
+/// @brief format and output a configuration
+void
+outputFormatted(const std::string& config) {
+ // pretty print it
+ ConstElementPtr json = parseDHCP6(config);
+ std::string prettier = prettyPrint(json, 4, 4);
+ // get it as a line array
+ std::list<std::string> lines;
+ boost::split(lines, prettier, boost::is_any_of("\n"));
+ // add escapes using again JSON
+ std::list<std::string> escapes;
+ while (!lines.empty()) {
+ const std::string& line = lines.front();
+ ConstElementPtr escaping = Element::create(line + "\n");
+ escapes.push_back(escaping->str());
+ lines.pop_front();
+ }
+ // output them on std::cerr
+ while (!escapes.empty()) {
+ std::cerr << "\n" << escapes.front();
+ escapes.pop_front();
+ }
+}
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @ref isc::dhcp::test::extractConfig in the header
+void
+extractConfig(const std::string& config) {
+ // skip when disable
+ if (extract_count < 0) {
+ return;
+ }
+ // mark beginning
+ if (extract_count == 0) {
+ // header (note there is no trailer)
+ std::cerr << "/// put this after const char* EXTRACTED_CONFIGS[] = {\n";
+ } else {
+ // end of previous configuration
+ std::cerr << ",\n";
+ }
+ std::cerr << " // CONFIGURATION " << extract_count;
+ try {
+ outputFormatted(config);
+ } catch (...) {
+ // mark error
+ std::cerr << "\n//// got an error\n";
+ }
+ ++extract_count;
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+/// Test fixture class (code from Dhcp6ParserTest)
+class Dhcp6GetConfigTest : public ::testing::TestWithParam<size_t> {
+public:
+ Dhcp6GetConfigTest() : rcode_(-1), srv_(0) {
+ // srv_(0) means to not open any sockets. We don't want to
+ // deal with sockets here, just check if configuration handling
+ // is sane.
+
+ // Reset configuration for each test.
+ resetConfiguration();
+ }
+
+ ~Dhcp6GetConfigTest() {
+ // Reset configuration database after each test.
+ resetConfiguration();
+ };
+
+ /// @brief Parse and Execute configuration
+ ///
+ /// Parses a configuration and executes a configuration of the server.
+ /// If the operation fails, the current test will register a failure.
+ ///
+ /// @param config Configuration to parse
+ /// @param operation Operation being performed. In the case of an error,
+ /// the error text will include the string "unable to <operation>.".
+ ///
+ /// @return true if the configuration succeeded, false if not.
+ bool
+ executeConfiguration(const std::string& config, const char* operation) {
+ // clear config manager
+ CfgMgr::instance().clear();
+
+ // enable fake network interfaces
+ IfaceMgrTestConfig test_config(true);
+
+ // try JSON parser
+ ConstElementPtr json;
+ try {
+ json = parseJSON(config);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "invalid JSON for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << config << "\n";
+ return (false);
+ }
+
+ // try DHCP6 parser
+ try {
+ json = parseDHCP6(config, true);
+ } catch (...) {
+ ADD_FAILURE() << "parsing failed for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // try DHCP6 configure
+ ConstElementPtr status;
+ try {
+ status = configureDhcp6Server(srv_, json);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "configure for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // The status object must not be NULL
+ if (!status) {
+ ADD_FAILURE() << "configure for " << operation
+ << " returned null on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // Returned value should be 0 (configuration success)
+ comment_ = parseAnswer(rcode_, status);
+ if (rcode_ != 0) {
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "configure for " << operation
+ << " returned error code "
+ << rcode_ << reason << " on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing all subnets and option-data. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ string config = "{"
+ "\"interfaces-config\": { \"interfaces\": [ \"*\" ] },"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet6\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ EXPECT_TRUE(executeConfiguration(config, "reset configuration"));
+ CfgMgr::instance().clear();
+ CfgMgr::instance().setFamily(AF_INET6);
+ }
+
+ int rcode_; ///< Return code (see @ref isc::config::parseAnswer)
+ ControlledDhcpv6Srv srv_; ///< Instance of the ControlledDhcp6Srv used during tests
+ ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
+};
+
+/// Test a configuration
+TEST_P(Dhcp6GetConfigTest, run) {
+ // configurations have not been extracted yet
+ if (max_config_counter == 0) {
+ return;
+ }
+
+ // get the index of configurations to test
+ size_t config_counter = GetParam();
+
+ // emit unparsed header if wanted
+ if ((config_counter == 0) && generate_action) {
+ std::cerr << "///put this after const char* UNPARSED_CONFIGS[] = {\n";
+ }
+
+ // get the extracted configuration
+ std::string config = EXTRACTED_CONFIGS[config_counter];
+ std::ostringstream ss;
+ ss << "extracted config #" << config_counter;
+
+ // execute the extracted configuration
+ ASSERT_TRUE(executeConfiguration(config, ss.str().c_str()));
+
+ // unparse it
+ ConstSrvConfigPtr extracted = CfgMgr::instance().getStagingCfg();
+ ConstElementPtr unparsed;
+ ASSERT_NO_THROW_LOG(unparsed = extracted->toElement());
+ ConstElementPtr dhcp;
+ ASSERT_NO_THROW_LOG(dhcp = unparsed->get("Dhcp6"));
+ ASSERT_TRUE(dhcp);
+
+ // dump if wanted else check
+ std::string expected;
+ if (generate_action) {
+ if (config_counter > 0) {
+ std::cerr << ",\n";
+ }
+ std::cerr << " // CONFIGURATION " << config_counter;
+ ASSERT_NO_THROW_LOG(expected = prettyPrint(dhcp));
+ ASSERT_NO_THROW_LOG(outputFormatted(dhcp->str()));
+ } else {
+ expected = UNPARSED_CONFIGS[config_counter];
+ // get the expected config using the dhcpv6 syntax parser
+ ElementPtr jsond;
+ ASSERT_NO_THROW_LOG(jsond = parseDHCP6(expected, true));
+ // get the expected config using the generic JSON syntax parser
+ ElementPtr jsonj;
+ ASSERT_NO_THROW_LOG(jsonj = parseJSON(expected));
+ // the generic JSON parser does not handle comments
+ EXPECT_TRUE(isEquivalent(jsond, moveComments(jsonj)));
+ // check that unparsed and expected values match
+ EXPECT_TRUE(isEquivalent(dhcp, jsonj));
+ // check on pretty prints too
+ std::string current = prettyPrint(dhcp, 4, 4) + "\n";
+ EXPECT_EQ(expected, current);
+ if (expected != current) {
+ expected = current;
+ }
+ }
+
+ // execute the dhcp configuration
+ ss.str("");
+ ss << "unparsed config #" << config_counter;
+ EXPECT_TRUE(executeConfiguration(expected, ss.str().c_str()));
+
+ // is it a fixed point?
+ ConstSrvConfigPtr extracted2 = CfgMgr::instance().getStagingCfg();
+ ConstElementPtr unparsed2;
+ ASSERT_NO_THROW_LOG(unparsed2 = extracted2->toElement());
+ ASSERT_TRUE(unparsed2);
+ EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
+
+class IntToString {
+public:
+ std::string operator()(const testing::TestParamInfo<size_t>& n) {
+ std::ostringstream ss;
+ ss << static_cast<size_t>(n.param);
+ return (ss.str());
+ }
+};
+
+/// Define the parameterized test loop.
+#ifdef INSTANTIATE_TEST_SUITE_P
+INSTANTIATE_TEST_SUITE_P(Dhcp6GetConfigTest, Dhcp6GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#else
+INSTANTIATE_TEST_CASE_P(Dhcp6GetConfigTest, Dhcp6GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#endif
+} // namespace
diff --git a/src/bin/dhcp6/tests/get_config_unittest.cc.skel b/src/bin/dhcp6/tests/get_config_unittest.cc.skel
new file mode 100644
index 0000000..44f3f1e
--- /dev/null
+++ b/src/bin/dhcp6/tests/get_config_unittest.cc.skel
@@ -0,0 +1,377 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
+#include <testutils/user_context_utils.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/get_config_unittest.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <list>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+
+namespace {
+
+/// @name How to fill configurations
+///
+/// Copy get_config_unittest.cc.skel into get_config_unittest.cc
+///
+/// For the extracted configurations define the EXTRACT_CONFIG and
+/// recompile this file. Run dhcp6_unittests on Dhcp6ParserTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp6_unittests --gtest_filter="Dhcp6Parser*" > /dev/null 2> x
+/// @endcode
+///
+/// Update EXTRACTED_CONFIGS with the file content
+///
+/// When configurations have been extracted the corresponding unparsed
+/// configurations must be generated. To do that define GENERATE_ACTION
+/// and recompile this file. Run dhcp6_unittests on Dhcp6GetConfigTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+/// ./dhcp6_unittests --gtest_filter="Dhcp6GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update UNPARSED_CONFIGS with the file content, recompile this file
+/// without EXTRACT_CONFIG and GENERATE_ACTION.
+///
+/// @note Check for failures at each step!
+/// @note The tests of this file do not check if configs returned
+/// by @ref isc::dhcp::CfgToElement::ToElement() are complete.
+/// This has to be done manually.
+///
+///@{
+/// @brief extracted configurations
+const char* EXTRACTED_CONFIGS[] = {
+ // "to be replaced"
+};
+
+/// @brief unparsed configurations
+const char* UNPARSED_CONFIGS[] = {
+ // "to be replaced"
+};
+
+/// @brief the number of configurations
+const size_t max_config_counter = sizeof(EXTRACTED_CONFIGS) / sizeof(char*);
+///@}
+
+/// @brief the extraction counter
+///
+/// < 0 means do not extract, >= 0 means extract on extractConfig() calls
+/// and increment
+#ifdef EXTRACT_CONFIG
+int extract_count = 0;
+#else
+int extract_count = -1;
+#endif
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+static_assert(max_config_counter == sizeof(UNPARSED_CONFIGS) / sizeof(char*),
+ "unparsed configurations must be generated");
+#endif
+
+/// @brief format and output a configuration
+void
+outputFormatted(const std::string& config) {
+ // pretty print it
+ ConstElementPtr json = parseDHCP6(config);
+ std::string prettier = prettyPrint(json, 4, 4);
+ // get it as a line array
+ std::list<std::string> lines;
+ boost::split(lines, prettier, boost::is_any_of("\n"));
+ // add escapes using again JSON
+ std::list<std::string> escapes;
+ while (!lines.empty()) {
+ const std::string& line = lines.front();
+ ConstElementPtr escaping = Element::create(line + "\n");
+ escapes.push_back(escaping->str());
+ lines.pop_front();
+ }
+ // output them on std::cerr
+ while (!escapes.empty()) {
+ std::cerr << "\n" << escapes.front();
+ escapes.pop_front();
+ }
+}
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @ref isc::dhcp::test::extractConfig in the header
+void
+extractConfig(const std::string& config) {
+ // skip when disable
+ if (extract_count < 0) {
+ return;
+ }
+ // mark beginning
+ if (extract_count == 0) {
+ // header (note there is no trailer)
+ std::cerr << "/// put this after const char* EXTRACTED_CONFIGS[] = {\n";
+ } else {
+ // end of previous configuration
+ std::cerr << ",\n";
+ }
+ std::cerr << " // CONFIGURATION " << extract_count;
+ try {
+ outputFormatted(config);
+ } catch (...) {
+ // mark error
+ std::cerr << "\n//// got an error\n";
+ }
+ ++extract_count;
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+/// Test fixture class (code from Dhcp6ParserTest)
+class Dhcp6GetConfigTest : public ::testing::TestWithParam<size_t> {
+public:
+ Dhcp6GetConfigTest() : rcode_(-1), srv_(0) {
+ // srv_(0) means to not open any sockets. We don't want to
+ // deal with sockets here, just check if configuration handling
+ // is sane.
+
+ // Reset configuration for each test.
+ resetConfiguration();
+ }
+
+ ~Dhcp6GetConfigTest() {
+ // Reset configuration database after each test.
+ resetConfiguration();
+ };
+
+ /// @brief Parse and Execute configuration
+ ///
+ /// Parses a configuration and executes a configuration of the server.
+ /// If the operation fails, the current test will register a failure.
+ ///
+ /// @param config Configuration to parse
+ /// @param operation Operation being performed. In the case of an error,
+ /// the error text will include the string "unable to <operation>.".
+ ///
+ /// @return true if the configuration succeeded, false if not.
+ bool
+ executeConfiguration(const std::string& config, const char* operation) {
+ // clear config manager
+ CfgMgr::instance().clear();
+
+ // enable fake network interfaces
+ IfaceMgrTestConfig test_config(true);
+
+ // try JSON parser
+ ConstElementPtr json;
+ try {
+ json = parseJSON(config);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "invalid JSON for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << config << "\n";
+ return (false);
+ }
+
+ // try DHCP6 parser
+ try {
+ json = parseDHCP6(config, true);
+ } catch (...) {
+ ADD_FAILURE() << "parsing failed for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // try DHCP6 configure
+ ConstElementPtr status;
+ try {
+ status = configureDhcp6Server(srv_, json);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "configure for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // The status object must not be NULL
+ if (!status) {
+ ADD_FAILURE() << "configure for " << operation
+ << " returned null on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // Returned value should be 0 (configuration success)
+ comment_ = parseAnswer(rcode_, status);
+ if (rcode_ != 0) {
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "configure for " << operation
+ << " returned error code "
+ << rcode_ << reason << " on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing all subnets and option-data. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ string config = "{"
+ "\"interfaces-config\": { \"interfaces\": [ \"*\" ] },"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet6\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ EXPECT_TRUE(executeConfiguration(config, "reset configuration"));
+ CfgMgr::instance().clear();
+ CfgMgr::instance().setFamily(AF_INET6);
+ }
+
+ int rcode_; ///< Return code (see @ref isc::config::parseAnswer)
+ ControlledDhcpv6Srv srv_; ///< Instance of the ControlledDhcp6Srv used during tests
+ ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
+};
+
+/// Test a configuration
+TEST_P(Dhcp6GetConfigTest, run) {
+ // configurations have not been extracted yet
+ if (max_config_counter == 0) {
+ return;
+ }
+
+ // get the index of configurations to test
+ size_t config_counter = GetParam();
+
+ // emit unparsed header if wanted
+ if ((config_counter == 0) && generate_action) {
+ std::cerr << "///put this after const char* UNPARSED_CONFIGS[] = {\n";
+ }
+
+ // get the extracted configuration
+ std::string config = EXTRACTED_CONFIGS[config_counter];
+ std::ostringstream ss;
+ ss << "extracted config #" << config_counter;
+
+ // execute the extracted configuration
+ ASSERT_TRUE(executeConfiguration(config, ss.str().c_str()));
+
+ // unparse it
+ ConstSrvConfigPtr extracted = CfgMgr::instance().getStagingCfg();
+ ConstElementPtr unparsed;
+ ASSERT_NO_THROW_LOG(unparsed = extracted->toElement());
+ ConstElementPtr dhcp;
+ ASSERT_NO_THROW_LOG(dhcp = unparsed->get("Dhcp6"));
+ ASSERT_TRUE(dhcp);
+
+ // dump if wanted else check
+ std::string expected;
+ if (generate_action) {
+ if (config_counter > 0) {
+ std::cerr << ",\n";
+ }
+ std::cerr << " // CONFIGURATION " << config_counter;
+ ASSERT_NO_THROW_LOG(expected = prettyPrint(dhcp));
+ ASSERT_NO_THROW_LOG(outputFormatted(dhcp->str()));
+ } else {
+ expected = UNPARSED_CONFIGS[config_counter];
+ // get the expected config using the dhcpv6 syntax parser
+ ElementPtr jsond;
+ ASSERT_NO_THROW_LOG(jsond = parseDHCP6(expected, true));
+ // get the expected config using the generic JSON syntax parser
+ ElementPtr jsonj;
+ ASSERT_NO_THROW_LOG(jsonj = parseJSON(expected));
+ // the generic JSON parser does not handle comments
+ EXPECT_TRUE(isEquivalent(jsond, moveComments(jsonj)));
+ // check that unparsed and expected values match
+ EXPECT_TRUE(isEquivalent(dhcp, jsonj));
+ // check on pretty prints too
+ std::string current = prettyPrint(dhcp, 4, 4) + "\n";
+ EXPECT_EQ(expected, current);
+ if (expected != current) {
+ expected = current;
+ }
+ }
+
+ // execute the dhcp configuration
+ ss.str("");
+ ss << "unparsed config #" << config_counter;
+ EXPECT_TRUE(executeConfiguration(expected, ss.str().c_str()));
+
+ // is it a fixed point?
+ ConstSrvConfigPtr extracted2 = CfgMgr::instance().getStagingCfg();
+ ConstElementPtr unparsed2;
+ ASSERT_NO_THROW_LOG(unparsed2 = extracted2->toElement());
+ ASSERT_TRUE(unparsed2);
+ EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
+
+class IntToString {
+public:
+ std::string operator()(const testing::TestParamInfo<size_t>& n) {
+ std::ostringstream ss;
+ ss << static_cast<size_t>(n.param);
+ return (ss.str());
+ }
+};
+
+/// Define the parameterized test loop.
+#ifdef INSTANTIATE_TEST_SUITE_P
+INSTANTIATE_TEST_SUITE_P(Dhcp6GetConfigTest, Dhcp6GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#else
+INSTANTIATE_TEST_CASE_P(Dhcp6GetConfigTest, Dhcp6GetConfigTest,
+ ::testing::Range(static_cast<size_t>(0),
+ max_config_counter),
+ IntToString());
+#endif
+} // namespace
diff --git a/src/bin/dhcp6/tests/get_config_unittest.h b/src/bin/dhcp6/tests/get_config_unittest.h
new file mode 100644
index 0000000..e1718e0
--- /dev/null
+++ b/src/bin/dhcp6/tests/get_config_unittest.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef GET_CONFIG_UNITTEST_H
+#define GET_CONFIG_UNITTEST_H
+
+#include <string.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Extract a configuration as a C++ source for JSON on std::cerr
+///
+/// This function should be called when a configuration in an unit test
+/// is interesting and should be extracted. Do nothing when extract_count
+/// is negative.
+void extractConfig(const std::string& config);
+
+};
+};
+};
+
+#endif // GET_CONFIG_UNITTEST_H
diff --git a/src/bin/dhcp6/tests/hooks_unittest.cc b/src/bin/dhcp6/tests/hooks_unittest.cc
new file mode 100644
index 0000000..c34aa76
--- /dev/null
+++ b/src/bin/dhcp6/tests/hooks_unittest.cc
@@ -0,0 +1,5817 @@
+// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/marker_file.h>
+#include <dhcp6/tests/test_libraries.h>
+#include <hooks/server_hooks.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_manager.h>
+#include <stats/stats_mgr.h>
+#include <util/buffer.h>
+#include <util/range_utilities.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace isc::stats;
+using namespace isc::util;
+
+using namespace std;
+
+namespace {
+
+// Checks if hooks are implemented properly.
+TEST_F(Dhcpv6SrvTest, Hooks) {
+ NakedDhcpv6Srv srv(0);
+
+ // check if appropriate hooks are registered
+ int hook_index_dhcp6_srv_configured = -1;
+ int hook_index_buffer6_receive = -1;
+ int hook_index_buffer6_send = -1;
+ int hook_index_lease6_renew = -1;
+ int hook_index_lease6_release = -1;
+ int hook_index_lease6_rebind = -1;
+ int hook_index_lease6_decline = -1;
+ int hook_index_pkt6_receive = -1;
+ int hook_index_pkt6_send = -1;
+ int hook_index_select_subnet = -1;
+ int hook_index_leases6_committed = -1;
+ int hook_index_host6_identifier = -1;
+
+ // check if appropriate indexes are set
+ EXPECT_NO_THROW(hook_index_dhcp6_srv_configured = ServerHooks::getServerHooks()
+ .getIndex("dhcp6_srv_configured"));
+ EXPECT_NO_THROW(hook_index_buffer6_receive = ServerHooks::getServerHooks()
+ .getIndex("buffer6_receive"));
+ EXPECT_NO_THROW(hook_index_buffer6_send = ServerHooks::getServerHooks()
+ .getIndex("buffer6_send"));
+ EXPECT_NO_THROW(hook_index_lease6_renew = ServerHooks::getServerHooks()
+ .getIndex("lease6_renew"));
+ EXPECT_NO_THROW(hook_index_lease6_release = ServerHooks::getServerHooks()
+ .getIndex("lease6_release"));
+ EXPECT_NO_THROW(hook_index_lease6_rebind = ServerHooks::getServerHooks()
+ .getIndex("lease6_rebind"));
+ EXPECT_NO_THROW(hook_index_lease6_decline = ServerHooks::getServerHooks()
+ .getIndex("lease6_decline"));
+ EXPECT_NO_THROW(hook_index_pkt6_receive = ServerHooks::getServerHooks()
+ .getIndex("pkt6_receive"));
+ EXPECT_NO_THROW(hook_index_pkt6_send = ServerHooks::getServerHooks()
+ .getIndex("pkt6_send"));
+ EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
+ .getIndex("subnet6_select"));
+ EXPECT_NO_THROW(hook_index_leases6_committed = ServerHooks::getServerHooks()
+ .getIndex("leases6_committed"));
+ EXPECT_NO_THROW(hook_index_host6_identifier = ServerHooks::getServerHooks()
+ .getIndex("host6_identifier"));
+
+ EXPECT_TRUE(hook_index_dhcp6_srv_configured > 0);
+ EXPECT_TRUE(hook_index_buffer6_receive > 0);
+ EXPECT_TRUE(hook_index_buffer6_send > 0);
+ EXPECT_TRUE(hook_index_lease6_renew > 0);
+ EXPECT_TRUE(hook_index_lease6_release > 0);
+ EXPECT_TRUE(hook_index_lease6_rebind > 0);
+ EXPECT_TRUE(hook_index_lease6_decline > 0);
+ EXPECT_TRUE(hook_index_pkt6_receive > 0);
+ EXPECT_TRUE(hook_index_pkt6_send > 0);
+ EXPECT_TRUE(hook_index_select_subnet > 0);
+ EXPECT_TRUE(hook_index_leases6_committed > 0);
+ EXPECT_TRUE(hook_index_host6_identifier > 0);
+}
+
+/// @brief a class dedicated to Hooks testing in DHCPv6 server
+///
+/// This class has a number of static members, because each non-static
+/// method has implicit 'this' parameter, so it does not match callout
+/// signature and couldn't be registered. Furthermore, static methods
+/// can't modify non-static members (for obvious reasons), so many
+/// fields are declared static. It is still better to keep them as
+/// one class rather than unrelated collection of global objects.
+class HooksDhcpv6SrvTest : public Dhcpv6SrvTest {
+
+public:
+
+ /// @brief creates Dhcpv6Srv and prepares buffers for callouts
+ HooksDhcpv6SrvTest() : Dhcpv6SrvTest() {
+ HooksManager::setTestMode(false);
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture ctor) unloadLibraries failed" << endl;
+ }
+
+ // Allocate new DHCPv6 Server
+ srv_.reset(new NakedDhcpv6Srv(0));
+
+ // Clear static buffers
+ resetCalloutBuffers();
+
+ io_service_ = boost::make_shared<IOService>();
+
+ // Reset the hook system in its original state
+ HooksManager::unloadLibraries();
+
+ // Clear statistics.
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief destructor (deletes Dhcpv6Srv)
+ ~HooksDhcpv6SrvTest() {
+ // Clear static buffers
+ resetCalloutBuffers();
+
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("dhcp6_srv_configured");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer6_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer6_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt6_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt6_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("subnet6_select");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("leases6_committed");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease6_renew");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease6_release");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease6_rebind");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease6_decline");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("host6_identifier");
+
+ HooksManager::setTestMode(false);
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture dtor) unloadLibraries failed" << endl;
+ }
+
+ // Clear statistics.
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief creates an option with specified option code
+ ///
+ /// This method is static, because it is used from callouts
+ /// that do not have a pointer to HooksDhcpv6SrvTest object
+ ///
+ /// @param option_code code of option to be created
+ ///
+ /// @return pointer to create option object
+ static OptionPtr createOption(uint16_t option_code) {
+
+ uint8_t payload[] = {
+ 0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14
+ };
+
+ OptionBuffer tmp(payload, payload + sizeof(payload));
+ return OptionPtr(new Option(Option::V6, option_code, tmp));
+ }
+
+ /// @brief Checks if the state of the callout handle associated with a query
+ /// was reset after the callout invocation.
+ ///
+ /// The check includes verification if the status was set to 'continue' and
+ /// that all arguments were deleted.
+ ///
+ /// @param query pointer to the query which callout handle is associated
+ /// with.
+ void checkCalloutHandleReset(const Pkt6Ptr& query) {
+ CalloutHandlePtr callout_handle = query->getCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getArgumentNames().empty());
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("buffer6_receive");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that changes first byte of client-id value.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_change_clientid_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // If there is at least one option with data
+ if (pkt->data_.size() > Pkt6::DHCPV6_PKT_HDR_LEN + Option::OPTION6_HDR_LEN) {
+ // Offset of the first byte of the first option. Let's set this byte
+ // to some new value that we could later check
+ pkt->data_[Pkt6::DHCPV6_PKT_HDR_LEN + Option::OPTION6_HDR_LEN] = 0xff;
+ }
+
+ // Carry on as usual
+ return buffer6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that deletes client-id.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_delete_clientid_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // this is modified SOLICIT (with missing mandatory client-id)
+ uint8_t data[] = {
+ 1, // type 1 = SOLICIT
+ 0xca, 0xfe, 0x01, // trans-id = 0xcafe01
+ 0, 3, // option type 3 (IA_NA)
+ 0, 12, // option length 12
+ 0, 0, 0, 1, // iaid = 1
+ 0, 0, 0, 0, // T1 = 0
+ 0, 0, 0, 0 // T2 = 0
+ };
+
+ OptionBuffer modifiedMsg(data, data + sizeof(data));
+
+ pkt->data_ = modifiedMsg;
+
+ // Carry on as usual
+ return buffer6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_skip_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // Carry on as usual
+ return buffer6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_drop_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // Carry on as usual
+ return buffer6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt6_receive");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that changes client-id value.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_change_clientid_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // Get rid of the old client-id
+ pkt->delOption(D6O_CLIENTID);
+
+ // Add a new option
+ pkt->addOption(createOption(D6O_CLIENTID));
+
+ // Carry on as usual
+ return pkt6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that deletes client-id.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_delete_clientid_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // Get rid of the old client-id
+ pkt->delOption(D6O_CLIENTID);
+
+ // Carry on as usual
+ return pkt6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_skip_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // Carry on as usual
+ return pkt6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_drop_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // Carry on as usual
+ return pkt6_receive_callout(callout_handle);
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt6_send");
+
+ callout_handle.getArgument("response6", callback_resp_pkt6_);
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ if (callback_resp_pkt6_) {
+ callback_resp_options_copy_ = callback_resp_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that changes server-id.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_change_serverid_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("response6", pkt);
+
+ // Get rid of the old server-id
+ pkt->delOption(D6O_SERVERID);
+
+ // Add a new option
+ pkt->addOption(createOption(D6O_SERVERID));
+
+ // Carry on as usual
+ return pkt6_send_callout(callout_handle);
+ }
+
+ /// @brief Test callback that deletes server-id.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_delete_serverid_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("response6", pkt);
+
+ // Get rid of the old client-id
+ pkt->delOption(D6O_SERVERID);
+
+ // Carry on as usual
+ return pkt6_send_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_skip_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("response6", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // carry on as usual
+ return pkt6_send_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_drop_callout(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("response6", pkt);
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // carry on as usual
+ return pkt6_send_callout(callout_handle);
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework.
+ /// @return always 0
+ static int
+ buffer6_send_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("buffer6_send");
+
+ callout_handle.getArgument("response6", callback_resp_pkt6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_resp_pkt6_) {
+ callback_resp_options_copy_ = callback_resp_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that sets skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_send_skip_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // Carry on as usual
+ return buffer6_send_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_send_drop_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // carry on as usual
+ return buffer6_send_callout(callout_handle);
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet6_select_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("subnet6_select");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("subnet6", callback_subnet6_);
+ callout_handle.getArgument("subnet6collection", callback_subnet6collection_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that picks the other subnet if possible.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet6_select_different_subnet_callout(CalloutHandle& callout_handle) {
+
+ // Call the basic callout to record all passed values
+ subnet6_select_callout(callout_handle);
+
+ const Subnet6Collection* subnets;
+ Subnet6Ptr subnet;
+ callout_handle.getArgument("subnet6", subnet);
+ callout_handle.getArgument("subnet6collection", subnets);
+
+ // Let's change to a different subnet
+ if (subnets->size() > 1) {
+ subnet = *std::next(subnets->begin()); // Let's pick the other subnet
+ callout_handle.setArgument("subnet6", subnet);
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that sets skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet6_select_skip_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // Carry on as usual
+ return subnet6_select_callout(callout_handle);
+ }
+
+ /// @brief Test callback that sets drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet6_select_drop_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ // Carry on as usual
+ return subnet6_select_callout(callout_handle);
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_renew_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_renew");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+ callout_handle.getArgument("ia_na", callback_ia_na_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// The following values are used by the callout to override
+ /// renewed lease parameters
+ static const uint32_t override_iaid_;
+ static const uint32_t override_preferred_;
+ static const uint32_t override_valid_;
+
+ /// @brief Test callback that overrides received lease.
+ ///
+ /// It updates T1, T2, preferred and valid lifetimes
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_renew_update_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_renew");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+ callout_handle.getArgument("ia_na", callback_ia_na_);
+
+ // Should be an ASSERT but it is not allowed here
+ EXPECT_TRUE(callback_lease6_);
+ // Let's override some values in the lease
+ callback_lease6_->iaid_ = override_iaid_;
+ callback_lease6_->preferred_lft_ = override_preferred_;
+ callback_lease6_->valid_lft_ = override_valid_;
+
+ // Should be an ASSERT but it is not allowed here
+ EXPECT_TRUE(callback_ia_na_);
+ // Override the values to be sent to the client as well
+ callback_ia_na_->setIAID(override_iaid_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// @brief Test callback that sets the skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_renew_skip_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_renew");
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_rebind_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_rebind");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+ callout_handle.getArgument("ia_na", callback_ia_na_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that overrides received lease.
+ ///
+ /// It updates T1, T2, preferred and valid lifetimes
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_rebind_update_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_rebind");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+ callout_handle.getArgument("ia_na", callback_ia_na_);
+
+ // Should be an ASSERT but it is not allowed here
+ EXPECT_TRUE(callback_lease6_);
+ // Let's override some values in the lease
+ callback_lease6_->iaid_ = override_iaid_;
+ callback_lease6_->preferred_lft_ = override_preferred_;
+ callback_lease6_->valid_lft_ = override_valid_;
+
+ // Should be an ASSERT but it is not allowed here
+ EXPECT_TRUE(callback_ia_na_);
+ // Override the values to be sent to the client as well
+ callback_ia_na_->setIAID(override_iaid_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// @brief Test callback that sets the skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_rebind_skip_callout(CalloutHandle& callout_handle) {
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (lease6_rebind_callout(callout_handle));
+ }
+
+ /// @brief Test callback that sets the drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_rebind_drop_callout(CalloutHandle& callout_handle) {
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ return (lease6_rebind_callout(callout_handle));
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_release_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_release");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that sets the skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_release_skip_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_release");
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
+ /// @brief Test callback that sets the drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_release_drop_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_release");
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ return (0);
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_decline_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_decline");
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback that sets the skip flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_decline_skip_callout(CalloutHandle& callout_handle) {
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (lease6_decline_callout(callout_handle));
+ }
+
+ /// @brief Test callback that sets the drop flag.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_decline_drop_callout(CalloutHandle& callout_handle) {
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ return (lease6_decline_callout(callout_handle));
+ }
+
+ /// @brief Test callback that stores callout name and passed parameters.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ leases6_committed_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("leases6_committed");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+
+ callout_handle.getArgument("leases6", callback_new_leases6_);
+
+ callout_handle.getArgument("deleted_leases6", callback_deleted_leases6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test callback which asks the server to unpark the packet.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static void
+ leases6_committed_unpark(ParkingLotHandlePtr parking_lot, Pkt6Ptr query) {
+ parking_lot->unpark(query);
+ }
+
+ /// @brief Test callback which asks the server to park the packet.
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ leases6_committed_park_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("leases6_committed");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+
+ io_service_->post(std::bind(&HooksDhcpv6SrvTest::leases6_committed_unpark,
+ callout_handle.getParkingLotHandlePtr(),
+ callback_qry_pkt6_));
+
+ callout_handle.getParkingLotHandlePtr()->reference(callback_qry_pkt6_);
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
+
+ callout_handle.getArgument("leases6", callback_new_leases6_);
+
+ callout_handle.getArgument("deleted_leases6", callback_deleted_leases6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ = callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// @brief Test host6_identifier callback by setting identifier to "foo".
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ host6_identifier_foo_callout(CalloutHandle& handle) {
+ callback_name_ = string("host6_identifier");
+
+ // Make sure the query6 parameter is passed.
+ handle.getArgument("query6", callback_qry_pkt6_);
+
+ // Make sure id_type parameter is passed.
+ Host::IdentifierType type = Host::IDENT_FLEX;
+ handle.getArgument("id_type", type);
+
+ // Make sure id_value parameter is passed.
+ std::vector<uint8_t> id_test;
+ handle.getArgument("id_value", id_test);
+
+ // Ok, now set the identifier.
+ std::vector<uint8_t> id = { 0x66, 0x6f, 0x6f }; // foo
+ handle.setArgument("id_value", id);
+ handle.setArgument("id_type", Host::IDENT_FLEX);
+
+ return (0);
+ }
+
+ /// @brief Test host6_identifier callout by setting identifier to hwaddr.
+ ///
+ /// This callout always returns fixed HWADDR: 00:01:02:03:04:05
+ ///
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ host6_identifier_hwaddr_callout(CalloutHandle& handle) {
+ callback_name_ = string("host6_identifier");
+
+ // Make sure the query6 parameter is passed.
+ handle.getArgument("query6", callback_qry_pkt6_);
+
+ // Make sure id_type parameter is passed.
+ Host::IdentifierType type = Host::IDENT_FLEX;
+ handle.getArgument("id_type", type);
+
+ // Make sure id_value parameter is passed.
+ std::vector<uint8_t> id_test;
+ handle.getArgument("id_value", id_test);
+
+ // Ok, now set the identifier to 00:01:02:03:04:05
+ std::vector<uint8_t> id = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+ handle.setArgument("id_value", id);
+ handle.setArgument("id_type", Host::IDENT_HWADDR);
+
+ return (0);
+ }
+
+ /// Resets buffers used to store data received by callouts
+ void resetCalloutBuffers() {
+ callback_name_ = string("");
+ callback_qry_pkt6_.reset();
+ callback_resp_pkt6_.reset();
+ callback_subnet6_.reset();
+ callback_lease6_.reset();
+ callback_new_leases6_.reset();
+ callback_deleted_leases6_.reset();
+ callback_ia_na_.reset();
+ callback_subnet6collection_ = NULL;
+ callback_argument_names_.clear();
+ callback_qry_options_copy_ = false;
+ callback_resp_options_copy_ = false;
+ }
+
+ /// @brief Fetches the current value of the given statistic.
+ /// @param name name of the desired statistic.
+ /// @return Current value of the statistic, or zero if the
+ /// statistic is not found.
+ uint64_t getStatistic(const std::string& name) {
+ ObservationPtr stat = StatsMgr::instance().getObservation(name);
+ if (!stat) {
+ return (0);
+ }
+
+ return (stat->getInteger().first);
+ }
+
+ /// Pointer to Dhcpv6Srv that is used in tests
+ boost::shared_ptr<NakedDhcpv6Srv> srv_;
+
+ /// Pointer to the IO service used in the tests.
+ static IOServicePtr io_service_;
+
+ // The following fields are used in testing pkt6_receive_callout
+
+ /// String name of the received callout
+ static string callback_name_;
+
+ /// Client's query Pkt6 structure returned in the callout
+ static Pkt6Ptr callback_qry_pkt6_;
+
+ /// Server's response Pkt6 structure returned in the callout
+ static Pkt6Ptr callback_resp_pkt6_;
+
+ /// Pointer to lease6 structure returned in the leases6_committed callout
+ static Lease6Ptr callback_lease6_;
+
+ /// Pointer to lease6 structure returned in the leases6_committed callout
+ static Lease6CollectionPtr callback_new_leases6_;
+
+ /// Pointer to lease6 structure returned in the leases6_committed callout
+ static Lease6CollectionPtr callback_deleted_leases6_;
+
+ /// Pointer to IA_NA option being renewed or rebound
+ static boost::shared_ptr<Option6IA> callback_ia_na_;
+
+ /// Pointer to a subnet received by callout
+ static Subnet6Ptr callback_subnet6_;
+
+ /// A list of all available subnets (received by callout)
+ static const Subnet6Collection* callback_subnet6collection_;
+
+ /// A list of all received arguments
+ static vector<string> callback_argument_names_;
+
+ /// Flag indicating if copying retrieved options was enabled for
+ /// a query during callout execution.
+ static bool callback_qry_options_copy_;
+
+ /// Flag indicating if copying retrieved options was enabled for
+ /// a response during callout execution.
+ static bool callback_resp_options_copy_;
+};
+
+// The following parameters are used by callouts to override
+// renewed lease parameters
+const uint32_t HooksDhcpv6SrvTest::override_iaid_ = 1000;
+const uint32_t HooksDhcpv6SrvTest::override_preferred_ = 1003;
+const uint32_t HooksDhcpv6SrvTest::override_valid_ = 1004;
+
+// The following fields are used in testing pkt6_receive_callout.
+// See fields description in the class for details
+IOServicePtr HooksDhcpv6SrvTest::io_service_;
+string HooksDhcpv6SrvTest::callback_name_;
+Pkt6Ptr HooksDhcpv6SrvTest::callback_qry_pkt6_;
+Pkt6Ptr HooksDhcpv6SrvTest::callback_resp_pkt6_;
+Subnet6Ptr HooksDhcpv6SrvTest::callback_subnet6_;
+const Subnet6Collection* HooksDhcpv6SrvTest::callback_subnet6collection_;
+Lease6Ptr HooksDhcpv6SrvTest::callback_lease6_;
+Lease6CollectionPtr HooksDhcpv6SrvTest::callback_new_leases6_;
+Lease6CollectionPtr HooksDhcpv6SrvTest::callback_deleted_leases6_;
+boost::shared_ptr<Option6IA> HooksDhcpv6SrvTest::callback_ia_na_;
+vector<string> HooksDhcpv6SrvTest::callback_argument_names_;
+bool HooksDhcpv6SrvTest::callback_qry_options_copy_;
+bool HooksDhcpv6SrvTest::callback_resp_options_copy_;
+
+/// @brief Fixture class used to do basic library load/unload tests
+class LoadUnloadDhcpv6SrvTest : public Dhcpv6SrvTest {
+public:
+ /// @brief Pointer to the tested server object
+ boost::shared_ptr<NakedDhcpv6Srv> server_;
+
+ LoadUnloadDhcpv6SrvTest() : Dhcpv6SrvTest() {
+ reset();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor
+ ~LoadUnloadDhcpv6SrvTest() {
+ server_.reset();
+ reset();
+ MultiThreadingMgr::instance().setMode(false);
+ };
+
+ /// @brief Reset hooks data
+ ///
+ /// Resets the data for the hooks-related portion of the test by ensuring
+ /// that no libraries are loaded and that any marker files are deleted.
+ void reset() {
+ // Unload any previously-loaded libraries.
+ EXPECT_TRUE(HooksManager::unloadLibraries());
+
+ // Get rid of any marker files.
+ static_cast<void>(remove(LOAD_MARKER_FILE));
+ static_cast<void>(remove(UNLOAD_MARKER_FILE));
+ static_cast<void>(remove(SRV_CONFIG_MARKER_FILE));
+
+ CfgMgr::instance().clear();
+ }
+};
+
+// Checks if callouts installed on buffer6_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "buffer6_receive".
+TEST_F(HooksDhcpv6SrvTest, buffer6ReceiveSimple) {
+
+ // Install buffer6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer6_receive", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query6"));
+
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on buffer6_receive is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv6SrvTest, buffer6ReceiveValueChange) {
+
+ // Install buffer6_receive_change_clientid_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_change_clientid_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_receive callback.
+ srv_->run();
+
+ // Check that the server did send a response
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(D6O_CLIENTID);
+
+ ASSERT_TRUE(clientid);
+
+ // ... and check if it is the modified value
+ EXPECT_EQ(0xff, clientid->getData()[0]);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on buffer6_receive is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv6SrvTest, buffer6ReceiveDeleteClientId) {
+
+ // Install buffer6_receive_delete_clientid_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_delete_clientid_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not send a response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on buffer6_receive is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, buffer6ReceiveSkip) {
+
+ // Install buffer6_receive_skip_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_skip_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on buffer6_receive is able to set drop flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, buffer6ReceiveDrop) {
+
+ // Install buffer6_receive_drop_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_drop_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "pkt6_receive".
+TEST_F(HooksDhcpv6SrvTest, pkt6ReceiveSimple) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt6_receive", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query6"));
+
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_received is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv6SrvTest, pkt6ReceiveValueChange) {
+
+ // Install pkt6_receive_change_clientid_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_change_clientid_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server did send a response
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(D6O_CLIENTID);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(D6O_CLIENTID);
+ EXPECT_TRUE(clientid->equals(expected));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_received is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv6SrvTest, pkt6ReceiveDeleteClientId) {
+
+ // Install pkt6_receive_delete_clientid_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_delete_clientid_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not send a response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_received is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, pkt6ReceiveSkip) {
+
+ // Install pkt6_receive_skip_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_skip_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_received is able to set drop flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, pkt6ReceiveDrop) {
+
+ // Install pkt6_receive_drop_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_drop_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv6SrvTest, pkt6SendSimple) {
+
+ // Install pkt6_send_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_send callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt6_send", callback_name_);
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+
+ // Check that pkt6 argument passing was successful and returned proper
+ // values
+ ASSERT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+ ASSERT_TRUE(callback_resp_pkt6_);
+ EXPECT_TRUE(callback_resp_pkt6_.get() == adv.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query6"));
+ expected_argument_names.push_back(string("response6"));
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+ EXPECT_TRUE(callback_resp_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_send is able to change
+// the values and the packet sent contains those changes
+TEST_F(HooksDhcpv6SrvTest, pkt6SendValueChange) {
+
+ // Install pkt6_send_change_serverid_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_change_serverid_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_send callback.
+ srv_->run();
+
+ // Check that the server did send a response
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(D6O_SERVERID);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(D6O_SERVERID);
+ EXPECT_TRUE(clientid->equals(expected));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_send is able to delete
+// existing options and that server applies those changes. In particular,
+// we are trying to send a packet without server-id. The packet should
+// be sent
+TEST_F(HooksDhcpv6SrvTest, pkt6SendDeleteServerId) {
+
+ // Install pkt6_send_delete_serverid_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_delete_serverid_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_send callback.
+ srv_->run();
+
+ // Check that the server indeed sent a malformed ADVERTISE
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Get that ADVERTISE
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Make sure that it does not have server-id
+ EXPECT_FALSE(adv->getOption(D6O_SERVERID));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_skip is able to set skip flag that
+// will cause the server to send an empty response.
+TEST_F(HooksDhcpv6SrvTest, pkt6SendSkip) {
+
+ // Install pkt6_send_skip_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_skip_callout));
+
+ // Let's create a simple REQUEST
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_send callback.
+ srv_->run();
+
+ // Check that the server sent the packet
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Get the first packet and check that it has zero length (i.e. the server
+ // did not do packing on its own)
+ Pkt6Ptr sent = srv_->fake_sent_.front();
+
+ // The actual size of sent packet should be 0
+ EXPECT_EQ(0, sent->getBuffer().getLength());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on pkt6_drop is able to set drop flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, pkt6SendDrop) {
+
+ // Install pkt6_send_drop_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_drop_callout));
+
+ // Let's create a simple REQUEST
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_send callback.
+ srv_->run();
+
+ // Check that the server does not send the packet
+ EXPECT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on buffer6_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv6SrvTest, buffer6SendSimple) {
+
+ // Install buffer6_send_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_send", buffer6_send_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_send callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer6_send", callback_name_);
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_resp_pkt6_.get() == adv.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("response6"));
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_resp_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on buffer6_send can set skip flag and that flag
+// causes the packet to not be sent
+TEST_F(HooksDhcpv6SrvTest, buffer6SendSkip) {
+
+ // Install buffer6_send_skip_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_send", buffer6_send_skip_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_send callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer6_send", callback_name_);
+
+ // Check that there is no packet sent.
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callouts installed on buffer6_send can set drop flag and that flag
+// causes the packet to not be sent
+TEST_F(HooksDhcpv6SrvTest, buffer6SendDrop) {
+
+ // Install buffer6_send_drop_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_send", buffer6_send_drop_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_send callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer6_send", callback_name_);
+
+ // Check that there is no packet sent
+ EXPECT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// This test checks if subnet6_select callout is triggered and reports
+// valid parameters
+TEST_F(HooksDhcpv6SrvTest, subnet6SelectSimple) {
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"" + valid_iface_ + "\" "
+ " }, {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP6(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = isc::config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Commit the config
+ CfgMgr::instance().commit();
+
+ // Install subnet6_select_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet6_select", subnet6_select_callout));
+
+ // Prepare solicit packet. Server should select first subnet for it
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface(valid_iface_);
+ sol->setIndex(valid_ifindex_);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_->earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_->initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_->processSolicit(ctx);
+
+ // Check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("subnet6_select", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+
+ const Subnet6Collection* exp_subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+
+ // The server is supposed to pick the first subnet, because of matching
+ // interface. Check that the value is reported properly.
+ ASSERT_TRUE(callback_subnet6_);
+ EXPECT_EQ(callback_subnet6_.get(), exp_subnets->begin()->get());
+
+ // Server is supposed to report two subnets
+ ASSERT_EQ(exp_subnets->size(), callback_subnet6collection_->size());
+ ASSERT_GE(exp_subnets->size(), 2);
+
+ // Compare that the available subnets are reported as expected
+ EXPECT_TRUE((*exp_subnets->begin())->get() == (*callback_subnet6collection_->begin())->get());
+ EXPECT_TRUE((*std::next(exp_subnets->begin()))->get() == (*std::next(callback_subnet6collection_->begin()))->get());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// This test checks if callout installed on subnet6_select hook point can pick
+// a different subnet.
+TEST_F(HooksDhcpv6SrvTest, subnet6SelectChange) {
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"" + valid_iface_ + "\" "
+ " }, {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP6(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = isc::config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Install subnet6_select_different_subnet_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet6_select", subnet6_select_different_subnet_callout));
+
+ // Prepare solicit packet. Server should select first subnet for it
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface(valid_iface_);
+ sol->setIndex(valid_ifindex_);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_->earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_->initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_->processSolicit(ctx);
+
+ // Check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // The response should have an address from second pool, so let's check it
+ OptionPtr tmp = adv->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ tmp = ia->getOption(D6O_IAADDR);
+ ASSERT_TRUE(tmp);
+ boost::shared_ptr<Option6IAAddr> addr_opt =
+ boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+ ASSERT_TRUE(addr_opt);
+
+ // Get all subnets and use second subnet for verification
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ // Advertised address must belong to the second pool (in subnet's range,
+ // in dynamic pool)
+ auto subnet = subnets->begin();
+ ++subnet;
+ EXPECT_TRUE((*subnet)->inRange(addr_opt->getAddress()));
+ EXPECT_TRUE((*subnet)->inPool(Lease::TYPE_NA, addr_opt->getAddress()));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks that subnet6_select is able to drop the packet.
+TEST_F(HooksDhcpv6SrvTest, subnet6SelectDrop) {
+
+ // Install subnet6_select_drop_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet6_select", subnet6_select_drop_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(PktCaptures::captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered subnet6_select callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// This test verifies that the leases6_committed hook point is not triggered
+// for the SOLICIT.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedSolicit) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ // Install leases6_committed_callout
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSolicit());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Make sure that the callout wasn't called.
+ EXPECT_TRUE(callback_name_.empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases6_committed hook point is not triggered
+// for the CONFIRM.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedConfirm) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ // Get a lease for the client.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Install leases6_committed_callout
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doConfirm());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Make sure that the callout wasn't called.
+ EXPECT_TRUE(callback_name_.empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases6_committed hook point is not triggered
+// for the INFREQUEST.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedInfRequest) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.useRelay();
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doInfRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Make sure that the callout wasn't called.
+ EXPECT_TRUE(callback_name_.empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of SOLICIT message with Rapid Commit option,
+// sent to allocate new lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRapidCommit) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"rapid-commit\": true, "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.useRapidCommit(true);
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSolicit());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that it is possible to park a SOLICIT packet including
+// Rapid Commit option as a result of the leases6_committed callouts. The
+// prefix delegation is requested with the Solicit packet.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedParkRapidCommitPrefixes) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"rapid-commit\": true, "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Create first client and perform SARR.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+ client1.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+ client1.useRapidCommit(true);
+
+ ASSERT_NO_THROW(configure(config, *client1.getServer()));
+
+ // This callout uses provided IO service object to post a function
+ // that unparks the packet. The packet is parked and can be unparked
+ // by simply calling IOService::poll.
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_park_callout));
+
+ ASSERT_NO_THROW(client1.doSolicit());
+
+ // We should be offered an address but the REPLY should not arrive
+ // at this point, because the packet is parked.
+ ASSERT_FALSE(client1.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be passed to the callout.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client1.getContext().query_);
+
+ // Reset all indicators because we'll be now creating a second client.
+ resetCalloutBuffers();
+
+ // Create the second client to test that it may communicate with the
+ // server while the previous packet is parked.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ client2.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:29::"));
+ client2.useRapidCommit(true);
+ ASSERT_NO_THROW(client2.doSolicit());
+
+ // The ADVERTISE should have been returned but not REPLAY, as this
+ // packet got parked too.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed.
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // There should be now two actions scheduled on our IO service
+ // by the invoked callouts. They unpark both REPLY messages.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client1.receiveResponse());
+ Pkt6Ptr rsp = client1.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client1.hasLeaseForPrefix(IOAddress("2001:db8:1:28::"), 64));
+
+ // Receive and check the second response.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ rsp = client2.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client2.hasLeaseForPrefix(IOAddress("2001:db8:1:29::"), 64));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client2.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REQUEST message sent to allocate new
+// lease or renew an existing lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRequest) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Request the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Requested lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Let's try to request again but force the client to request a different
+ // address with a different IAID.
+ client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
+
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // New lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(2, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
+
+ // The old lease is kept.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // The requested address is just a hint.
+ client.requestAddress(0x5577, IOAddress("4000::2"));
+
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Request a prefix: this should lead to an error as no prefix pool
+ // is configured.
+ client.requestPrefix(0x1122, 64, IOAddress("2001:db8:1000::"));
+
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check the error.
+ EXPECT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(0x1122));
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REQUEST message sent to allocate new
+// lease or renew an existing lease. Prefix variant.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRequestPrefix) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Request the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Requested lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Let's try to request again but force the client to request a different
+ // prefix with a different IAID.
+ client.requestPrefix(0x2233, 64, IOAddress("2001:db8:1:29::"));
+
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // New lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(2, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:29::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // The old lease is kept.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // The requested prefix is just a hint.
+ client.requestPrefix(0x5577, 64, IOAddress("4000::1"));
+
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Request an address: this should lead to an error as no address pool
+ // is configured.
+ client.requestAddress(0x1122, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check the error.
+ EXPECT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(0x1122));
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of RENEW message sent to allocate new
+// lease or renew an existing lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRenew) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Renew the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Renewed lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ resetCalloutBuffers();
+
+ // Let's try to renew again but force the client to renew a different
+ // address with a different IAID.
+ client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
+
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // New lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(2, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
+
+ // The old lease is kept.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // The renewed address is just a hint.
+ client.requestAddress(0x5577, IOAddress("4000::2"));
+
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Renew a prefix: this should lead to an error as no prefix pool
+ // is configured.
+ client.requestPrefix(0x1122, 64, IOAddress("2001:db8:1000::"));
+
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check the error.
+ EXPECT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(0x1122));
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of RENEW message sent to allocate new
+// lease or renew an existing lease. Prefix variant.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRenewPrefix) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ resetCalloutBuffers();
+
+ // Renew the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Renewed lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Let's try to renew again but force the client to renew a different
+ // prefix with a different IAID.
+ client.requestPrefix(0x2233, 64, IOAddress("2001:db8:1:29::"));
+
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // New lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(2, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:29::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // The old lease is kept.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // The renewed prefix is just a hint.
+ client.requestPrefix(0x5577, 64, IOAddress("4000::1"));
+
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Renew an address: this should lead to an error as no address pool
+ // is configured.
+ client.requestAddress(0x1122, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check the error.
+ EXPECT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(0x1122));
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REBIND message sent to allocate new
+// lease or renew an existing lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRebind) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Rebind the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Rebound lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Let's try to rebind again but force the client to rebind a different
+ // address with a different IAID.
+ client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
+
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // New lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(2, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
+
+ // The old lease is kept.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // The rebound address is just a hint.
+ client.requestAddress(0x5577, IOAddress("4000::2"));
+
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Rebind a prefix: this should lead to an error as no prefix pool
+ // is configured.
+ client.requestPrefix(0x1122, 64, IOAddress("2001:db8:1000::"));
+
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check the error.
+ EXPECT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(0x1122));
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REBIND message sent to allocate new
+// lease or renew an existing lease. Prefix variant.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRebindPrefix) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Rebind the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Rebound lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Let's try to rebind again but force the client to rebind a different
+ // prefix with a different IAID.
+ client.requestPrefix(0x2233, 64, IOAddress("2001:db8:1:29::"));
+
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // New lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(2, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:29::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // The old lease is kept.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // The rebound prefix is just a hint.
+ client.requestPrefix(0x5577, 64, IOAddress("4000::1"));
+
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ lease = callback_new_leases6_->at(2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Rebind an address: this should lead to an error as no address pool
+ // is configured.
+ client.requestAddress(0x1122, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check the error.
+ EXPECT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(0x1122));
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_EQ(3, callback_new_leases6_->size());
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases6_committed callout is executed
+// when DECLINE is processed. The declined lease is expected to be passed
+// in leases6 argument to the callout.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedDecline) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ ASSERT_NO_THROW(client.doDecline());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // No deleted leases.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Declined lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases6_committed callout is executed
+// when DECLINE is processed. Variant with 2 IA_NAs.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedDeclineTwoNAs) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(client.doSARR());
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doDecline());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // No deleted leases.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ ASSERT_TRUE(callback_deleted_leases6_->empty());
+
+ // Declined lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(2, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+ lease = callback_new_leases6_->at(1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::29", lease->addr_.toText());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases6_committed callout is executed
+// with deleted leases as argument when RELEASE is processed.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedRelease) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ ASSERT_NO_THROW(client.doRelease());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // No new allocations.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_TRUE(callback_new_leases6_->empty());
+
+ // Released lease should be returned.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_EQ(1, callback_deleted_leases6_->size());
+ Lease6Ptr lease = callback_deleted_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases6_committed callout is executed
+// with deleted leases as argument when RELEASE is processed. Prefix variant.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedReleasePrefix) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doRelease());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // No new allocations.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_TRUE(callback_new_leases6_->empty());
+
+ // Released lease should be returned.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_EQ(1, callback_deleted_leases6_->size());
+ Lease6Ptr lease = callback_deleted_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the leases6_committed callout is executed
+// with deleted leases as argument when RELEASE is processed.
+// Variant with two addresses and two prefixes.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedReleaseMultiple) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:2::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8::/32\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ // In theory we can reuse the IAID but copyIAsFromLeases() copies
+ // only one lease...
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestPrefix(0xabcb, 64, IOAddress("2001:db8:2:28::"));
+ client.requestAddress(0x2233, IOAddress("2001:db8:1::29"));
+ client.requestPrefix(0x2234, 64, IOAddress("2001:db8:2:29::"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(client.doSARR());
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doRelease());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // No new allocations.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_TRUE(callback_new_leases6_->empty());
+
+ // Released lease should be returned.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_EQ(4, callback_deleted_leases6_->size());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REQUEST message sent to reuse an
+// existing lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedCache) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\", "
+ " \"cache-threshold\": .25 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Request the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Requested lease should not be present, because it is reused.
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_TRUE(callback_new_leases6_->empty());
+
+ // Deleted lease must not be present, because it is renewed.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REQUEST message sent to reuse an
+// existing lease. Prefix variant.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedCachePrefix) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\", "
+ " \"cache-threshold\": .25 "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_callout));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be returned.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+
+ resetCalloutBuffers();
+
+ // Request the lease and make sure that the callout has been executed.
+ ASSERT_NO_THROW(client.doRequest());
+
+ // Make sure that we received a response
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Requested lease should not be present, because it is reused.
+ ASSERT_TRUE(callback_new_leases6_);
+ EXPECT_TRUE(callback_new_leases6_->empty());
+
+ // Deleted lease must not be present, because it is renewed.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that it is possible to park a packet as a result of
+// the leases6_committed callouts.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedParkRequests) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Create first client and perform SARR.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+ client1.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(configure(config, *client1.getServer()));
+
+ // This callout uses provided IO service object to post a function
+ // that unparks the packet. The packet is parked and can be unparked
+ // by simply calling IOService::poll.
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_park_callout));
+
+ ASSERT_NO_THROW(client1.doSARR());
+
+ // We should be offered an address but the REPLY should not arrive
+ // at this point, because the packet is parked.
+ ASSERT_FALSE(client1.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be passed to the callout.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client1.getContext().query_);
+
+ // Reset all indicators because we'll be now creating a second client.
+ resetCalloutBuffers();
+
+ // Create the second client to test that it may communicate with the
+ // server while the previous packet is parked.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ client2.requestAddress(0xabca, IOAddress("2001:db8:1::29"));
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // The ADVERTISE should have been returned but not REPLAY, as this
+ // packet got parked too.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed.
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // There should be now two actions scheduled on our IO service
+ // by the invoked callouts. They unpark both REPLY messages.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client1.receiveResponse());
+ Pkt6Ptr rsp = client1.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::28")));
+
+ // Receive and check the second response.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ rsp = client2.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::29")));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client2.getContext().query_);
+}
+
+// This test verifies that it is possible to park a packet as a result of
+// the leases6_committed callouts. Prefix variant.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedParkRequestsPrefixes) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": [ {"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 56, "
+ " \"delegated-len\": 64 } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Create first client and perform SARR.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+ client1.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+
+ ASSERT_NO_THROW(configure(config, *client1.getServer()));
+
+ // This callout uses provided IO service object to post a function
+ // that unparks the packet. The packet is parked and can be unparked
+ // by simply calling IOService::poll.
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_park_callout));
+
+ ASSERT_NO_THROW(client1.doSARR());
+
+ // We should be offered an address but the REPLY should not arrive
+ // at this point, because the packet is parked.
+ ASSERT_FALSE(client1.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("deleted_leases6");
+ expected_argument_names.push_back("leases6");
+
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Newly allocated lease should be passed to the callout.
+ ASSERT_TRUE(callback_new_leases6_);
+ ASSERT_EQ(1, callback_new_leases6_->size());
+ Lease6Ptr lease = callback_new_leases6_->at(0);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+ EXPECT_EQ(64, lease->prefixlen_);
+
+ // Deleted lease must not be present, because it is a new allocation.
+ ASSERT_TRUE(callback_deleted_leases6_);
+ EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client1.getContext().query_);
+
+ // Reset all indicators because we'll be now creating a second client.
+ resetCalloutBuffers();
+
+ // Create the second client to test that it may communicate with the
+ // server while the previous packet is parked.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ client2.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:29::"));
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // The ADVERTISE should have been returned but not REPLAY, as this
+ // packet got parked too.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed.
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // There should be now two actions scheduled on our IO service
+ // by the invoked callouts. They unpark both REPLY messages.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client1.receiveResponse());
+ Pkt6Ptr rsp = client1.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client1.hasLeaseForPrefix(IOAddress("2001:db8:1:28::"), 64));
+
+ // Receive and check the second response.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ rsp = client2.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client2.hasLeaseForPrefix(IOAddress("2001:db8:1:29::"), 64));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client2.getContext().query_);
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are triggered.
+TEST_F(HooksDhcpv6SrvTest, lease6RenewSimple) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_renew_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_renew", lease6_renew_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRenew(req);
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_renew", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+ EXPECT_TRUE(callback_ia_na_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ expected_argument_names.push_back("ia_na");
+
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt;
+ ASSERT_NO_FATAL_FAILURE(addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2()));
+
+ ASSERT_TRUE(addr_opt);
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that the lease has been returned
+ ASSERT_TRUE(callback_lease6_);
+
+ // Check that the returned lease6 in callout is the same as the one in the
+ // database
+ EXPECT_TRUE(*callback_lease6_ == *l);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are able to change the lease being updated.
+TEST_F(HooksDhcpv6SrvTest, lease6RenewLeaseUpdate) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_renew_update_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_renew", lease6_renew_update_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Turn on tee time calculation so we can see the effect of overriding
+ // the lease life time.
+ subnet_->setCalculateTeeTimes(true);
+ Triplet<uint32_t> unspecified;
+ subnet_->setT1(unspecified);
+ subnet_->setT2(unspecified);
+ subnet_->setT1Percent(0.60);
+ subnet_->setT2Percent(0.80);
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRenew(req);
+ ASSERT_TRUE(reply);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt;
+ ASSERT_NO_FATAL_FAILURE(addr_opt = checkIA_NA(reply, 1000, 602, 802));
+
+ ASSERT_TRUE(addr_opt);
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that we chose the distinct override values
+ ASSERT_NE(override_preferred_, subnet_->getPreferred());
+ EXPECT_NE(override_valid_, subnet_->getValid());
+
+ // Check that preferred, valid were overridden the callout
+ EXPECT_EQ(override_preferred_, l->preferred_lft_);
+ EXPECT_EQ(override_valid_, l->valid_lft_);
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // Equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ Lease6Ptr deleted_lease =
+ LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr_opt->getAddress());
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(deleted_lease));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are able to set the skip flag that will
+// reject the renewal
+TEST_F(HooksDhcpv6SrvTest, lease6RenewSkip) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_renew_skip_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_renew", lease6_renew_skip_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt are really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRenew(req);
+ ASSERT_TRUE(reply);
+
+ // Check that our callback was called
+ EXPECT_EQ("lease6_renew", callback_name_);
+
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+
+ // Check that the old values are still there and they were not
+ // updated by the renewal
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that incoming (positive) REBIND can be handled properly,
+// and the lease6_rebind callouts are triggered.
+TEST_F(HooksDhcpv6SrvTest, lease6RebindSimple) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_rebind_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_rebind", lease6_rebind_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during REBIND.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a REBIND
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REBIND, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr rebound_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(rebound_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRebind(req);
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_rebind", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+ EXPECT_TRUE(callback_ia_na_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ expected_argument_names.push_back("ia_na");
+
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt;
+ ASSERT_NO_FATAL_FAILURE(addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2()));
+
+ ASSERT_TRUE(addr_opt);
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that the lease has been returned
+ ASSERT_TRUE(callback_lease6_);
+
+ // Check that the returned lease6 in callout is the same as the one in the
+ // database
+ EXPECT_TRUE(*callback_lease6_ == *l);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that incoming (positive) REBIND can be handled properly,
+// and the lease6_rebind callouts are able to change the lease being updated.
+TEST_F(HooksDhcpv6SrvTest, lease6RebindLeaseUpdate) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_rebind_update_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_rebind", lease6_rebind_update_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during REBIND.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a REBIND
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REBIND, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr rebound_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(rebound_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Turn on tee time calculation so we can see the effect of overriding
+ // the lease life time.
+ subnet_->setCalculateTeeTimes(true);
+ Triplet<uint32_t> unspecified;
+ subnet_->setT1(unspecified);
+ subnet_->setT2(unspecified);
+ subnet_->setT1Percent(0.60);
+ subnet_->setT2Percent(0.80);
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRebind(req);
+ ASSERT_TRUE(reply);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ // Note we also verify that T1 and T2 were calculated correctly.
+ boost::shared_ptr<Option6IAAddr> addr_opt;
+ ASSERT_NO_FATAL_FAILURE(addr_opt = checkIA_NA(reply, 1000, 602, 802));
+
+ ASSERT_TRUE(addr_opt);
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that we chose the distinct override values
+ ASSERT_NE(override_preferred_, subnet_->getPreferred());
+ EXPECT_NE(override_valid_, subnet_->getValid());
+
+ // Check that preferred and valid were overridden in the callout
+ EXPECT_EQ(override_preferred_, l->preferred_lft_);
+ EXPECT_EQ(override_valid_, l->valid_lft_);
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // Equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ Lease6Ptr deleted_lease =
+ LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr_opt->getAddress());
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(deleted_lease));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that incoming (positive) REBIND can be handled properly,
+// and the lease6_rebind callouts are able to set the skip flag that will
+// reject the rebinding
+TEST_F(HooksDhcpv6SrvTest, lease6RebindSkip) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_rebind_skip_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_rebind", lease6_rebind_skip_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during REBIND.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Check that preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a REBIND
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REBIND, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ req->setIface("eth0");
+ req->setIndex(ETH0_INDEX);
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr rebound_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(rebound_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRebind(req);
+ ASSERT_TRUE(reply);
+
+ // Check that our callback was called
+ EXPECT_EQ("lease6_rebind", callback_name_);
+
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+
+ // Check that the old values are still there and they were not
+ // updated by the rebinding
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(HooksDhcpv6SrvTest, lease6ReleaseSimple) {
+ NakedDhcpv6Srv srv(0);
+ CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setFlushReclaimedTimerWaitTime(0);
+ CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setHoldReclaimedTime(0);
+
+ // Install lease6_release_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr rel = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ rel->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ rel->addOption(ia);
+ rel->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ rel->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(rel);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_FALSE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_FALSE(l);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(rel);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+// This test is using infinite lease with lease affinity enabled.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(HooksDhcpv6SrvTest, lease6ReleaseSimpleInfiniteLease) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_release_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ lease->valid_lft_ = Lease::INFINITY_LFT;
+ lease->current_valid_lft_ = Lease::INFINITY_LFT;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr rel = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ rel->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ rel->addOption(ia);
+ rel->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ rel->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(rel);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_FALSE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_FALSE(l);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(rel);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(HooksDhcpv6SrvTest, lease6ReleaseSimpleNoDelete) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_release_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check that the lease is not gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_TRUE(l);
+
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This is a variant of the previous test that tests that callouts are
+// properly invoked for the prefix release case.
+TEST_F(HooksDhcpv6SrvTest, lease6ReleasePrefixSimple) {
+ NakedDhcpv6Srv srv(0);
+ CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setFlushReclaimedTimerWaitTime(0);
+ CfgMgr::instance().getCurrentCfg()->getCfgExpiration()->setHoldReclaimedTime(0);
+
+ // Install lease6_release_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_callout));
+
+ const IOAddress prefix("2001:db8:1:2:1::");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the prefix we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr(), 80));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_PD, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAPrefix(D6O_IAPREFIX, prefix, 80,
+ 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_FALSE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_FALSE(l);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This is a variant of the previous test that tests that callouts are
+// properly invoked for the prefix release case.
+// This test is using infinite lease with lease affinity enabled.
+TEST_F(HooksDhcpv6SrvTest, lease6ReleasePrefixSimpleInfiniteLease) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_release_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_callout));
+
+ const IOAddress prefix("2001:db8:1:2:1::");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the prefix we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr(), 80));
+ lease->cltt_ = 1234;
+ lease->valid_lft_ = Lease::INFINITY_LFT;
+ lease->current_valid_lft_ = Lease::INFINITY_LFT;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_PD, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAPrefix(D6O_IAPREFIX, prefix, 80,
+ 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_FALSE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_FALSE(l);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This is a variant of the previous test that tests that callouts are
+// properly invoked for the prefix release case.
+TEST_F(HooksDhcpv6SrvTest, lease6ReleasePrefixSimpleNoDelete) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_release_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_callout));
+
+ const IOAddress prefix("2001:db8:1:2:1::");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the prefix we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr(), 80));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_PD, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAPrefix(D6O_IAPREFIX, prefix, 80,
+ 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_qry_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check that the lease is not gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+ ASSERT_TRUE(l);
+
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_TRUE(l);
+
+ EXPECT_EQ(l->valid_lft_, 0);
+ EXPECT_EQ(l->preferred_lft_, 0);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(req);
+}
+
+// This test verifies that skip flag returned by a callout installed on the
+// lease6_release hook point will keep the lease.
+TEST_F(HooksDhcpv6SrvTest, lease6ReleaseSkip) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_release_skip_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_skip_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr rel = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ rel->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ rel->addOption(ia);
+ rel->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ rel->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(rel);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that the lease is still there
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_TRUE(l);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(rel);
+}
+
+// This test verifies that drop flag returned by a callout installed on the
+// lease6_release hook point will keep the lease.
+TEST_F(HooksDhcpv6SrvTest, lease6ReleaseDrop) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install lease6_release_drop_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_drop_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr rel = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ rel->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(D6O_IA_NA, iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ rel->addOption(ia);
+ rel->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ rel->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a response
+ Pkt6Ptr reply = srv.processRelease(rel);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that the lease is still there
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, *duid_, iaid,
+ subnet_->getID());
+ ASSERT_TRUE(l);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(rel);
+}
+
+// This test checks that the basic decline hook (lease6_decline) is
+// triggered properly.
+TEST_F(HooksDhcpv6SrvTest, lease6DeclineSimple) {
+ IfaceMgrTestConfig test_config(true);
+
+ // Install lease6_decline_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_decline", lease6_decline_callout));
+
+ HooksManager::setTestMode(true);
+
+ // Get an address and decline it. DUIDs, IAID match and we send valid
+ // address, so the decline procedure should be successful.
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1234, VALID_ADDR, SHOULD_PASS);
+
+ // Check that the proper callback was called.
+ EXPECT_EQ("lease6_decline", callback_name_);
+
+ // And valid parameters were passed.
+ ASSERT_TRUE(callback_qry_pkt6_);
+ ASSERT_TRUE(callback_lease6_);
+
+ // Test sanity check - it was a decline, right?
+ EXPECT_EQ(DHCPV6_DECLINE, callback_qry_pkt6_->getType());
+
+ // Get the address from this decline.
+ OptionPtr ia = callback_qry_pkt6_->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia);
+ boost::shared_ptr<Option6IAAddr> addr_opt =
+ boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
+ ASSERT_TRUE(addr_opt);
+ IOAddress addr(addr_opt->getAddress());
+
+ // Now get a lease from the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+ // Now check that it's indeed declined.
+ EXPECT_EQ(Lease::STATE_DECLINED, from_mgr->state_);
+
+ // And that the parameters passed to callout are consistent with the database
+ EXPECT_EQ(addr, from_mgr->addr_);
+ EXPECT_EQ(addr, callback_lease6_->addr_);
+
+ // Pkt passed to a callout must be configured to copy retrieved options.
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// Test that the lease6_decline hook point can handle SKIP status.
+TEST_F(HooksDhcpv6SrvTest, lease6DeclineSkip) {
+ IfaceMgrTestConfig test_config(true);
+
+ // Install lease6_decline_skip_callout. It will set the status to skip
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_decline", lease6_decline_skip_callout));
+
+ HooksManager::setTestMode(true);
+
+ // Get an address and decline it. DUIDs, IAID match and we send valid
+ // address, so the decline procedure should be successful.
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1234, VALID_ADDR, SHOULD_FAIL);
+
+ // Check that the proper callback was called.
+ EXPECT_EQ("lease6_decline", callback_name_);
+
+ // And valid parameters were passed.
+ ASSERT_TRUE(callback_qry_pkt6_);
+ ASSERT_TRUE(callback_lease6_);
+
+ // Test sanity check - it was a decline, right?
+ EXPECT_EQ(DHCPV6_DECLINE, callback_qry_pkt6_->getType());
+
+ // Get the address from this decline.
+ OptionPtr ia = callback_qry_pkt6_->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia);
+ boost::shared_ptr<Option6IAAddr> addr_opt =
+ boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
+ ASSERT_TRUE(addr_opt);
+ IOAddress addr(addr_opt->getAddress());
+
+ // Now get a lease from the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+ // Now check that it's NOT declined.
+ EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_);
+
+ // And that the parameters passed to callout are consistent with the database
+ EXPECT_EQ(addr, from_mgr->addr_);
+ EXPECT_EQ(addr, callback_lease6_->addr_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// Test that the lease6_decline hook point can handle DROP status.
+TEST_F(HooksDhcpv6SrvTest, lease6DeclineDrop) {
+ IfaceMgrTestConfig test_config(true);
+
+ // Install lease6_decline_drop_callout. It will set the status to drop
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_decline", lease6_decline_drop_callout));
+
+ HooksManager::setTestMode(true);
+
+ // Get an address and decline it. DUIDs, IAID match and we send valid
+ // address, so it would work, but the callout sets status to DROP, so
+ // the server should not update the lease and should not send back any
+ // packets.
+ Dhcp6Client client;
+ acquireAndDecline(client, "01:02:03:04:05:06", 1234, "01:02:03:04:05:06",
+ 1234, VALID_ADDR, SHOULD_FAIL);
+
+ // Check that the proper callback was called.
+ EXPECT_EQ("lease6_decline", callback_name_);
+
+ // And valid parameters were passed.
+ ASSERT_TRUE(callback_qry_pkt6_);
+ ASSERT_TRUE(callback_lease6_);
+
+ // Test sanity check - it was a decline, right?
+ EXPECT_EQ(DHCPV6_DECLINE, callback_qry_pkt6_->getType());
+
+ // Get the address from this decline.
+ OptionPtr ia = callback_qry_pkt6_->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia);
+ boost::shared_ptr<Option6IAAddr> addr_opt =
+ boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
+ ASSERT_TRUE(addr_opt);
+ IOAddress addr(addr_opt->getAddress());
+
+ // Now get a lease from the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+ // Now check that it's NOT declined.
+ EXPECT_EQ(Lease::STATE_DEFAULT, from_mgr->state_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(client.getContext().query_);
+}
+
+// Should test with one NA and two addresses but need an example first...
+// Checks if callout installed on host6_identifier can generate an
+// identifier and whether that identifier is actually used.
+TEST_F(HooksDhcpv6SrvTest, host6Identifier) {
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"preferred-lifetime\": 3000,\n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000,\n"
+ "\"host-reservation-identifiers\": [ \"flex-id\" ],\n"
+ "\"subnet6\": [ {\n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],\n"
+ " \"subnet\": \"2001:db8::/48\", \n"
+ " \"interface\": \"" + valid_iface_ + "\",\n"
+ " \"reservations\": [\n"
+ " {\n"
+ " \"flex-id\": \"'foo'\",\n"
+ " \"ip-addresses\": [ \"2001:db8::f00\" ]\n"
+ " }\n"
+ " ]\n"
+ " } ]\n,"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP6(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = isc::config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Install host6_identifier_foo_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "host6_identifier", host6_identifier_foo_callout));
+
+ // Prepare solicit packet. Server should select first subnet for it
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface(valid_iface_);
+ sol->setIndex(valid_ifindex_);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_->earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_->initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_->processSolicit(ctx);
+
+ // Check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("host6_identifier", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+
+ // Now check if we got the reserved address
+ OptionPtr tmp = adv->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt;
+ ASSERT_NO_FATAL_FAILURE(addr_opt = checkIA_NA(adv, 234, 1000, 2000));
+
+ ASSERT_TRUE(addr_opt);
+ ASSERT_EQ("2001:db8::f00", addr_opt->getAddress().toText());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Checks if callout installed on host6_identifier can generate an identifier of
+// other type. This particular callout always returns hwaddr.
+TEST_F(HooksDhcpv6SrvTest, host6IdentifierHWAddr) {
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"preferred-lifetime\": 3000,\n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000,\n"
+ "\"host-reservation-identifiers\": [ \"flex-id\" ],\n"
+ "\"subnet6\": [ {\n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],\n"
+ " \"subnet\": \"2001:db8::/48\", \n"
+ " \"interface\": \"" + valid_iface_ + "\",\n"
+ " \"reservations\": [\n"
+ " {\n"
+ " \"hw-address\": \"00:01:02:03:04:05\",\n"
+ " \"ip-addresses\": [ \"2001:db8::f00\" ]\n"
+ " }\n"
+ " ]\n"
+ " } ]\n,"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ EXPECT_NO_THROW(json = parseDHCP6(config));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = isc::config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Install host6_identifier_hwaddr_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "host6_identifier", host6_identifier_hwaddr_callout));
+
+ // Prepare solicit packet. Server should select first subnet for it
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface(valid_iface_);
+ sol->setIndex(valid_ifindex_);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_->earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_->initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_->processSolicit(ctx);
+
+ // Check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("host6_identifier", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_qry_pkt6_.get() == sol.get());
+
+ // Now check if we got the reserved address
+ OptionPtr tmp = adv->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt;
+ ASSERT_NO_FATAL_FAILURE(addr_opt = checkIA_NA(adv, 234, 1000, 2000));
+
+ ASSERT_TRUE(addr_opt);
+ ASSERT_EQ("2001:db8::f00", addr_opt->getAddress().toText());
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(sol);
+}
+
+// Verifies that libraries are unloaded by server destruction
+// The callout libraries write their library index number to a marker
+// file upon load and unload, making it simple to test whether or not
+// the load and unload callouts have been invoked.
+TEST_F(LoadUnloadDhcpv6SrvTest, unloadLibraries) {
+
+ ASSERT_NO_THROW(server_.reset(new NakedDhcpv6Srv(0)));
+
+ // Ensure no marker files to start with.
+ ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Load the test libraries
+ HookLibsCollection libraries;
+ libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_1),
+ ConstElementPtr()));
+ libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_2),
+ ConstElementPtr()));
+
+ ASSERT_TRUE(HooksManager::loadLibraries(libraries));
+
+ // Verify that they load functions created the LOAD_MARKER_FILE
+ // and that its contents are correct: "12" - the first library
+ // appends "1" to the file, the second appends "2"). Also
+ // check that the unload marker file does not yet exist.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Destroy the server, instance which should unload the libraries.
+ server_.reset();
+
+ // Check that the libraries were unloaded. The libraries are
+ // unloaded in the reverse order to which they are loaded, and
+ // this should be reflected in the unload file.
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+}
+
+// Verifies that libraries incompatible with multi threading are not loaded by
+// the server.
+// The callout libraries write their library index number to a marker
+// file upon load and unload, making it simple to test whether or not
+// the load and unload callouts have been invoked.
+TEST_F(LoadUnloadDhcpv6SrvTest, failLoadIncompatibleLibraries) {
+
+ ASSERT_NO_THROW(server_.reset(new NakedDhcpv6Srv(0)));
+
+ // Ensure no marker files to start with.
+ ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Load the test libraries
+ HookLibsCollection libraries;
+ libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_2),
+ ConstElementPtr()));
+
+ ASSERT_FALSE(HooksManager::loadLibraries(libraries, true));
+
+ // The library is missing multi_threading_compatible function so loading
+ // should fail
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ libraries.clear();
+ libraries.push_back(make_pair(std::string(CALLOUT_LIBRARY_3),
+ ConstElementPtr()));
+
+ ASSERT_FALSE(HooksManager::loadLibraries(libraries, true));
+
+ // The library is not multi threading compatible so loading should fail
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Destroy the server, instance which should unload the libraries.
+ server_.reset();
+
+ // Check that the libraries were unloaded. The libraries are
+ // unloaded in the reverse order to which they are loaded, and
+ // this should be reflected in the unload file.
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+}
+
+// Checks if callouts installed on the dhcp6_srv_configured ared indeed called
+// and all the necessary parameters are passed.
+TEST_F(LoadUnloadDhcpv6SrvTest, Dhcpv6SrvConfigured) {
+ for (string parameters : {
+ "",
+ R"(, "parameters": { "mode": "fail-without-error" } )",
+ R"(, "parameters": { "mode": "fail-with-error" } )"}) {
+
+ reset();
+
+ boost::shared_ptr<ControlledDhcpv6Srv> srv(new ControlledDhcpv6Srv(0));
+
+ // Ensure no marker files to start with.
+ ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(SRV_CONFIG_MARKER_FILE));
+
+ // Minimal valid configuration for the server. It includes the
+ // section which loads the callout library #3, which implements
+ // dhcp6_srv_configured callout. MT needs to be disabled
+ // since the library is single-threaded.
+ string config_str =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ " },"
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"subnet6\": [ ],"
+ " \"valid-lifetime\": 4000,"
+ " \"lease-database\": {"
+ " \"type\": \"memfile\","
+ " \"persist\": false"
+ " },"
+ " \"hooks-libraries\": ["
+ " {"
+ " \"library\": \"" + std::string(CALLOUT_LIBRARY_3) + "\""
+ + parameters +
+ " }"
+ R"( ],
+ "multi-threading": {
+ "enable-multi-threading": false
+ }
+ })";
+
+
+ ConstElementPtr config = Element::fromJSON(config_str);
+
+ // Configure the server.
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = srv->processConfig(config));
+
+ // Make sure there were no errors.
+ int status_code;
+ parseAnswer(status_code, answer);
+ if (parameters.empty()) {
+ EXPECT_EQ(0, status_code);
+ string expected = "{ \"arguments\": { \"hash\": \"";
+ ConstElementPtr config =
+ CfgMgr::instance().getStagingCfg()->toElement();
+ expected += BaseCommandMgr::getHash(config);
+ expected += "\" }, \"result\": 0, \"text\": ";
+ expected += "\"Configuration successful.\" }";
+ EXPECT_EQ(answer->str(), expected);
+ } else {
+ EXPECT_EQ(1, status_code);
+ if (parameters.find("fail-without-error") != string::npos) {
+ EXPECT_EQ(answer->str(), R"({ "result": 1, "text": "unknown error" })");
+ } else if (parameters.find("fail-with-error") != string::npos) {
+ EXPECT_EQ(answer->str(),
+ R"({ "result": 1, "text": "user explicitly configured me to fail" })");
+ } else {
+ GTEST_FAIL() << "unchecked test case";
+ }
+ }
+
+ // The hook library should have been loaded.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "3"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+ // The dhcp6_srv_configured should have been invoked and the provided
+ // parameters should be recorded.
+ EXPECT_TRUE(checkMarkerFile(SRV_CONFIG_MARKER_FILE,
+ "3io_contextjson_confignetwork_stateserver_config"));
+
+ // Destroy the server, instance which should unload the libraries.
+ srv.reset();
+
+ // The server was destroyed, so the unload() function should now
+ // include the library number in its marker file.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "3"));
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "3"));
+ EXPECT_TRUE(checkMarkerFile(SRV_CONFIG_MARKER_FILE,
+ "3io_contextjson_confignetwork_stateserver_config"));
+ }
+}
+
+// This test verifies that parked-packet-limit is properly enforced.
+TEST_F(HooksDhcpv6SrvTest, leases6ParkedPacketLimit) {
+ IfaceMgrTestConfig test_config(true);
+
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"parked-packet-limit\": 1,"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config, *srv_));
+
+ // Verify we have no packets parked.
+ const auto& parking_lot = ServerHooks::getServerHooks()
+ .getParkingLotPtr("leases6_committed");
+ ASSERT_TRUE(parking_lot);
+ ASSERT_EQ(0, parking_lot->size());
+
+ // Statistic should not show any drops.
+ EXPECT_EQ(0, getStatistic("pkt6-receive-drop"));
+
+ // This callout uses provided IO service object to post a function
+ // that unparks the packet. The packet is parked and can be unparked
+ // by simply calling IOService::poll.
+ ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "leases6_committed", leases6_committed_park_callout));
+
+ // Create first client and perform SARR.
+ Dhcp6Client client1(srv_);
+ client1.setInterface("eth1");
+ client1.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+ ASSERT_NO_THROW(client1.doSARR());
+
+ // We should be offered an address but the REPLY should not arrive
+ // at this point, because the packet is parked.
+ ASSERT_FALSE(client1.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Verify we have one parked packet and no drops.
+ ASSERT_EQ(1, parking_lot->size());
+ EXPECT_EQ(0, getStatistic("pkt6-receive-drop"));
+
+ // Reset all indicators because we'll be now creating a second client.
+ resetCalloutBuffers();
+
+ // Create the second client to test that it may communicate with the
+ // server while the previous packet is parked.
+ Dhcp6Client client2(srv_);
+ client2.setInterface("eth1");
+ client2.requestAddress(0xabca, IOAddress("2001:db8:1::29"));
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // The ADVERTISE should have been returned but not REPLY, this
+ // packet should have been dropped.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Check that no callback was called.
+ EXPECT_EQ("", callback_name_);
+
+ // Verify we have one parked packet and one drop.
+ ASSERT_EQ(1, parking_lot->size());
+ EXPECT_EQ(1, getStatistic("pkt6-receive-drop"));
+
+ // There should now be one action scheduled on our IO service
+ // by the invoked callout. It should unpark the REPLY message
+ // for client1.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client1.receiveResponse());
+ Pkt6Ptr rsp = client1.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::28")));
+
+ // Verify we have no parked packets and one drop.
+ ASSERT_EQ(0, parking_lot->size());
+ EXPECT_EQ(1, getStatistic("pkt6-receive-drop"));
+
+ // Should not anything to receive for client2.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Reset all indicators because we'll be now creating a second client.
+ resetCalloutBuffers();
+
+ // Retry the second client and verify that it is allowed to park
+ // and be responded to.
+ client2.requestAddress(0xabcb, IOAddress("2001:db8:1::29"));
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // The ADVERTISE should have been returned but not REPLY, that
+ // packet should have been parked.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Check that the callback was called.
+ EXPECT_EQ("leases6_committed", callback_name_);
+
+ // Verify we again have one parked packet and one drop.
+ ASSERT_EQ(1, parking_lot->size());
+ EXPECT_EQ(1, getStatistic("pkt6-receive-drop"));
+
+ // There should now be one action scheduled on our IO service
+ // by the invoked callout. It should unpark the REPLY message
+ // for client2.
+ ASSERT_NO_THROW(io_service_->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ rsp = client2.getContext().response_;
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPV6_REPLY, rsp->getType());
+ EXPECT_TRUE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::29")));
+
+ // Verify we no parked packets and one drop.
+ ASSERT_EQ(0, parking_lot->size());
+ EXPECT_EQ(1, getStatistic("pkt6-receive-drop"));
+}
+
+} // namespace
diff --git a/src/bin/dhcp6/tests/host_unittest.cc b/src/bin/dhcp6/tests/host_unittest.cc
new file mode 100644
index 0000000..efc3f46
--- /dev/null
+++ b/src/bin/dhcp6/tests/host_unittest.cc
@@ -0,0 +1,2653 @@
+// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/lexical_cast.hpp>
+#include <functional>
+#include <list>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used by the Host reservation unit tests.
+///
+/// - Configuration 0:
+/// Single subnet with two reservations, one with a hostname, one without
+///
+/// - Configuration 1:
+/// Multiple reservations using different host identifiers.
+///
+/// - Configuration 2:
+/// Same as configuration 1 but 'host-reservation-identifiers' specified
+/// in non-default order.
+///
+/// - Configuration 3:
+/// - Used to test that host specific options override pool specific,
+/// subnet specific and global options.
+///
+/// - Configuration 4:
+/// - Used to test that client receives options solely specified in a
+/// host scope.
+///
+/// - Configuration 5:
+/// - Used to test that host specific vendor options override globally
+/// specified vendor options.
+///
+/// - Configuration 6:
+/// - One subnet with very short pool, i.e. two addresses
+///
+/// - Configuration 7:
+/// - Similar to Configuration 6, but one of the addresses reserved to client
+/// with the DUID 04:03:02:01.
+///
+/// Descriptions of next configurations are in the comment with the number.
+const char* CONFIGS[] = {
+ // Configuration 0:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1:1::/64\" } ],"
+ " \"interface\" : \"eth0\" , "
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:04\","
+ " \"ip-addresses\": [ \"2001:db8:1:1::babe\" ],"
+ " \"hostname\": \"alice\""
+ " },"
+ " {"
+ " \"duid\": \"01:02:03:05\","
+ " \"ip-addresses\": [ \"2001:db8:1:1::babf\" ]"
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 1:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"mac-sources\": [ \"ipv6-link-local\" ], "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"interface\" : \"eth0\" , "
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"38:60:77:d5:ff:ee\","
+ " \"ip-addresses\": [ \"2001:db8:1::1\" ]"
+ " },"
+ " {"
+ " \"duid\": \"01:02:03:05\","
+ " \"ip-addresses\": [ \"2001:db8:1::2\" ]"
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 2:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ],"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"mac-sources\": [ \"ipv6-link-local\" ], "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"interface\" : \"eth0\" , "
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"38:60:77:d5:ff:ee\","
+ " \"ip-addresses\": [ \"2001:db8:1::1\" ]"
+ " },"
+ " {"
+ " \"duid\": \"01:02:03:05\","
+ " \"ip-addresses\": [ \"2001:db8:1::2\" ]"
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 3:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"duid\" ],"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"nisp-servers\","
+ " \"data\": \"3000:3::123\""
+ "} ],"
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ {"
+ " \"pool\": \"2001:db8:1::/64\","
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:2::111\""
+ " } ]"
+ " } ],"
+ " \"interface\" : \"eth0\","
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:2::123\""
+ " },"
+ " {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"3000:2::123\""
+ " },"
+ " {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::123\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:05\","
+ " \"ip-addresses\": [ \"2001:db8:1::2\" ],"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::234\""
+ " },"
+ " {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"3000:1::234\""
+ " } ]"
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 4:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"duid\" ],"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"interface\" : \"eth0\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:05\","
+ " \"ip-addresses\": [ \"2001:db8:1::2\" ],"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::234\""
+ " },"
+ " {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"3000:1::234\""
+ " } ]"
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 5:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"duid\" ],"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"vendor-opts\","
+ " \"data\": \"4491\""
+ "},"
+ "{"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"3000:3::123\""
+ "} ],"
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"interface\" : \"eth0\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"01:02:03:05\","
+ " \"ip-addresses\": [ \"2001:db8:1::2\" ],"
+ " \"option-data\": [ {"
+ " \"name\": \"vendor-opts\","
+ " \"data\": \"4491\""
+ " },"
+ " {"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"3000:1::234\""
+ " } ]"
+ " } ]"
+ " } ]"
+ "}",
+
+ // Configuration 6:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"duid\" ],"
+ "\"valid-lifetime\": 40, "
+ "\"preferred-lifetime\": 30,"
+ "\"rebind-timer\": 20, "
+ "\"renew-timer\": 10, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::2\" } ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"3000::\","
+ " \"prefix-len\": 119,"
+ " \"delegated-len\": 120"
+ " }"
+ " ],"
+ " \"interface\" : \"eth0\""
+ "} ]"
+ "}",
+
+ // Configuration 7:
+ "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"host-reservation-identifiers\": [ \"duid\" ],"
+ "\"valid-lifetime\": 40, "
+ "\"preferred-lifetime\": 30,"
+ "\"rebind-timer\": 20, "
+ "\"renew-timer\": 10, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::2\" } ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"3000::\","
+ " \"prefix-len\": 119,"
+ " \"delegated-len\": 120"
+ " }"
+ " ],"
+ " \"interface\" : \"eth0\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"04:03:02:01\","
+ " \"ip-addresses\": [ \"2001:db8:1::2\" ],"
+ " \"prefixes\": [ \"3000::100/120\" ]"
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 8: Global HRs TYPE_NAs
+ "{ "
+ "\"interfaces-config\": { \n"
+ " \"interfaces\": [ \"*\" ] \n"
+ "},\n "
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"reservations\": [ \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:04\", \n"
+ " \"hostname\": \"duid-host-fixed-out-of-range\", \n"
+ " \"ip-addresses\": [ \"2001:db8:1::1\" ] \n"
+ "}, \n"
+ "{ \n"
+ " \"duid\": \"02:02:03:04\", \n"
+ " \"hostname\": \"duid-host-fixed-in-range\", \n"
+ " \"ip-addresses\": [ \"2001:db8:1::77\" ] \n"
+ "}, \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"duid-host-dynamic\" \n"
+ "}, \n"
+ "{ \n"
+ " \"hw-address\": \"38:60:77:d5:ff:ee\", \n"
+ " \"hostname\": \"hw-host\" \n"
+ "} \n"
+ "], \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"mac-sources\": [ \"ipv6-link-local\" ], \n"
+ "\"subnet6\": [ \n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+ " \"interface\" : \"eth0\", \n"
+ " \"reservations-global\": true, \n"
+ " \"reservations-in-subnet\": false \n"
+ " },"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db8:2::/48\", \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ], \n"
+ " \"interface\" : \"eth1\", \n"
+ " \"reservations\": [ \n"
+ " { \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"subnet-duid-host\" \n"
+ " }] \n"
+ " }"
+ " ] \n"
+ "} \n"
+ ,
+ // Configuration 9: Global HRs TYPE_PDs
+ "{ "
+ "\"interfaces-config\": { \n"
+ " \"interfaces\": [ \"*\" ] \n"
+ "},\n "
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"reservations\": [ \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:04\", \n"
+ " \"hostname\": \"duid-host-fixed\", \n"
+ " \"prefixes\": [ \"4000::100/120\" ]"
+ "}, \n"
+ "{ \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"duid-host-dynamic\" \n"
+ "} \n"
+ "], \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"preferred-lifetime\": 3000, \n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"mac-sources\": [ \"ipv6-link-local\" ], \n"
+ "\"subnet6\": [ \n"
+ " { \n"
+ " \"id\": 1, \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"interface\" : \"eth0\", \n"
+ " \"reservations-global\": true, \n"
+ " \"reservations-in-subnet\": false, \n"
+ " \"pd-pools\": [ \n"
+ " { \n"
+ " \"prefix\": \"3000::\", \n"
+ " \"prefix-len\": 119, \n"
+ " \"delegated-len\": 120 \n"
+ " }] \n"
+ " },"
+ " { \n"
+ " \"id\": 2, \n"
+ " \"subnet\": \"2001:db8:2::/48\", \n"
+ " \"interface\" : \"eth1\", \n"
+ " \"pd-pools\": [ \n"
+ " { \n"
+ " \"prefix\": \"3001::\", \n"
+ " \"prefix-len\": 119, \n"
+ " \"delegated-len\": 120 \n"
+ " }], \n"
+ " \"reservations\": [ \n"
+ " { \n"
+ " \"duid\": \"01:02:03:05\", \n"
+ " \"hostname\": \"subnet-duid-host\" \n"
+ " }] \n"
+ " }"
+ " ] \n"
+ "} \n",
+
+ // Configuration 10: client-class reservation in global, shared network
+ // and client-class guarded pools.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"reservations-global\": true,\n"
+ "\"reservations-in-subnet\": false,\n"
+ "\"valid-lifetime\": 4000,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::11\","
+ " \"client-class\": \"reserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:2::/64\", \n"
+ " \"id\": 11,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::10-2001:db8:2::11\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 11: client-class reservation in global, shared network
+ // and client-class guarded subnets.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ], \n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"reservations-global\": true,\n"
+ "\"reservations-in-subnet\": false,\n"
+ "\"valid-lifetime\": 4000,\n"
+ "\"reservations\": [ \n"
+ "{\n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ "}\n"
+ "],\n"
+ "\"shared-networks\": [{"
+ " \"name\": \"frog\",\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"client-class\": \"reserved_class\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::11\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " },\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:2::/64\", \n"
+ " \"client-class\": \"unreserved_class\","
+ " \"id\": 11,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::10-2001:db8:2::11\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ " ]\n"
+ "}]\n"
+ "}",
+
+ // Configuration 12 client-class reservation and client-class guarded pools.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"reserved_class\""
+ "},"
+ "{"
+ " \"name\": \"unreserved_class\","
+ " \"test\": \"not member('reserved_class')\""
+ "}"
+ "],\n"
+ "\"valid-lifetime\": 4000,\n"
+ "\"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"id\": 10,"
+ " \"reservations\": [{ \n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"client-classes\": [ \"reserved_class\" ]\n"
+ " }],\n"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::11\","
+ " \"client-class\": \"reserved_class\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::20-2001:db8:1::21\","
+ " \"client-class\": \"unreserved_class\""
+ " }"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
+ "}",
+
+ // Configuration 13 multiple reservations for the same IP address.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"valid-lifetime\": 4000,\n"
+ "\"ip-reservations-unique\": false,\n"
+ "\"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\",\n"
+ " \"id\": 10,"
+ " \"reservations\": [\n"
+ " {\n"
+ " \"duid\": \"01:02:03:04\",\n"
+ " \"ip-addresses\": [ \"2001:db8:1::15\" ]\n"
+ " },\n"
+ " {\n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"ip-addresses\": [ \"2001:db8:1::15\" ]\n"
+ " }\n"
+ " ],\n"
+ " \"pools\": ["
+ " {\n"
+ " \"pool\": \"2001:db8:1::10-2001:db8:1::200\""
+ " }\n"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
+ "}",
+
+ // Configuration 14 multiple reservations for the same delegated prefix.
+ "{ \"interfaces-config\": {\n"
+ " \"interfaces\": [ \"*\" ]\n"
+ "},\n"
+ "\"valid-lifetime\": 4000,\n"
+ "\"ip-reservations-unique\": false,\n"
+ "\"subnet6\": [\n"
+ " {\n"
+ " \"subnet\": \"2001:db8:1::/64\",\n"
+ " \"id\": 10,"
+ " \"reservations\": [\n"
+ " {\n"
+ " \"duid\": \"01:02:03:04\",\n"
+ " \"prefixes\": [ \"3000::5a:0/112\" ]\n"
+ " },\n"
+ " {\n"
+ " \"duid\": \"01:02:03:05\",\n"
+ " \"prefixes\": [ \"3000::5a:0/112\" ]\n"
+ " }\n"
+ " ],\n"
+ " \"pd-pools\": ["
+ " {\n"
+ " \"prefix\": \"3000::\",\n"
+ " \"prefix-len\": 64,\n"
+ " \"delegated-len\": 112\n"
+ " }\n"
+ " ],\n"
+ " \"interface\": \"eth0\"\n"
+ " }\n"
+ "]\n"
+ "}"
+};
+
+/// @brief Base class representing leases and hints conveyed within IAs.
+///
+/// This is a base class for @ref Reservation and @ref Hint classes.
+class IAResource {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates a resource instance from a string. The string is provided in
+ /// one of the following formats:
+ /// - "2001:db8:1::1" for addresses.
+ /// - "2001:db8::/64" for prefixes.
+ /// - "::/0" to mark lease or hint as unspecified (empty).
+ IAResource(const std::string& resource);
+
+ /// @brief Checks if resource is unspecified.
+ ///
+ /// @return true if resource is unspecified.
+ bool isEmpty() const;
+
+ /// @brief Checks if resource is a prefix.
+ ///
+ /// @return true if resource is a prefix.
+ bool isPrefix() const;
+
+ /// @brief Returns prefix or address (depending on resource type).
+ const IOAddress& getPrefix() const;
+
+ /// @brief Returns prefix length.
+ uint8_t getPrefixLen() const;
+
+ /// @brief Returns textual representation of the resource.
+ std::string toText() const;
+
+ /// @brief Operator converting resource to string.
+ operator std::string() const;
+
+private:
+
+ /// @brief Holds prefix or address (depending on resource type).
+ IOAddress prefix_;
+
+ /// @brief Holds prefix length (for prefixes).
+ uint8_t prefix_len_;
+
+};
+
+IAResource::IAResource(const std::string& resource)
+ : prefix_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_len_(0) {
+ // Check if resource is a prefix, i.e. search for slash.
+ size_t slash_pos = resource.find("/");
+ if ((slash_pos != std::string::npos) && (slash_pos < resource.size() - 1)) {
+ prefix_len_ = boost::lexical_cast<unsigned int>(resource.substr(slash_pos + 1));
+ }
+ prefix_ = IOAddress(resource.substr(0, slash_pos));
+}
+
+bool
+IAResource::isEmpty() const {
+ return (prefix_.isV6Zero() && (prefix_len_ == 0));
+}
+
+bool
+IAResource::isPrefix() const {
+ return (!isEmpty() && (prefix_len_ > 0));
+}
+
+const IOAddress&
+IAResource::getPrefix() const {
+ return (prefix_);
+}
+
+uint8_t
+IAResource::getPrefixLen() const {
+ return (prefix_len_);
+}
+
+std::string
+IAResource::toText() const {
+ std::ostringstream s;
+ s << "\"" << prefix_;
+ if (prefix_len_ > 0) {
+ s << "/" << static_cast<int>(prefix_len_);
+ }
+ s << "\"";
+ return (s.str());
+}
+
+IAResource::operator std::string() const {
+ return (toText());
+}
+
+/// @brief Address or prefix reservation.
+class Reservation : public IAResource {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param resource Resource string as for @ref IAResource constructor.
+ Reservation(const std::string& resource)
+ : IAResource(resource) {
+ }
+
+ /// @brief Convenience function returning unspecified resource.
+ static const Reservation& UNSPEC();
+
+};
+
+const Reservation& Reservation::UNSPEC() {
+ static Reservation unspec("::/0");
+ return (unspec);
+}
+
+/// @brief Address or prefix hint.
+class Hint : public IAResource {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Includes IAID of an IA in which hint should be placed.
+ ///
+ /// @param iaid IAID of IA in which hint should be placed.
+ /// @param resource Resource string as for @ref IAResource constructor.
+ Hint(const IAID& iaid, const std::string& resource)
+ : IAResource(resource), iaid_(iaid) {
+ }
+
+ /// @brief Returns IAID.
+ const IAID& getIAID() const;
+
+ /// @brief Convenience function returning unspecified hint.
+ static const Hint& UNSPEC();
+
+private:
+
+ /// @brief Holds IAID as 32-bit unsigned integer.
+ IAID iaid_;
+};
+
+const IAID&
+Hint::getIAID() const {
+ return (iaid_);
+}
+
+const Hint& Hint::UNSPEC() {
+ static Hint unspec(IAID(0), "::/0");
+ return (unspec);
+}
+
+/// @brief Test fixture class for testing host reservations
+class HostTest : public Dhcpv6SrvTest {
+public:
+
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ HostTest()
+ : Dhcpv6SrvTest(),
+ iface_mgr_test_config_(true),
+ client_(),
+ do_solicit_(std::bind(&Dhcp6Client::doSolicit, &client_, true)),
+ do_solicit_request_(std::bind(&Dhcp6Client::doSARR, &client_)) {
+ }
+
+ /// @brief Checks that specified option contains a desired address.
+ ///
+ /// The option must cast to the @ref Option6AddrLst type. The function
+ /// expects that this option contains at least one address and checks
+ /// first address for equality with @ref expected_addr.
+ ///
+ /// @param option_type Option type.
+ /// @param expected_addr Desired address.
+ /// @param config Configuration obtained from the server.
+ void verifyAddressOption(const uint16_t option_type,
+ const std::string& expected_addr,
+ const Dhcp6Client::Configuration& config) const {
+ Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(config.findOption(option_type));
+ ASSERT_TRUE(opt) << "option " << option_type << " not found or it "
+ "is of incorrect type";
+ Option6AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_GE(addrs.size(), 1) << "test failed for option type " << option_type;
+ EXPECT_EQ(expected_addr, addrs[0].toText())
+ << "test failed for option type " << option_type;
+ }
+
+ /// @brief Verifies that the reservation is retrieved by the server
+ /// using one of the host identifiers.
+ ///
+ /// @param client Reference to a client to be used in the test.
+ /// The client should be preconfigured to insert a specific identifier
+ /// into the message, e.g. DUID, HW address etc.
+ /// @param config_index Index of the configuration to use in the CONFIGS
+ /// table.
+ /// @param exp_ip_address Expected IPv6 address in the returned
+ /// reservation.
+ void testReservationByIdentifier(Dhcp6Client& client,
+ const unsigned int config_index,
+ const std::string& exp_ip_address) {
+ configure(CONFIGS[config_index], *client.getServer());
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Configure client to request IA_NA and append IA_NA option
+ // to the client's message.
+ client.requestAddress(1234, IOAddress("2001:db8:1:1::dead:beef"));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Verify that the client we got the reserved address
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ(exp_ip_address, lease_client.addr_.toText());
+ }
+
+ /// @brief Initiate exchange with DHCPv6 server.
+ ///
+ /// This method initiates DHCPv6 message exchange between a specified
+ /// client and the server. The msg_type is used to indicate what kind
+ /// of exchange should be initiated. If the message type is a Renew
+ /// or Rebind, the 4-way handshake is made first. If the message type
+ /// is a Request, the Solicit-Advertise is done prior to this.
+ ///
+ /// @param msg_type Message type to be sent to the server.
+ /// @param client Reference to a client to be used to initiate the
+ /// exchange with the server.
+ void doExchange(const uint16_t msg_type, Dhcp6Client& client);
+
+ /// @brief Verifies that host specific options override subnet specific
+ /// options.
+ ///
+ /// Overridden options are requested with Option Request option.
+ ///
+ /// @param msg_type DHCPv6 message type to be sent to the server. If the
+ /// message type is Renew or Rebind, the 4-way exchange is made prior to
+ /// sending a Renew or Rebind. For a Request case, the Solicit-Advertise
+ /// is also performed.
+ void testOverrideRequestedOptions(const uint16_t msg_type);
+
+ /// @brief Verifies that client receives options when they are solely
+ /// defined in the host scope (and not in the global, subnet or pool
+ /// scope).
+ ///
+ /// @param msg_type DHCPv6 message type to be sent to the server. If the
+ /// message type is Renew or Rebind, the 4-way exchange is made prior to
+ /// sending a Renew or Rebind. For a Request case, the Solicit-Advertise
+ /// is also performed.
+ void testHostOnlyOptions(const uint16_t msg_type);
+
+ /// @brief Verifies that host specific vendor options override vendor
+ /// options defined in the global scope.
+ ///
+ /// @param msg_type DHCPv6 message type to be sent to the server. If the
+ /// message type is Renew or Rebind, the 4-way exchange is made prior to
+ /// sending a Renew or Rebind. For a Request case, the Solicit-Advertise
+ /// is also performed.
+ void testOverrideVendorOptions(const uint16_t msg_type);
+
+ /// @brief Checks if the client obtained lease for specified reservation.
+ ///
+ /// @param r Reservation.
+ /// @param [out] address_count This value is incremented if the client
+ /// obtained the address lease.
+ /// @param [out] prefix_count This value is incremented if the client
+ /// obtained the prefix lease.
+ void testLeaseForIA(const Reservation& r, size_t& address_count,
+ size_t& prefix_count);
+
+ /// @brief Checks if the client obtained lease for specified hint.
+ ///
+ /// The hint belongs to a specific IA (identified by IAID) and is expected
+ /// to be returned in this IA by the server.
+ ///
+ /// @param h Hint.
+ void testLeaseForIA(const Hint& h);
+
+ /// @brief A generic test for assigning multiple reservations to a single
+ /// client sending multiple IAs.
+ ///
+ /// This test creates a server configuration which includes one subnet,
+ /// address pool of "2001:db8:1::1 - 2001:db8:1::10" and the prefix pool
+ /// of 3001::/32, with delegated prefix length of 64. The configuration
+ /// may include between 0 and 6 reservations for a client with DUID of
+ /// "01:02:03:04".
+ ///
+ /// The test performs an exchange with a server, typically 4-way exchange
+ /// or Solicit-Advertise. The client's message includes 3 IA_NAs (with
+ /// IAIDs in range of 1..3) and 3 IA_PDs (with IAIDs in range of 4..6).
+ ///
+ /// It is possible to specify hints for selected IAs. The IA is in such
+ /// case identified by the IAID.
+ ///
+ /// The test expects that the server returns 6 leases. It checks if those
+ /// leases contain all reserved addresses and prefixes specified as
+ /// arguments of the test. If the number of IAs is greater than the
+ /// number of reservations it checks that for the remaining IAs the
+ /// leases from dynamic pools are assigned.
+ ///
+ /// The strict_iaid_check flag controls whether the test should verify
+ /// that the address or prefix specified as a hint is assigned by the
+ /// server to the IA in which the hint was placed by the client.
+ ///
+ /// @param client_operation Dhcp6Client function to be executed to
+ /// perform an exchange with the server.
+ /// @param r1 Reservation 1. Default value is "unspecified", in which
+ /// case the reservation will not be created.
+ /// @param r2 Reservation 2.
+ /// @param r3 Reservation 3.
+ /// @param r4 Reservation 4.
+ /// @param r5 Reservation 5.
+ /// @param r6 Reservation 6.
+ /// @param strict_iaid_check Indicates if the test should check if the
+ /// hints sent by the client have been allocated by the server to the
+ /// particular IAs. Default value is NO (no checks).
+ /// @param h1 Hint 1. Default value is "unspecified", in which case the
+ /// hint will not be included.
+ /// @param h2 Hint 2.
+ /// @param h3 Hint 3.
+ /// @param h4 Hint 4.
+ /// @param h5 Hint 5.
+ /// @param h6 Hint 6.
+ void testMultipleIAs(const std::function<void ()>& client_operation,
+ const Reservation& r1 = Reservation::UNSPEC(),
+ const Reservation& r2 = Reservation::UNSPEC(),
+ const Reservation& r3 = Reservation::UNSPEC(),
+ const Reservation& r4 = Reservation::UNSPEC(),
+ const Reservation& r5 = Reservation::UNSPEC(),
+ const Reservation& r6 = Reservation::UNSPEC(),
+ const StrictIAIDChecking& strict_iaid_check =
+ StrictIAIDChecking::NO(),
+ const Hint& h1 = Hint::UNSPEC(),
+ const Hint& h2 = Hint::UNSPEC(),
+ const Hint& h3 = Hint::UNSPEC(),
+ const Hint& h4 = Hint::UNSPEC(),
+ const Hint& h5 = Hint::UNSPEC(),
+ const Hint& h6 = Hint::UNSPEC());
+
+ /// @brief Checks if specified reservation is for address or prefix and
+ /// stores reservation in the textual format on one of the lists.
+ ///
+ /// @param [out] address_list Reference to a list containing address
+ /// reservations.
+ /// @param [out] prefix_list Reference to a list containing prefix
+ /// reservations.
+ static void storeReservation(const Reservation& r,
+ std::list<std::string>& address_list,
+ std::list<std::string>& prefix_list);
+
+ /// @brief Creates configuration for testing processing multiple IAs.
+ ///
+ /// This method creates a server configuration which includes one subnet,
+ /// address pool of "2001:db8:1::1 - 2001:db8:1::10" and the prefix pool
+ /// of 3001::/32, with delegated prefix length of 64. The configuration
+ /// may include between 0 and 6 reservations for a client with DUID of
+ /// "01:02:03:04".
+ ///
+ /// @param r1 Reservation 1. Default value is "unspecified" in which case
+ /// the reservation will not be included in the configuration.
+ /// @param r2 Reservation 2.
+ /// @param r3 Reservation 3.
+ /// @param r4 Reservation 4.
+ /// @param r5 Reservation 5.
+ /// @param r6 Reservation 6.
+ ///
+ /// @return Text containing server configuration in JSON format.
+ std::string configString(const DUID& duid,
+ const Reservation& r1 = Reservation::UNSPEC(),
+ const Reservation& r2 = Reservation::UNSPEC(),
+ const Reservation& r3 = Reservation::UNSPEC(),
+ const Reservation& r4 = Reservation::UNSPEC(),
+ const Reservation& r5 = Reservation::UNSPEC(),
+ const Reservation& r6 = Reservation::UNSPEC()) const;
+
+ /// @brief Verifies that an SARR exchange results in the expected lease
+ ///
+ /// @param client Client configured to request a single lease
+ /// @param exp_address expected address/prefix of the lease
+ /// @param exp_hostname expected hostname on the lease
+ void sarrTest(Dhcp6Client& client, const std::string& exp_address,
+ const std::string& exp_hostname);
+
+ /// @brief Configures client to include hint.
+ ///
+ /// @param client Reference to a client.
+ /// @param hint Const reference to an object holding the hint.
+ static void requestIA(Dhcp6Client& client, const Hint& hint);
+
+ /// @brief Test pool or subnet selection using global class reservation.
+ ///
+ /// Verifies that client class specified in the global reservation
+ /// may be used to influence pool or subnet selection.
+ ///
+ /// @param config_idx Index of the server configuration from the
+ /// @c CONFIGS array.
+ /// @param first_address Address to be allocated from the pool having
+ /// a reservation.
+ /// @param second_address Address to be allocated from the pool not
+ /// having a reservation.
+ void testGlobalClassSubnetPoolSelection(const int config_idx,
+ const std::string& first_address = "2001:db8:1::10",
+ const std::string& second_address = "2001:db8:2::10");
+
+ /// @brief Test that two clients having reservations for the same IP
+ /// address are offered the reserved lease.
+ ///
+ /// This test verifies the case when two clients have reservations for
+ /// the same IP address. The first client sends Solicit and is offered
+ /// the reserved address. At the same time, the second client having
+ /// the reservation for the same IP address performs 4-way exchange
+ /// using the reserved address as a hint in Solicit.
+ /// The client gets the lease for this address. This test verifies
+ /// that the allocation engine correctly identifies that the second
+ /// client has a reservation for this address.
+ ///
+ /// @param duid1 Hardware address of the first client having the
+ /// reservation.
+ /// @param duid2 Hardware address of the second client having the
+ /// reservation.
+ void testMultipleClientsRace(const std::string& duid1,
+ const std::string& duid2);
+
+ /// @brief Configures client to include 6 IAs without hints.
+ ///
+ /// This method configures the client to include 3 IA_NAs and
+ /// 3 IA_PDs.
+ ///
+ /// @param client Reference to a client.
+ static void requestEmptyIAs(Dhcp6Client& client);
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+ /// @brief Instance of the common DHCPv6 client.
+ Dhcp6Client client_;
+
+ /// @brief Pointer to the Dhcp6Client::doSolicit method.
+ std::function<void() > do_solicit_;
+
+ /// @brief Pointer to the Dhcp6Client::doSARR method.
+ std::function<void() > do_solicit_request_;
+};
+
+void
+HostTest::doExchange(const uint16_t msg_type, Dhcp6Client& client) {
+ switch (msg_type) {
+ case DHCPV6_INFORMATION_REQUEST:
+ ASSERT_NO_THROW(client.doInfRequest());
+ break;
+ case DHCPV6_REQUEST:
+ ASSERT_NO_THROW(client.doSARR());
+ break;
+ case DHCPV6_SOLICIT:
+ ASSERT_NO_THROW(client.doSolicit());
+ break;
+ case DHCPV6_RENEW:
+ ASSERT_NO_THROW(client.doSARR());
+ ASSERT_NO_THROW(client.doRenew());
+ break;
+ case DHCPV6_REBIND:
+ ASSERT_NO_THROW(client.doSARR());
+ ASSERT_NO_THROW(client.doRebind());
+ break;
+ default:
+ ;
+ }
+
+ // Make sure that the server has responded with a Reply.
+ ASSERT_TRUE(client.getContext().response_);
+ ASSERT_EQ(DHCPV6_REPLY, client.getContext().response_->getType());
+
+}
+
+
+void
+HostTest::testOverrideRequestedOptions(const uint16_t msg_type) {
+ Dhcp6Client client;
+ // Reservation has been made for a client with this DUID.
+ client.setDUID("01:02:03:05");
+
+ // Request all options specified in the configuration.
+ client.requestOption(D6O_NAME_SERVERS);
+ client.requestOption(D6O_NIS_SERVERS);
+ client.requestOption(D6O_NISP_SERVERS);
+ client.requestOption(D6O_SNTP_SERVERS);
+
+ configure(CONFIGS[3], *client.getServer());
+
+ ASSERT_NO_FATAL_FAILURE(doExchange(msg_type, client));
+
+ {
+ SCOPED_TRACE("host specific dns-servers");
+ // Host specific DNS server should be used.
+ verifyAddressOption(D6O_NAME_SERVERS, "3000:1::234", client.config_);
+ }
+
+ {
+ SCOPED_TRACE("host specific nis-servers");
+ // Host specific NIS server should be used.
+ verifyAddressOption(D6O_NIS_SERVERS, "3000:1::234", client.config_);
+ }
+
+ {
+ SCOPED_TRACE("subnet specific sntp-servers");
+ // Subnet specific SNTP server should be used as it is not specified
+ // in a host scope.
+ verifyAddressOption(D6O_SNTP_SERVERS, "3000:2::123", client.config_);
+ }
+
+ {
+ SCOPED_TRACE("global nisp-servers");
+ // Globally specified NISP server should be used as it is not
+ // specified in a host scope.
+ verifyAddressOption(D6O_NISP_SERVERS, "3000:3::123", client.config_);
+ }
+}
+
+void
+HostTest::testLeaseForIA(const Reservation& r, size_t& address_count,
+ size_t& prefix_count) {
+ if (r.isPrefix()) {
+ ++prefix_count;
+ EXPECT_TRUE(client_.hasLeaseForPrefix(r.getPrefix(),
+ r.getPrefixLen(),
+ IAID(3 + prefix_count)));
+
+ } else if (!r.isEmpty()) {
+ ++address_count;
+ EXPECT_TRUE(client_.hasLeaseForAddress(r.getPrefix(),
+ IAID(address_count)));
+ }
+}
+
+void
+HostTest::testLeaseForIA(const Hint& h) {
+ if (h.isPrefix()) {
+ EXPECT_TRUE(client_.hasLeaseForPrefix(h.getPrefix(), h.getPrefixLen(),
+ h.getIAID()))
+ << "there is no lease for prefix " << h.toText()
+ << " and IAID = " << h.getIAID();
+
+ } else if (!h.isEmpty()) {
+ EXPECT_TRUE(client_.hasLeaseForAddress(h.getPrefix(), h.getIAID()))
+ << "there is no lease for address " << h.toText()
+ << " and IAID = " << h.getIAID();
+ }
+}
+
+void
+HostTest::testMultipleIAs(const std::function<void ()>& client_operation,
+ const Reservation& r1, const Reservation& r2,
+ const Reservation& r3, const Reservation& r4,
+ const Reservation& r5, const Reservation& r6,
+ const StrictIAIDChecking& strict_iaid_check,
+ const Hint& h1, const Hint& h2 ,
+ const Hint& h3, const Hint& h4,
+ const Hint& h5, const Hint& h6) {
+ client_.setDUID("01:02:03:04");
+
+ /// Create configuration with 0 to 6 reservations.
+ const std::string c = configString(*client_.getDuid(), r1, r2, r3,
+ r4, r5, r6);
+
+ ASSERT_NO_THROW(configure(c, *client_.getServer()));
+
+ // First includes all IAs. They are initially empty.
+ requestEmptyIAs(client_);
+
+ // For each specified hint, include it in the respective IA. Hints
+ // which are "unspecified" will not be included.
+ requestIA(client_, h1);
+ requestIA(client_, h2);
+ requestIA(client_, h3);
+ requestIA(client_, h4);
+ requestIA(client_, h5);
+ requestIA(client_, h6);
+
+
+ // Send Solicit and require that the client saves received configuration
+ // so as we can test that advertised configuration is correct.
+ ASSERT_NO_THROW(client_operation());
+
+ ASSERT_EQ(6, client_.getLeaseNum());
+
+ // Count reserved addresses and prefixes assigned from reservations.
+ size_t address_count = 0;
+ size_t prefix_count = 0;
+
+ testLeaseForIA(r1, address_count, prefix_count);
+ testLeaseForIA(r2, address_count, prefix_count);
+ testLeaseForIA(r3, address_count, prefix_count);
+ testLeaseForIA(r4, address_count, prefix_count);
+ testLeaseForIA(r5, address_count, prefix_count);
+ testLeaseForIA(r6, address_count, prefix_count);
+
+ // Get all addresses assigned from the dynamic pool (not reserved).
+ std::vector<Lease6> leases =
+ client_.getLeasesByAddressRange(IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::10"));
+ // There are 3 IA_NAs and for a few we have assigned reserved addresses.
+ // The remaining ones should be assigned from the dynamic pool.
+ ASSERT_EQ(3 - address_count, leases.size());
+
+ // Get all prefixes assigned from the dynamic pool (not reserved).
+ leases = client_.getLeasesByPrefixPool(IOAddress("3001::"), 32, 64);
+ ASSERT_EQ(3 - prefix_count, leases.size());
+
+ // Check that the hints have been allocated to respective IAs.
+ if (strict_iaid_check) {
+ testLeaseForIA(h1);
+ testLeaseForIA(h2);
+ testLeaseForIA(h3);
+ testLeaseForIA(h4);
+ testLeaseForIA(h5);
+ testLeaseForIA(h6);
+ }
+}
+
+
+void
+HostTest::storeReservation(const Reservation& r,
+ std::list<std::string>& address_list,
+ std::list<std::string>& prefix_list) {
+ if (!r.isEmpty()) {
+ if (r.isPrefix()) {
+ prefix_list.push_back(r);
+ } else {
+ address_list.push_back(r);
+ }
+ }
+}
+
+std::string
+HostTest::configString(const DUID& duid,
+ const Reservation& r1, const Reservation& r2,
+ const Reservation& r3, const Reservation& r4,
+ const Reservation& r5, const Reservation& r6) const {
+ std::list<std::string> address_list;
+ std::list<std::string> prefix_list;
+ storeReservation(r1, address_list, prefix_list);
+ storeReservation(r2, address_list, prefix_list);
+ storeReservation(r3, address_list, prefix_list);
+ storeReservation(r4, address_list, prefix_list);
+ storeReservation(r5, address_list, prefix_list);
+ storeReservation(r6, address_list, prefix_list);
+
+ std::ostringstream s;
+ s << "{ "
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 4000, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ "
+ " { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+ " \"pd-pools\": [ { \"prefix\": \"3001::\", \"prefix-len\": 32,"
+ " \"delegated-len\": 64 } ],"
+ " \"interface\" : \"eth0\"";
+
+ // Create reservations.
+ if (!address_list.empty() || !prefix_list.empty()) {
+ s << ","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": ";
+ s << "\"" << duid.toText() << "\",";
+
+ if (!address_list.empty()) {
+ s << " \"ip-addresses\": [ "
+ << boost::algorithm::join(address_list, ", ")
+ << "]";
+ }
+
+ if (!prefix_list.empty()) {
+ if (!address_list.empty()) {
+ s << ", ";
+ }
+ s << " \"prefixes\": [ "
+ << boost::algorithm::join(prefix_list, ", ")
+ << "]";
+ }
+
+ s << " } ]";
+ }
+
+ s << " } ]"
+ "}";
+
+ return (s.str());
+}
+
+void
+HostTest::requestIA(Dhcp6Client& client, const Hint& hint) {
+ if ((hint.getIAID() != 0) && !hint.isEmpty()) {
+ if (hint.isPrefix()) {
+ client.requestPrefix(hint.getIAID(), hint.getPrefixLen(),
+ hint.getPrefix());
+ } else {
+ client.requestAddress(hint.getIAID(), hint.getPrefix());
+ }
+ }
+}
+
+void
+HostTest::testHostOnlyOptions(const uint16_t msg_type) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:05");
+ client.requestOption(D6O_NAME_SERVERS);
+ client.requestOption(D6O_NIS_SERVERS);
+
+ configure(CONFIGS[3], *client.getServer());
+
+ ASSERT_NO_FATAL_FAILURE(doExchange(msg_type, client));
+
+ {
+ SCOPED_TRACE("host specific dns-servers");
+ // DNS servers are specified only in a host scope.
+ verifyAddressOption(D6O_NAME_SERVERS, "3000:1::234", client.config_);
+ }
+
+ {
+ SCOPED_TRACE("host specific nis-servers");
+ // NIS servers are specified only in a host scope.
+ verifyAddressOption(D6O_NIS_SERVERS, "3000:1::234", client.config_);
+ }
+}
+
+void
+HostTest::testOverrideVendorOptions(const uint16_t msg_type) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:05");
+
+ // Client needs to include Vendor Specific Information option
+ // with ORO suboption, which the server will use to determine
+ // which suboptions should be returned to the client.
+ OptionVendorPtr opt_vendor(new OptionVendor(Option::V6, VENDOR_ID_CABLE_LABS));
+ // Include ORO with TFTP servers suboption code being requested.
+ opt_vendor->addOption(OptionPtr(new OptionUint16(Option::V6, DOCSIS3_V6_ORO,
+ DOCSIS3_V6_TFTP_SERVERS)));
+ client.addExtraOption(opt_vendor);
+
+ configure(CONFIGS[5], *client.getServer());
+
+ ASSERT_NO_FATAL_FAILURE(doExchange(msg_type, client));
+
+ // Vendor Specific Information option should be returned by the server.
+ OptionVendorPtr vendor_opt = boost::dynamic_pointer_cast<
+ OptionVendor>(client.config_.findOption(D6O_VENDOR_OPTS));
+ ASSERT_TRUE(vendor_opt);
+
+ // TFTP server suboption should be returned because it was requested
+ // with Option Request suboption.
+ Option6AddrLstPtr tftp = boost::dynamic_pointer_cast<
+ Option6AddrLst>(vendor_opt->getOption(DOCSIS3_V6_TFTP_SERVERS));
+ ASSERT_TRUE(tftp);
+
+ // Address specified in the host scope should be used.
+ Option6AddrLst::AddressContainer addrs = tftp->getAddresses();
+ ASSERT_EQ(addrs.size(), 1);
+ EXPECT_EQ("3000:1::234", addrs[0].toText());
+}
+
+void
+HostTest::testGlobalClassSubnetPoolSelection(const int config_idx,
+ const std::string& first_address,
+ const std::string& second_address) {
+ Dhcp6Client client_resrv;
+
+ // Use DUID for which we have host reservation including client class.
+ client_resrv.setDUID("01:02:03:05");
+
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[config_idx], *client_resrv.getServer()));
+
+ // This client should be given an address from the 2001:db8:1::/64 subnet.
+ // Let's use the 2001:db8:2::10 as a hint to make sure that the server
+ // refuses allocating it and uses the sole pool available for this
+ // client.
+ client_resrv.requestAddress(1, IOAddress(second_address));
+ ASSERT_NO_THROW(client_resrv.doSARR());
+ ASSERT_EQ(1, client_resrv.getLeaseNum());
+ Lease6 lease_client = client_resrv.getLease(0);
+ EXPECT_EQ(first_address, lease_client.addr_.toText());
+
+ // This client has no reservation and therefore should be
+ // assigned to the unreserved_class and be given an address
+ // from the other pool.
+ Dhcp6Client client_no_resrv(client_resrv.getServer());
+ client_no_resrv.setDUID("01:02:03:04");
+
+ // Let's use the address of 2001:db8:1::10 as a hint to make sure that the
+ // server refuses it in favor of the 2001:db8:2::10.
+ client_no_resrv.requestAddress(1, IOAddress(first_address));
+ ASSERT_NO_THROW(client_no_resrv.doSARR());
+ ASSERT_EQ(1, client_no_resrv.getLeaseNum());
+ lease_client = client_no_resrv.getLease(0);
+ EXPECT_EQ(second_address, lease_client.addr_.toText());
+}
+
+void
+HostTest::testMultipleClientsRace(const std::string& duid1,
+ const std::string& duid2) {
+ Dhcp6Client client1;
+ client1.setDUID(duid1);
+ ASSERT_NO_THROW(configure(CONFIGS[13], *client1.getServer()));
+ // client1 performs 4-way exchange to get the reserved lease.
+ requestIA(client1, Hint(IAID(1), "2001:db8:1::15"));
+ ASSERT_NO_THROW(client1.doSARR());
+
+ // Make sure the client has obtained reserved lease.
+ ASSERT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+ // Create another client that has a reservation for the same
+ // IP address.
+ Dhcp6Client client2(client1.getServer());
+ client2.setDUID(duid2);
+ requestIA(client2, Hint(IAID(1), "2001:db8:1::15"));
+
+ // client2 performs 4-way exchange.
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // Make sure the client didn't get the reserved lease. This lease has been
+ // already taken by the client1.
+ EXPECT_FALSE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+ // Make sure the client2 got a lease from the configured pool.
+ EXPECT_TRUE(client2.hasLeaseForAddressRange(IOAddress("2001:db8:1::10"),
+ IOAddress("2001:db8:1::200")));
+}
+
+void
+HostTest::requestEmptyIAs(Dhcp6Client& client) {
+ // Create IAs with IAIDs between 1 and 6.
+ client.requestAddress(1);
+ client.requestAddress(2);
+ client.requestAddress(3);
+ client.requestPrefix(4);
+ client.requestPrefix(5);
+ client.requestPrefix(6);
+}
+
+void
+HostTest::sarrTest(Dhcp6Client& client, const std::string& exp_address,
+ const std::string& exp_hostname) {
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Verify that the client got a dynamic address
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ(exp_address, lease_client.addr_.toText());
+
+ // Check that the server recorded the lease
+ // and that the server lease has expected hostname.
+ Lease6Ptr lease_server = checkLease(lease_client);
+ ASSERT_TRUE(lease_server);
+ EXPECT_EQ(exp_hostname, lease_server->hostname_);
+}
+
+
+// Test basic SARR scenarios against a server configured with one subnet
+// containing two reservations. One reservation with a hostname, one
+// without a hostname. Scenarios:
+//
+// - Verify that a client when matched to a host reservation with a hostname
+// gets that reservation and the lease hostname matches the reserved hostname
+//
+// - Verify that a client when matched to a host reservation without a hostname
+// gets that reservation and the lease hostname is blank
+//
+// - Verify that a client that does not match a host reservation gets a dynamic
+// lease and the hostname for the lease is blank.
+//
+TEST_F(HostTest, basicSarrs) {
+ Dhcp6Client client;
+ configure(CONFIGS[0], *client.getServer());
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Configure client to request IA_NA and append IA_NA option
+ // to the client's message.
+ client.setDUID("01:02:03:04");
+ client.requestAddress(1234, IOAddress("2001:db8:1:1::dead:beef"));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Verify that the client we got the reserved address
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::babe", lease_client.addr_.toText());
+
+ // Check that the server recorded the lease.
+ // and lease has reserved hostname
+ Lease6Ptr lease_server = checkLease(lease_client);
+ ASSERT_TRUE(lease_server);
+ EXPECT_EQ("alice", lease_server->hostname_);
+
+ // Now redo the client, adding one to the DUID
+ client.clearConfig();
+ client.modifyDUID();
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Verify that the client we got the reserved address
+ ASSERT_EQ(1, client.getLeaseNum());
+ lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::babf", lease_client.addr_.toText());
+
+ // Check that the server recorded the lease.
+ // and that the server lease has NO hostname
+ lease_server = checkLease(lease_client);
+ ASSERT_TRUE(lease_server);
+ EXPECT_EQ("", lease_server->hostname_);
+
+ // Now redo the client with yet another DUID and verify that
+ // we get a dynamic address.
+ client.clearConfig();
+ client.modifyDUID();
+ client.clearRequestedIAs();
+ client.requestAddress(1234);
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Verify that the client got a dynamic address
+ ASSERT_EQ(1, client.getLeaseNum());
+ lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::", lease_client.addr_.toText());
+
+ // Check that the server recorded the lease.
+ // and that the server lease has NO hostname
+ lease_server = checkLease(lease_client);
+ ASSERT_TRUE(lease_server);
+ EXPECT_EQ("", lease_server->hostname_);
+}
+
+// Test basic SARR and renew situation with a client that matches a host
+// reservation
+TEST_F(HostTest, sarrAndRenew) {
+ Dhcp6Client client;
+
+ configure(CONFIGS[0], *client.getServer());
+
+ // Configure client to request IA_NA.
+ client.requestAddress();
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Configure client to request IA_NA and aAppend IA_NA option
+ // to the client's message.
+ client.setDUID("01:02:03:04");
+ client.requestAddress(1234, IOAddress("2001:db8:1:1::dead:beef"));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Now play with time
+ client.fastFwdTime(1000);
+
+ // Verify that the client we got the reserved address
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::babe", lease_client.addr_.toText());
+
+ // Do not send the hint while renewing.
+ client.clearRequestedIAs();
+
+ // Send Renew message to the server.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Verify that we got an extended lease back
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::babe", lease_client2.addr_.toText());
+
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+
+ // Make sure, that the client's lease matches the lease held by the
+ // server and that we have the reserved host name.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+ EXPECT_EQ("alice", lease_server2->hostname_);
+}
+
+// Test basic SARR and rebind situation with a client that matches a host
+// reservation.
+TEST_F(HostTest, sarrAndRebind) {
+ Dhcp6Client client;
+
+ configure(CONFIGS[0], *client.getServer());
+
+ // Configure client to request IA_NA.
+ client.requestAddress();
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Configure client to request IA_NA and aAppend IA_NA option
+ // to the client's message.
+ client.setDUID("01:02:03:04");
+ client.requestAddress(1234, IOAddress("2001:db8:1:1::dead:beef"));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Now play with time
+ client.fastFwdTime(1000);
+
+ // Verify that the client we got the reserved address
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::babe", lease_client.addr_.toText());
+
+ // Do not send the hint while renewing.
+ client.clearRequestedIAs();
+
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Verify that we got an extended lease back
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::babe", lease_client2.addr_.toText());
+
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+
+ // Make sure, that the client's lease matches the lease held by the
+ // server and that we have the reserved host name.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+ EXPECT_EQ("alice", lease_server2->hostname_);
+}
+
+// This test verifies that the host reservation by DUID is found by the
+// server.
+TEST_F(HostTest, reservationByDUID) {
+ Dhcp6Client client;
+ // Set DUID matching the one used to create host reservations.
+ client.setDUID("01:02:03:05");
+ // Run the actual test.
+ testReservationByIdentifier(client, 1, "2001:db8:1::2");
+}
+
+// This test verifies that the host reservation by HW address is found
+// by the server.
+TEST_F(HostTest, reservationByHWAddress) {
+ Dhcp6Client client;
+ // Set link local address for the client which the server will
+ // use to decode the HW address as 38:60:77:d5:ff:ee. This
+ // decoded address will be used to search for host reservations.
+ client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee"));
+ // Run the actual test.
+ testReservationByIdentifier(client, 1, "2001:db8:1::1");
+}
+
+// This test verifies that order in which host identifiers are used to
+// retrieve host reservations can be controlled.
+TEST_F(HostTest, hostIdentifiersOrder) {
+ Dhcp6Client client;
+ // Set DUID matching the one used to create host reservations.
+ client.setDUID("01:02:03:05");
+ // Set link local address for the client which the server will
+ // use to decode the HW address as 38:60:77:d5:ff:ee. This
+ // decoded address will be used to search for host reservations.
+ client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee"));
+ testReservationByIdentifier(client, 2, "2001:db8:1::2");
+}
+
+// This test checks that host specific options override subnet specific
+// and pool specific options. Overridden options are requested with Option
+// Request option (Information-request case).
+TEST_F(HostTest, overrideRequestedOptionsInformationRequest) {
+ testOverrideRequestedOptions(DHCPV6_INFORMATION_REQUEST);
+}
+
+// This test checks that host specific options override subnet specific
+// and pool specific options. Overridden options are requested with Option
+// Request option (Request case).
+TEST_F(HostTest, overrideRequestedOptionsRequest) {
+ testOverrideRequestedOptions(DHCPV6_REQUEST);
+}
+
+// This test checks that host specific options override subnet specific
+// and pool specific options. Overridden options are requested with Option
+// Request option (Renew case).
+TEST_F(HostTest, overrideRequestedOptionsRenew) {
+ testOverrideRequestedOptions(DHCPV6_RENEW);
+}
+
+// This test checks that host specific options override subnet specific
+// and pool specific options. Overridden options are requested with Option
+// Request option (Rebind case).
+TEST_F(HostTest, overrideRequestedOptionsRebind) {
+ testOverrideRequestedOptions(DHCPV6_REBIND);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (Information-request case).
+TEST_F(HostTest, testHostOnlyOptionsInformationRequest) {
+ testHostOnlyOptions(DHCPV6_INFORMATION_REQUEST);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (Request case).
+TEST_F(HostTest, testHostOnlyOptionsRequest) {
+ testHostOnlyOptions(DHCPV6_REQUEST);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (Renew case).
+TEST_F(HostTest, testHostOnlyOptionsRenew) {
+ testHostOnlyOptions(DHCPV6_RENEW);
+}
+
+// This test checks that client receives options when they are
+// solely defined in the host scope and not in the global or subnet
+// scope (Rebind case).
+TEST_F(HostTest, testHostOnlyOptionsRebind) {
+ testHostOnlyOptions(DHCPV6_REBIND);
+}
+
+// This test checks that host specific vendor options override vendor
+// options defined in the global scope (Request case).
+TEST_F(HostTest, overrideVendorOptionsRequest) {
+ testOverrideVendorOptions(DHCPV6_REQUEST);
+}
+
+// This test checks that host specific vendor options override vendor
+// options defined in the global scope (Renew case).
+TEST_F(HostTest, overrideVendorOptionsRenew) {
+ testOverrideVendorOptions(DHCPV6_RENEW);
+}
+
+// This test checks that host specific vendor options override vendor
+// options defined in the global scope (Rebind case).
+TEST_F(HostTest, overrideVendorOptionsRebind) {
+ testOverrideVendorOptions(DHCPV6_REBIND);
+}
+
+// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs
+// without hints and the server should return those IAs with 3 reserved
+// addresses and 3 reserved prefixes.
+TEST_F(HostTest, multipleIAsSolicit) {
+ testMultipleIAs(do_solicit_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation("3000:1:3::/64"));
+}
+
+// In this test the client performs 4-way exchange, sending 3 IA_NAs
+// and 3 IA_PDs without hints. The server should return those IAs
+// with 3 reserved addresses and 3 reserved prefixes.
+TEST_F(HostTest, multipleIAsRequest) {
+ testMultipleIAs(do_solicit_request_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation("3000:1:3::/64"));
+}
+
+// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs
+// without hints. The server has 2 reservations for addresses and
+// 2 reservations for prefixes for this client. The server should
+// assign reserved addresses and prefixes to the client, and return
+// them in 2 IA_NAs and 2 IA_PDs. For the remaining IA_NA and IA_PD
+// the server should allocate address and prefix from a dynamic pools.
+TEST_F(HostTest, staticAndDynamicIAs) {
+ testMultipleIAs(do_solicit_,
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:3::/64"));
+}
+
+// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs.
+// The client includes an address hint for IAID = 1, a prefix length
+// hint for the IAID = 5, and the prefix hint for IAID = 6. The hints
+// match the reserved resources and should be allocated for the client.
+TEST_F(HostTest, multipleIAsHintsForReservations) {
+ testMultipleIAs(do_solicit_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation("3000:1:3::/64"),
+ StrictIAIDChecking::NO(),
+ Hint(IAID(1), "2001:db8:1:1::2"),
+ Hint(IAID(5), "::/64"),
+ Hint(IAID(6), "3000:1:1::/64"));
+}
+
+// In this test the client sends Solicit with 3 IA_NAs and 3 IA_PDs.
+// The client includes one address hint for IAID = 1 and one
+// prefix hint for IAID = 6. The hints point to an address and prefix
+// from the dynamic pools, but because the server has reservations
+// for other addresses and prefixes outside the pool, the address
+// and prefix specified as hint should not be allocated. Instead
+// the server should allocate reserved leases.
+TEST_F(HostTest, multipleIAsHintsInPool) {
+ testMultipleIAs(do_solicit_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation("3000:1:3::/64"),
+ StrictIAIDChecking::NO(),
+ Hint(IAID(1), "2001:db8:1::2"),
+ Hint(IAID(6), "3001::/64"));
+}
+
+// In this test, the client sends Solicit with 3 IA_NAs and 3 IA_PDs.
+// The client includes one address hint for which the client has
+// reservation, one prefix hint for which the client has reservation,
+// one hint for an address from the dynamic pool and one hint for a
+// prefix from a dynamic pool. The server has reservations for 2
+// addresses and 2 prefixes. The server should allocate reserved
+// leases and address and prefix from a dynamic pool, which client
+// included as hints.
+TEST_F(HostTest, staticAndDynamicIAsHints) {
+ testMultipleIAs(do_solicit_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation::UNSPEC(),
+ Reservation::UNSPEC(),
+ StrictIAIDChecking::NO(),
+ Hint(IAID(1), "2001:db8:1::2"),
+ Hint(IAID(3), "2001:db8:1:1::1"),
+ Hint(IAID(5), "3001::/64"),
+ Hint(IAID(6), "3000::/64"));
+}
+
+// In this test, the client sends Solicit with 3 IA_NAs and 3 IA_PDs.
+// The server has reservation for two addresses and two prefixes for
+// this client. The client includes address hint in the third IA_NA
+// and in the third IA_PD. The server should offer 2 addresses in the
+// first two IA_NAs and 2 prefixes in the two IA_PDs. The server should
+// respect hints provided within the 3rd IA_NA and 3rd IA_PD. The server
+// wouldn't respect hints if they were provided within 1st or 2nd IA of
+// a given type, because the server always tries to allocate the
+// reserved leases in the first place.
+TEST_F(HostTest, staticAndDynamicIAsHintsStrictIAIDCheck) {
+ testMultipleIAs(do_solicit_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation::UNSPEC(),
+ Reservation::UNSPEC(),
+ StrictIAIDChecking::YES(),
+ Hint(IAID(3), "2001:db8:1::5"),
+ Hint(IAID(6), "3001:0:0:10::/64"));
+}
+
+// In this test, the client performs 4-way exchange and includes 3 IA_NAs
+// and 3 IA_PDs. The client provides no hints. The server has 3 address
+// reservations and 3 prefix reservations for this client and allocates them
+// as a result of 4-way exchange. The client then sends a Renew and the server
+// should renew all leases allocated for the client during the 4-way exchange.
+TEST_F(HostTest, multipleIAsRenew) {
+ // 4-way exchange
+ testMultipleIAs(do_solicit_request_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation("3000:1:3::/64"));
+
+ // Renew
+ ASSERT_NO_THROW(client_.doRenew());
+
+ // Make sure that the client still has the same leases.
+ ASSERT_EQ(6, client_.getLeaseNum());
+
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::1")));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:1::"), 64));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:2::"), 64));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64));
+}
+
+// In this test, the client performs 4-way exchange and includes 3 IA_NAs
+// and IA_PDs. The server has 3 address and 3 prefix reservations for the
+// client and allocates them all. Once the 4-way exchange is complete,
+// the client sends Solicit in which it specifies hints for all IAs. The
+// hints are for the reserved addresses but some of them are included in
+// different IAs than they are assigned to. The server should ignore hints
+// and respond with currently assigned leases.
+TEST_F(HostTest, multipleIAsSolicitAfterAcquisition) {
+ // 4-way exchange
+ testMultipleIAs(do_solicit_request_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation("3000:1:3::/64"));
+
+ client_.clearRequestedIAs();
+
+ // Specify hints.
+
+ // "2001:db8:1:1::1" is allocated for IAID = 1 but we specify it as
+ // a hint for IAID = 3 and so on.
+ requestIA(client_, Hint(IAID(3), "2001:db8:1:1::1"));
+ requestIA(client_, Hint(IAID(2), "2001:db8:1:1::2"));
+ requestIA(client_, Hint(IAID(1), "2001:db8:1:1::3"));
+ requestIA(client_, Hint(IAID(6), "3000:1:1::/64"));
+ requestIA(client_, Hint(IAID(5), "3000:1:2::/64"));
+ requestIA(client_, Hint(IAID(4), "3000:1:3::/64"));
+
+ // Send Solicit with hints as specified above.
+ ASSERT_NO_THROW(do_solicit_());
+
+ // Make sure that the client still has the same leases and the leases
+ // should be assigned to the same IAs.
+ ASSERT_EQ(6, client_.getLeaseNum());
+
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::1"),
+ IAID(1)));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::2"),
+ IAID(2)));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"),
+ IAID(3)));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:1::"), 64,
+ IAID(4)));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:2::"), 64,
+ IAID(5)));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64,
+ IAID(6)));
+}
+
+// In this test, the client performs 4-way exchange and includes 3 IA_NAs and
+// 3 IA_PDs and includes no hints. The server has reservations for 2 addresses
+// and 2 prefixes for this client. The server allocates reserved leases and
+// an additional address and prefix from the dynamic pools. The server is
+// reconfigured to add 3rd address and 3rd prefix reservation for the client.
+// The client sends a Renew and the server should renew existing leases and
+// allocate newly reserved address and prefix, replacing the previously
+// allocated dynamic leases. For both dynamically allocated leases, the
+// server should return IAs with zero lifetimes.
+TEST_F(HostTest, appendReservationDuringRenew) {
+ // 4-way exchange to acquire 4 reserved leases and 2 dynamic leases.
+ testMultipleIAs(do_solicit_request_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"));
+
+ // The server must have not lease for the address and prefix for which
+ // we will later make reservations, because these are outside of the
+ // dynamic pool.
+ ASSERT_FALSE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+ ASSERT_FALSE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64));
+
+ // Retrieve leases from the dynamic pools and store them so as we can
+ // later check that they were returned with zero lifetimes when the
+ // reservations are added.
+ std::vector<Lease6> leases =
+ client_.getLeasesByAddressRange(IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::10"));
+ ASSERT_EQ(1, leases.size());
+ IOAddress dynamic_address_lease = leases[0].addr_;
+
+ leases = client_.getLeasesByPrefixPool(IOAddress("3001::"), 32, 64);
+ ASSERT_EQ(1, leases.size());
+ IOAddress dynamic_prefix_lease = leases[0].addr_;
+
+ // Add two additional reservations.
+ std::string c = configString(*client_.getDuid(),
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"),
+ Reservation("3000:1:3::/64"));
+
+ ASSERT_NO_THROW(configure(c, *client_.getServer()));
+
+ // Client renews and includes all leases it currently has in the IAs.
+ ASSERT_NO_THROW(client_.doRenew());
+
+ // The expectation is that the server allocated two new reserved leases to
+ // the client and removed leases allocated from the dynamic pools. The
+ // number if leases in the server configuration should include those that
+ // are returned with zero lifetimes. Hence, the total number of leases
+ // should be equal to 6 + 2 = 8.
+ ASSERT_EQ(8, client_.getLeaseNum());
+
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::1")));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::2")));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:1::"), 64));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:2::"), 64));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64));
+
+ // Make sure that the replaced leases have been returned with zero lifetimes.
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(dynamic_address_lease));
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(dynamic_prefix_lease, 64));
+
+ // Now let's test the scenario when all reservations are removed for this
+ // client.
+ c = configString(*client_.getDuid());
+
+ ASSERT_NO_THROW(configure(c, *client_.getServer()));
+
+ // An attempt to renew should result in removing all allocated leases,
+ // because these leases are no longer reserved and they don't belong to the
+ // dynamic pools.
+ ASSERT_NO_THROW(client_.doRenew());
+
+ // The total number of leases should include removed leases and newly
+ // allocated once, i.e. 6 + 6 = 12.
+ ASSERT_EQ(12, client_.getLeaseNum());
+
+ // All removed leases should be returned with zero lifetimes.
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1:1::1")));
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1:1::2")));
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1:1::3")));
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000:1:1::"), 64));
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000:1:2::"), 64));
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000:1:3::"), 64));
+
+ // Make sure that all address leases are within the dynamic pool range.
+ leases = client_.getLeasesByAddressRange(IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::10"));
+ EXPECT_EQ(3, leases.size());
+
+ // Make sure that all prefix leases are also within the dynamic pool range.
+ leases = client_.getLeasesByPrefixPool(IOAddress("3001::"), 32, 64);
+ EXPECT_EQ(3, leases.size());
+}
+
+// In this test, the client performs 4-way exchange and includes 3 IA_NAs
+// and 3 IA_PDs. Initially, the server has 2 address reservations and
+// 2 prefix reservations for this client. The server allocates the 2
+// reserved addresses to the first 2 IA_NAs and 2 reserved prefixes to the
+// first two IA_PDs. The server is reconfigured and 2 new reservations are
+// inserted: new address reservation before existing address reservations
+// and prefix reservation before existing prefix reservations.
+// The server should detect that leases already exist for reserved addresses
+// and prefixes and it should not remove existing leases. Instead, it should
+// replace dynamically allocated leases with newly added reservations
+TEST_F(HostTest, insertReservationDuringRenew) {
+ // 4-way exchange to acquire 4 reserved leases and 2 dynamic leases.
+ testMultipleIAs(do_solicit_request_,
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"));
+
+ // The server must have not lease for the address and prefix for which
+ // we will later make reservations, because these are outside of the
+ // dynamic pool.
+ ASSERT_FALSE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3")));
+ ASSERT_FALSE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64));
+
+ // Retrieve leases from the dynamic pools and store them so as we can
+ // later check that they were returned with zero lifetimes when the
+ // reservations are added.
+ std::vector<Lease6> leases =
+ client_.getLeasesByAddressRange(IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::10"));
+ ASSERT_EQ(1, leases.size());
+ IOAddress dynamic_address_lease = leases[0].addr_;
+
+ leases = client_.getLeasesByPrefixPool(IOAddress("3001::"), 32, 64);
+ ASSERT_EQ(1, leases.size());
+ IOAddress dynamic_prefix_lease = leases[0].addr_;
+
+ // Add two additional reservations.
+ std::string c = configString(*client_.getDuid(),
+ Reservation("2001:db8:1:1::3"),
+ Reservation("2001:db8:1:1::1"),
+ Reservation("2001:db8:1:1::2"),
+ Reservation("3000:1:3::/64"),
+ Reservation("3000:1:1::/64"),
+ Reservation("3000:1:2::/64"));
+
+ ASSERT_NO_THROW(configure(c, *client_.getServer()));
+
+ // Client renews and includes all leases it currently has in the IAs.
+ ASSERT_NO_THROW(client_.doRenew());
+
+ // The expectation is that the server allocated two new reserved leases to
+ // the client and removed leases allocated from the dynamic pools. The
+ // number if leases in the server configuration should include those that
+ // are returned with zero lifetimes. Hence, the total number of leases
+ // should be equal to 6 + 2 = 8.
+ ASSERT_EQ(8, client_.getLeaseNum());
+
+ // Even though the new reservations have been added before existing
+ // reservations, the server should assign them to the IAs with
+ // IAID = 3 (for address) and IAID = 6 (for prefix).
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::1"),
+ IAID(1)));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::2"),
+ IAID(2)));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1:1::3"),
+ IAID(3)));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:1::"), 64,
+ IAID(4)));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:2::"), 64,
+ IAID(5)));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3000:1:3::"), 64,
+ IAID(6)));
+
+ // Make sure that the replaced leases have been returned with zero lifetimes.
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForAddress(dynamic_address_lease));
+ EXPECT_TRUE(client_.hasLeaseWithZeroLifetimeForPrefix(dynamic_prefix_lease, 64));
+}
+
+// In this test there are two clients. One client obtains two leases: one
+// for a prefix, another one for an address. The server is reconfigured
+// to make 4 reservations to a different client. Two of those reservations
+// are for the prefix and the address assigned to the first client. The
+// second client performs 4-way exchange and the server detects that two
+// reserved leases are not available because they are in use by another
+// client. The server assigns available address and prefix and an address
+// and prefix from dynamic pool. The first client renews and the server
+// detects that the renewed leases are reserved for another client. As
+// a result, the client obtains an address and prefix from the dynamic
+// pools. The second client renews and it obtains all reserved
+// addresses and prefixes.
+TEST_F(HostTest, multipleIAsConflict) {
+ Dhcp6Client client;
+ client.setDUID("01:02:03:05");
+
+ // Create configuration without any reservations.
+ std::string c = configString(*client_.getDuid());
+
+ ASSERT_NO_THROW(configure(c, *client_.getServer()));
+
+ // First client performs 4-way exchange and obtains an address and
+ // prefix indicated in hints.
+ requestIA(client, Hint(IAID(1), "2001:db8:1::1"));
+ requestIA(client, Hint(IAID(2), "3001:0:0:10::/64"));
+
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure the client has obtained requested leases.
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:1::1"), IAID(1)));
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("3001:0:0:10::"), 64,
+ IAID(2)));
+
+ // Reconfigure the server to make reservations for the second client.
+ // The reservations include a prefix and address acquired by the
+ // first client in the previous transaction.
+ c = configString(*client_.getDuid(),
+ Reservation("2001:db8:1::1"),
+ Reservation("2001:db8:1::2"),
+ Reservation("3001:0:0:9::/64"),
+ Reservation("3001:0:0:10::/64"));
+
+ ASSERT_NO_THROW(configure(c, *client_.getServer()));
+
+ // Configure the second client to send two IA_NAs and two IA_PDs with
+ // IAIDs from 1 to 4.
+ client_.requestAddress(1);
+ client_.requestAddress(2);
+ client_.requestPrefix(3);
+ client_.requestPrefix(4);
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(do_solicit_request_());
+
+ // The client should have obtained 4 leases: two prefixes and two addresses.
+ ASSERT_EQ(4, client_.getLeaseNum());
+
+ // The address "2001:db8:1::2" is reserved and available so the
+ // server should have assigned it.
+ ASSERT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1::2"),
+ IAID(1)));
+ // The address "2001:db8:1::1" was hijacked by another client so it
+ // must not be assigned to this client.
+ ASSERT_FALSE(client_.hasLeaseForAddress(IOAddress("2001:db8:1::1")));
+ // This client should have got an address from the dynamic pool excluding
+ // two addresses already assigned, i.e. excluding "2001:db8:1::1" and
+ // "2001:db8:1::2".
+ ASSERT_TRUE(client_.hasLeaseForAddressRange(IOAddress("2001:db8:1::3"),
+ IOAddress("2001:db8:1::10")));
+
+ // Same story with prefixes.
+ ASSERT_TRUE(client_.hasLeaseForPrefix(IOAddress("3001:0:0:9::"), 64,
+ IAID(3)));
+ ASSERT_FALSE(client_.hasLeaseForPrefix(IOAddress("3001:0:0:10::"), 64));
+
+
+ // Now that the reservations have been made, the first client should get
+ // non-reserved leases upon renewal. The server detects that the leases
+ // are reserved for someone else.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // For those leases, the first client should get 0 lifetimes.
+ ASSERT_TRUE(client.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1::1")));
+ ASSERT_TRUE(client.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3001:0:0:10::"), 64));
+
+ // The total number of leases should be 4 - two leases with zero lifetimes
+ // and two leases with address and prefix from the dynamic pools, which
+ // replace previously assigned leases. We don't care too much what those
+ // leases are, though.
+ EXPECT_EQ(4, client.getLeaseNum());
+
+ // The second client renews and the server should be now able to assign
+ // all reserved leases to this client.
+ ASSERT_NO_THROW(client_.doRenew());
+
+ // Client requests 4 leases, but there are additional two with zero
+ // lifetimes to indicate that the client should not use the address
+ // and prefix from the dynamic pools anymore.
+ ASSERT_EQ(6, client_.getLeaseNum());
+
+ // Check that the client has all reserved leases.
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1::2"),
+ IAID(1)));
+ EXPECT_TRUE(client_.hasLeaseForAddress(IOAddress("2001:db8:1::1"),
+ IAID(2)));
+
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3001:0:0:9::"), 64,
+ IAID(3)));
+ EXPECT_TRUE(client_.hasLeaseForPrefix(IOAddress("3001:0:0:10::"), 64,
+ IAID(4)));
+}
+
+// This test verifies a scenario in which a client trying to renew a
+// lease is refused this lease because it has been reserved to another
+// client. The client is assigned another available lease from a
+// dynamic pool by reusing an expired lease.
+TEST_F(HostTest, conflictResolutionReuseExpired) {
+ Dhcp6Client client1;
+
+ ASSERT_NO_THROW(configure(CONFIGS[6], *client1.getServer()));
+
+ // First client performs 4-way exchange and obtains an address and
+ // prefix indicated in hints.
+ requestIA(client1, Hint(IAID(1), "2001:db8:1::1"));
+ requestIA(client1, Hint(IAID(2), "3000::/120"));
+
+ ASSERT_NO_THROW(client1.doSARR());
+
+ // Make sure the client has obtained requested leases.
+ ASSERT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::1"), IAID(1)));
+ ASSERT_TRUE(client1.hasLeaseForPrefix(IOAddress("3000::"), 120));
+
+ // Create another client which is assigned another lease.
+ Dhcp6Client client2(client1.getServer());
+
+ // Second client performs 4-way exchange and obtains an address and
+ // prefix indicated in hints.
+ requestIA(client2, Hint(IAID(1), "2001:db8:1::2"));
+ requestIA(client2, Hint(IAID(2), "3000::100/120"));
+
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // Make sure the client has obtained requested leases.
+ ASSERT_TRUE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::2"), IAID(1)));
+ ASSERT_TRUE(client2.hasLeaseForPrefix(IOAddress("3000::100"), 120));
+
+ // Fast forward time to simulate aging of leases. After that, both leases are
+ // expired because their valid lifetime is 40s. The second argument indicates
+ // that the leases should also be updated on the server.
+ client1.fastFwdTime(60, true);
+ client2.fastFwdTime(60, true);
+
+ // Reconfigure the server, so as the address 2001:db8:1::2 and prefix
+ // 3000::10/120 is now reserved for another client.
+ ASSERT_NO_THROW(configure(CONFIGS[7], *client1.getServer()));
+
+ client1.clearRequestedIAs();
+ client2.clearRequestedIAs();
+
+ // Try to renew the address of 2001:db8:1::2 and prefix 3000::100/120.
+ ASSERT_NO_THROW(client2.doRenew());
+
+ // The renewed address and prefix are now reserved for another client so
+ // available leases should be allocated instead.
+ EXPECT_TRUE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::1")));
+ EXPECT_TRUE(client2.hasLeaseForPrefix(IOAddress("3000::"), 120));
+ // The previously allocated leases should now be returned with zero lifetimes.
+ EXPECT_TRUE(client2.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1::2")));
+ EXPECT_TRUE(client2.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000::100"), 120));
+
+ // We've had a bug in DHCPv6 server that reused lease (allocated previously to
+ // a different client) was returned to the client reusing leases. This a big issue
+ // because effectively a client reusing an expired lease would get this lease twice:
+ // with non-zero lifetimes and the second time with zero lifetimes. This is seriously
+ // confusing for the clients. This checks tha the bug has been eliminated.
+ EXPECT_FALSE(client2.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1::1")));
+ EXPECT_FALSE(client2.hasLeaseWithZeroLifetimeForPrefix(IOAddress("3000::"), 120));
+}
+
+// Verifies fundamental Global vs Subnet host reservations for NA leases
+TEST_F(HostTest, globalReservationsNA) {
+ Dhcp6Client client;
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[8], *client.getServer()));
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ {
+ SCOPED_TRACE("Global HR by DUID with in-range reserved address");
+ client.setDUID("02:02:03:04");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get global reserved address and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:1::77", "duid-host-fixed-in-range"));
+ }
+
+ {
+ SCOPED_TRACE("Global HR by DUID with an out-of-range reserved address");
+ client.setDUID("01:02:03:04");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get global reserved address and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:1::1", "duid-host-fixed-out-of-range"));
+ }
+
+ {
+ SCOPED_TRACE("Global HR by DUID with dynamic address");
+ client.clearConfig();
+ client.setDUID("01:02:03:05");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:1::", "duid-host-dynamic"));
+ }
+
+ {
+ SCOPED_TRACE("Global HR by HW Address with dynamic address");
+ client.clearConfig();
+ client.setDUID("33:44:55:66");
+ client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee"));
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and hardware host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:1::2", "hw-host"));
+ }
+
+ {
+ SCOPED_TRACE("Default subnet reservations flags excludes global reservations");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:04");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and no host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:2::", ""));
+ }
+
+ {
+ SCOPED_TRACE("Subnet reservation over global");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:05");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:2::1", "subnet-duid-host"));
+ }
+
+ {
+ SCOPED_TRACE("Subnet reservation preferred over global");
+ // Patch the second subnet to both global and in-subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getSubnet(2);
+ ASSERT_TRUE(subnet);
+ subnet->setReservationsGlobal(true);
+ subnet->setReservationsInSubnet(true);
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:05");
+ client.requestAddress(1234, IOAddress("::"));
+ // Should get dynamic address and host name because it has preference
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "2001:db8:2::1", "subnet-duid-host"));
+ }
+}
+
+// Verifies fundamental Global vs Subnet host reservations for PD leases
+TEST_F(HostTest, globalReservationsPD) {
+ Dhcp6Client client;
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[9], *client.getServer()));
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ {
+ SCOPED_TRACE("Global HR by DUID with reserved prefix");
+ client.setDUID("01:02:03:04");
+ client.requestPrefix(1);
+ // Should get global reserved prefix and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "4000::100", "duid-host-fixed"));
+ }
+
+ {
+ SCOPED_TRACE("Global HR by DUID with dynamic prefix");
+ client.clearConfig();
+ client.setDUID("01:02:03:05");
+ client.requestPrefix(1);
+ // Should get dynamic prefix and reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3000::", "duid-host-dynamic"));
+ }
+
+ {
+ SCOPED_TRACE("Default subnet reservations flags excludes global reservations");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:04");
+ client.requestPrefix(1);
+ // Should get dynamic prefix and no host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3001::", ""));
+ }
+
+ {
+ SCOPED_TRACE("Subnet reservation over global");
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:05");
+ client.requestPrefix(1);
+ // Should get dynamic prefix and subnet reserved host name
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3001::100", "subnet-duid-host"));
+ }
+
+ {
+ SCOPED_TRACE("Subnet reservation preferred over global");
+ // Patch the second subnet to both global and in-subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getSubnet(2);
+ ASSERT_TRUE(subnet);
+ subnet->setReservationsGlobal(true);
+ subnet->setReservationsInSubnet(true);
+ client.clearConfig();
+ client.setInterface("eth1");
+ client.setDUID("01:02:03:05");
+ client.requestPrefix(1);
+ // Should get dynamic prefix and subnet reserved host name
+ // because it has preference over the global reservation.
+ ASSERT_NO_FATAL_FAILURE(sarrTest(client, "3001::100", "subnet-duid-host"));
+ }
+}
+
+// Verifies that client class specified in the global reservation
+// may be used to influence pool selection.
+TEST_F(HostTest, clientClassGlobalPoolSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(10));
+}
+
+// Verifies that client class specified in the global reservation
+// may be used to influence subnet selection within shared network.
+TEST_F(HostTest, clientClassGlobalSubnetSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(11));
+}
+
+// Verifies that client class specified in the reservation may be
+// used to influence pool selection within a subnet.
+TEST_F(HostTest, clientClassPoolSelection) {
+ ASSERT_NO_FATAL_FAILURE(testGlobalClassSubnetPoolSelection(12, "2001:db8:1::10",
+ "2001:db8:1::20"));
+}
+
+// Verifies that if the server is configured to allow for specifying
+// multiple reservations for the same IP address the first client
+// matching the reservation will be given this address. The second
+// client will be given a different lease.
+TEST_F(HostTest, firstClientGetsReservedAddress) {
+ // Create a client which has DUID matching the reservation.
+ Dhcp6Client client1;
+ client1.setDUID("01:02:03:04");
+ ASSERT_NO_THROW(configure(CONFIGS[13], *client1.getServer()));
+ // client1 performs 4-way exchange to get the reserved lease.
+ requestIA(client1, Hint(IAID(1), "2001:db8:1::10"));
+ ASSERT_NO_THROW(client1.doSARR());
+
+ // Make sure the client has obtained reserved lease.
+ ASSERT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+ // Create another client that has a reservation for the same
+ // IP address.
+ Dhcp6Client client2(client1.getServer());
+ client2.setDUID("01:02:03:05");
+ requestIA(client2, Hint(IAID(1), "2001:db8:1::10"));
+
+ // client2 performs 4-way exchange.
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // Make sure the client didn't get the reserved lease. This lease has been
+ // already taken by the client1.
+ EXPECT_FALSE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+ // Make sure the client2 got a lease from the configured pool.
+ auto leases = client2.getLeasesByAddressRange(IOAddress("2001:db8:1::10"),
+ IOAddress("2001:db8:1::200"));
+ EXPECT_EQ(1, leases.size());
+
+ // Verify that the client1 can renew the lease.
+ ASSERT_NO_THROW(client1.doRenew());
+ EXPECT_TRUE(client1.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+
+ // The client2 should also renew the lease.
+ ASSERT_NO_THROW(client2.doRenew());
+ EXPECT_FALSE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+ leases = client2.getLeasesByAddressRange(IOAddress("2001:db8:1::10"),
+ IOAddress("2001:db8:1::200"));
+ EXPECT_EQ(1, leases.size());
+
+ // If the client1 releases the reserved lease, the client2 should acquire it.
+ ASSERT_NO_THROW(client1.doRelease());
+ ASSERT_NO_THROW(client2.doRenew());
+ EXPECT_TRUE(client2.hasLeaseForAddress(IOAddress("2001:db8:1::15"), IAID(1)));
+}
+
+// Verifies that if the server is configured to allow for specifying
+// multiple reservations for the same delegated prefix the first client
+// matching the reservation will be given this prefix. The second
+// client will be given a different lease.
+TEST_F(HostTest, firstClientGetsReservedPrefix) {
+ // Create a client which has DUID matching the reservation.
+ Dhcp6Client client1;
+ client1.setDUID("01:02:03:04");
+ ASSERT_NO_THROW(configure(CONFIGS[14], *client1.getServer()));
+ // client1 performs 4-way exchange to get the reserved lease.
+ client1.requestPrefix(1);
+ ASSERT_NO_THROW(client1.doSARR());
+
+ // Make sure the client has obtained reserved lease.
+ ASSERT_TRUE(client1.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+
+ // Create another client that has a reservation for the same
+ // IP address.
+ Dhcp6Client client2(client1.getServer());
+ client2.setDUID("01:02:03:05");
+ client2.requestPrefix(1);
+
+ // client2 performs 4-way exchange.
+ ASSERT_NO_THROW(client2.doSARR());
+
+ // Make sure the client didn't get the reserved lease. This lease has been
+ // already taken by the client1.
+ EXPECT_FALSE(client2.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+
+ // Make sure the client2 got a lease from the configured pool.
+ EXPECT_TRUE(client2.hasLeaseForPrefixPool(IOAddress("3000::"), 64, 112));
+
+ // Verify that the client1 can renew the lease.
+ ASSERT_NO_THROW(client1.doRenew());
+ EXPECT_TRUE(client1.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+
+ // The client2 should also renew the lease.
+ ASSERT_NO_THROW(client2.doRenew());
+ EXPECT_TRUE(client2.hasLeaseForPrefixPool(IOAddress("3000::"), 64, 112));
+
+ // If the client1 releases the reserved lease, the client2 should acquire it.
+ ASSERT_NO_THROW(client1.doRelease());
+ ASSERT_NO_THROW(client2.doRenew());
+ EXPECT_TRUE(client2.hasLeaseForPrefix(IOAddress("3000::5a:0"), 112, IAID(1)));
+}
+
+/// This test verifies the case when two clients have reservations for
+/// the same IP address. The first client sends Solicit and is offered
+/// the reserved address. At the same time, the second client having
+/// the reservation for the same IP address performs 4-way exchange
+/// using the reserved address as a hint in Solicit.
+/// The client gets the lease for this address. This test verifies
+/// that the allocation engine correctly identifies that the second
+/// client has a reservation for this address.
+TEST_F(HostTest, multipleClientsRace1) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleClientsRace("01:02:03:04", "01:02:03:05"));
+}
+
+// This is a second variant of the multipleClientsRace1. The test is almost
+// the same but the client matching the second reservation sends Solicit
+// first and then the client having the first reservation performs 4-way
+// exchange. This is to ensure that the order in which reservations are
+// defined does not matter.
+TEST_F(HostTest, multipleClientsRace2) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleClientsRace("01:02:03:05", "01:02:03:04"));
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/infrequest_unittest.cc b/src/bin/dhcp6/tests/infrequest_unittest.cc
new file mode 100644
index 0000000..df3ac76
--- /dev/null
+++ b/src/bin/dhcp6/tests/infrequest_unittest.cc
@@ -0,0 +1,363 @@
+// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <stats/stats_mgr.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used by the Information-Request unit tests.
+///
+/// - Configuration 0:
+/// - one subnet used on eth0 interface
+/// - with address and prefix pools
+/// - dns-servers option
+/// - Configuration 1:
+/// - one subnet used on eth0 interface
+/// - no addresses or prefixes
+/// - domain-search option
+/// - Configuration 2:
+/// - one subnet used on eth0 interface
+/// - dns-servers option for subnet
+/// - sip-servers defined in global scope
+/// - Configuration 3:
+/// - nis-server, nis-domain specified in global scope
+/// - no subnets defined
+const char* CONFIGS[] = {
+ // Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:3::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64"
+ " } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8::1, 2001:db8::2\""
+ " } ],"
+ " \"subnet\": \"2001:db8::/32\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"option-data\": [ {"
+ " \"name\": \"sip-server-addr\","
+ " \"data\": \"2001:db8::abcd\""
+ " } ],"
+ " \"subnet\": \"2001:db8::/32\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ " \"option-data\": [ {"
+ " \"name\": \"sip-server-addr\","
+ " \"data\": \"2001:db8::1\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"subnet\": \"2001:db8::/32\", "
+ " \"interface\": \"eth0\","
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8::2\""
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"option-data\": [ {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"2001:db8::1, 2001:db8::2\""
+ " } ]"
+ "}"
+};
+
+/// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
+/// Request-Reply.
+class InfRequestTest : public Dhcpv6SrvTest {
+public:
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ InfRequestTest()
+ : Dhcpv6SrvTest(),
+ iface_mgr_test_config_(true) {
+
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes any statistics that may have been set.
+ ~InfRequestTest() {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+/// Check that server processes correctly an incoming inf-request in a
+/// typical subnet that has also address and prefix pools.
+TEST_F(InfRequestTest, infRequestBasic) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_PD.
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Perform 2-way exchange (Inf-request/reply)
+ client.requestOption(D6O_NAME_SERVERS);
+ ASSERT_NO_THROW(client.doInfRequest());
+
+ // Confirm that there's a response
+ Pkt6Ptr response = client.getContext().response_;
+ ASSERT_TRUE(response);
+
+ // Check that it contains our client-id
+ OptionPtr client_id = response->getOption(D6O_CLIENTID);
+ ASSERT_TRUE(client_id);
+ EXPECT_TRUE(compareOptions(client_id, client.getClientId()));
+
+ // Check that it contains proper server-id
+ OptionPtr server_id = response->getOption(D6O_SERVERID);
+ ASSERT_TRUE(server_id);
+ EXPECT_TRUE(compareOptions(server_id, client.getServer()->getServerID()));
+
+ // Check that we received requested DNS servers option
+ Option6AddrLstPtr dns = boost::dynamic_pointer_cast<Option6AddrLst>
+ (response->getOption(D6O_NAME_SERVERS));
+ ASSERT_TRUE(dns);
+ Option6AddrLst::AddressContainer addrs = dns->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_EQ("2001:db8::1", addrs[0].toText());
+ EXPECT_EQ("2001:db8::2", addrs[1].toText());
+}
+
+/// Check that server processes correctly an incoming inf-request
+/// that does not hold client-id. It's so called anonymous inf-request.
+/// Uncommon, but certainly valid behavior.
+TEST_F(InfRequestTest, infRequestAnonymous) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_PD.
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Perform 2-way exchange (Inf-request/reply)
+ client.requestOption(D6O_NAME_SERVERS);
+ client.useClientId(false);
+ ASSERT_NO_THROW(client.doInfRequest());
+
+ // Confirm that there's a response
+ Pkt6Ptr response = client.getContext().response_;
+ ASSERT_TRUE(response);
+
+ // Check that we received the requested DNS servers option
+ Option6AddrLstPtr dns = boost::dynamic_pointer_cast<Option6AddrLst>
+ (response->getOption(D6O_NAME_SERVERS));
+ ASSERT_TRUE(dns);
+ Option6AddrLst::AddressContainer addrs = dns->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_EQ("2001:db8::1", addrs[0].toText());
+ EXPECT_EQ("2001:db8::2", addrs[1].toText());
+}
+
+/// Check that server processes correctly an incoming inf-request
+/// if there is a subnet without any addresses or prefixes configured.
+TEST_F(InfRequestTest, infRequestStateless) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_PD.
+ configure(CONFIGS[1], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Perform 2-way exchange (Inf-request/reply)
+ client.requestOption(D6O_SIP_SERVERS_ADDR);
+ ASSERT_NO_THROW(client.doInfRequest());
+
+ // Confirm that there's a response
+ Pkt6Ptr response = client.getContext().response_;
+ ASSERT_TRUE(response);
+
+ // Check that we received the requested SIP servers option
+ Option6AddrLstPtr sip = boost::dynamic_pointer_cast<Option6AddrLst>
+ (response->getOption(D6O_SIP_SERVERS_ADDR));
+ ASSERT_TRUE(sip);
+ Option6AddrLst::AddressContainer addrs = sip->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8::abcd", addrs[0].toText());
+}
+
+/// Check that server processes correctly an incoming inf-request
+/// if there are options defined at both global and subnet scope.
+TEST_F(InfRequestTest, infRequestSubnetAndGlobal) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_PD.
+ configure(CONFIGS[2], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Perform 2-way exchange (Inf-request/reply)
+ client.requestOption(D6O_SIP_SERVERS_ADDR);
+ client.requestOption(D6O_NAME_SERVERS);
+ ASSERT_NO_THROW(client.doInfRequest());
+
+ // Confirm that there's a response
+ Pkt6Ptr response = client.getContext().response_;
+ ASSERT_TRUE(response);
+
+ // Check that we received the requested sip servers option
+ Option6AddrLstPtr sip = boost::dynamic_pointer_cast<Option6AddrLst>
+ (response->getOption(D6O_SIP_SERVERS_ADDR));
+ ASSERT_TRUE(sip);
+ Option6AddrLst::AddressContainer addrs = sip->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8::1", addrs[0].toText());
+
+ // Check that we received the requested dns servers option
+ Option6AddrLstPtr dns = boost::dynamic_pointer_cast<Option6AddrLst>
+ (response->getOption(D6O_NAME_SERVERS));
+ ASSERT_TRUE(dns);
+ addrs = dns->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8::2", addrs[0].toText());
+}
+
+/// Check that server processes correctly an incoming inf-request
+/// if there are options defined at global scope only (no subnets).
+TEST_F(InfRequestTest, infRequestNoSubnets) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_PD.
+ configure(CONFIGS[3], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(0, subnets->size());
+
+ // Perform 2-way exchange (Inf-request/reply)
+ client.requestOption(D6O_NIS_SERVERS);
+ ASSERT_NO_THROW(client.doInfRequest());
+
+ // Confirm that there's a response
+ Pkt6Ptr response = client.getContext().response_;
+ ASSERT_TRUE(response);
+
+ // Check that we received the requested sip servers option
+ Option6AddrLstPtr nis = boost::dynamic_pointer_cast<Option6AddrLst>
+ (response->getOption(D6O_NIS_SERVERS));
+ ASSERT_TRUE(nis);
+ Option6AddrLst::AddressContainer addrs = nis->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_EQ("2001:db8::1", addrs[0].toText());
+ EXPECT_EQ("2001:db8::2", addrs[1].toText());
+}
+
+/// Check that server processes correctly an incoming inf-request in a
+/// typical subnet that has also address and prefix pools.
+TEST_F(InfRequestTest, infRequestStats) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_PD.
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+
+ // Check that the tested statistics is initially set to 0
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt6_rcvd = mgr.getObservation("pkt6-received");
+ ObservationPtr pkt6_infreq_rcvd = mgr.getObservation("pkt6-infrequest-received");
+ ObservationPtr pkt6_reply_sent = mgr.getObservation("pkt6-reply-sent");
+ ObservationPtr pkt6_sent = mgr.getObservation("pkt6-sent");
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(pkt6_infreq_rcvd);
+ ASSERT_TRUE(pkt6_reply_sent);
+ ASSERT_TRUE(pkt6_sent);
+ EXPECT_EQ(0, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(0, pkt6_infreq_rcvd->getInteger().first);
+ EXPECT_EQ(0, pkt6_reply_sent->getInteger().first);
+ EXPECT_EQ(0, pkt6_sent->getInteger().first);
+
+ // Perform 2-way exchange (Inf-request/reply)
+ client.requestOption(D6O_NAME_SERVERS);
+ ASSERT_NO_THROW(client.doInfRequest());
+
+ // Confirm that there's a response
+ Pkt6Ptr response = client.getContext().response_;
+ ASSERT_TRUE(response);
+
+ pkt6_rcvd = mgr.getObservation("pkt6-received");
+ pkt6_infreq_rcvd = mgr.getObservation("pkt6-infrequest-received");
+ pkt6_reply_sent = mgr.getObservation("pkt6-reply-sent");
+ pkt6_sent = mgr.getObservation("pkt6-sent");
+
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(pkt6_infreq_rcvd);
+ ASSERT_TRUE(pkt6_reply_sent);
+ ASSERT_TRUE(pkt6_sent);
+
+ // They also must have expected values.
+ EXPECT_EQ(1, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(1, pkt6_infreq_rcvd->getInteger().first);
+ EXPECT_EQ(1, pkt6_reply_sent->getInteger().first);
+ EXPECT_EQ(1, pkt6_sent->getInteger().first);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/kea_controller_unittest.cc b/src/bin/dhcp6/tests/kea_controller_unittest.cc
new file mode 100644
index 0000000..eb06f34
--- /dev/null
+++ b/src/bin/dhcp6/tests/kea_controller_unittest.cc
@@ -0,0 +1,1087 @@
+// Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/parser_context.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcpsrv/cb_ctl_dhcp4.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <process/config_base.h>
+
+#ifdef HAVE_MYSQL
+#include <mysql/testutils/mysql_schema.h>
+#endif
+
+#include <log/logger_support.h>
+#include <util/stopwatch.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+
+#ifdef HAVE_MYSQL
+using namespace isc::db::test;
+#endif
+
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+
+namespace {
+
+/// @brief Test implementation of the @c CBControlDHCPv6.
+///
+/// This implementation is installed on the test server instance. It
+/// overrides the implementation of the @c databaseConfigFetch function
+/// to verify arguments passed to this function and throw an exception
+/// when desired in the negative test scenarios. It doesn't do the
+/// actual configuration fetch as this is tested elswhere and would
+/// require setting up a database configuration backend.
+class TestCBControlDHCPv6 : public CBControlDHCPv6 {
+public:
+
+ /// @brief Constructor.
+ TestCBControlDHCPv6()
+ : CBControlDHCPv6(), db_total_config_fetch_calls_(0),
+ db_current_config_fetch_calls_(0), db_staging_config_fetch_calls_(0),
+ enable_check_fetch_mode_(false), enable_throw_(false) {
+ }
+
+ /// @brief Stub implementation of the "fetch" function.
+ ///
+ /// If this is not the first invocation of this function, it
+ /// verifies that the @c fetch_mode has been correctly set to
+ /// @c FetchMode::FETCH_UPDATE.
+ ///
+ /// It also throws an exception when desired by a test, to
+ /// verify that the server gracefully handles such exception.
+ ///
+ /// @param config either the staging or the current configuration.
+ /// @param fetch_mode value indicating if the method is called upon the
+ /// server start up or it is called to fetch configuration updates.
+ ///
+ /// @throw Unexpected when configured to do so.
+ virtual void databaseConfigFetch(const process::ConfigPtr& config,
+ const FetchMode& fetch_mode) {
+ ++db_total_config_fetch_calls_;
+
+ if (config == CfgMgr::instance().getCurrentCfg()) {
+ ++db_current_config_fetch_calls_;
+ } else if (config == CfgMgr::instance().getStagingCfg()) {
+ ++db_staging_config_fetch_calls_;
+ }
+
+ if (enable_check_fetch_mode_) {
+ if ((db_total_config_fetch_calls_ <= 1) &&
+ (fetch_mode == FetchMode::FETCH_UPDATE)) {
+ ADD_FAILURE() << "databaseConfigFetch was called with the value "
+ "of fetch_mode=FetchMode::FETCH_UPDATE upon the server configuration";
+
+ } else if ((db_total_config_fetch_calls_ > 1) &&
+ (fetch_mode == FetchMode::FETCH_ALL)) {
+ ADD_FAILURE() << "databaseConfigFetch was called with the value "
+ "of fetch_mode=FetchMode::FETCH_ALL during fetching the updates";
+ }
+ }
+
+ if (enable_throw_) {
+ isc_throw(Unexpected, "testing if exceptions are correctly handled");
+ }
+ }
+
+ /// @brief Returns number of invocations of the @c databaseConfigFetch
+ /// (total).
+ size_t getDatabaseTotalConfigFetchCalls() const {
+ return (db_total_config_fetch_calls_);
+ }
+
+ /// @brief Returns number of invocations of the @c databaseConfigFetch
+ /// (current configuration).
+ size_t getDatabaseCurrentConfigFetchCalls() const {
+ return (db_current_config_fetch_calls_);
+ }
+
+ /// @brief Returns number of invocations of the @c databaseConfigFetch
+ /// (staging configuration).
+ size_t getDatabaseStagingConfigFetchCalls() const {
+ return (db_staging_config_fetch_calls_);
+ }
+
+ /// @brief Enables checking of the @c fetch_mode value.
+ void enableCheckFetchMode() {
+ enable_check_fetch_mode_ = true;
+ }
+
+ /// @brief Enables the object to throw from @c databaseConfigFetch.
+ void enableThrow() {
+ enable_throw_ = true;
+ }
+
+private:
+
+ /// @brief Counter holding number of invocations of the
+ /// @c databaseConfigFetch (total).
+ size_t db_total_config_fetch_calls_;
+
+ /// @brief Counter holding number of invocations of the
+ /// @c databaseConfigFetch (current configuration).
+ size_t db_current_config_fetch_calls_;
+
+ /// @brief Counter holding number of invocations of the
+ /// @c databaseConfigFetch (staging configuration).
+ size_t db_staging_config_fetch_calls_;
+
+ /// @brief Boolean flag indicated if the value of the @c fetch_mode
+ /// should be verified.
+ bool enable_check_fetch_mode_;
+
+ /// @brief Boolean flag indicating if the @c databaseConfigFetch should
+ /// throw.
+ bool enable_throw_;
+};
+
+/// @brief Shared pointer to the @c TestCBControlDHCPv6.
+typedef boost::shared_ptr<TestCBControlDHCPv6> TestCBControlDHCPv6Ptr;
+
+/// @brief "Naked" DHCPv6 server.
+///
+/// Exposes internal fields and installs stub implementation of the
+/// @c CBControlDHCPv6 object.
+class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
+public:
+
+ /// @brief Constructor.
+ NakedControlledDhcpv6Srv()
+ : ControlledDhcpv6Srv(0) {
+ // We're replacing the @c CBControlDHCPv6 instance with our
+ // stub implementation used in tests.
+ cb_control_.reset(new TestCBControlDHCPv6());
+ }
+};
+
+
+class JSONFileBackendTest : public dhcp::test::BaseServerTest {
+public:
+ JSONFileBackendTest()
+ : BaseServerTest() {
+ }
+
+ ~JSONFileBackendTest() {
+ LeaseMgrFactory::destroy();
+ isc::log::setDefaultLoggingOutput();
+ static_cast<void>(remove(TEST_FILE));
+ static_cast<void>(remove(TEST_INCLUDE));
+ };
+
+ void writeFile(const std::string& file_name, const std::string& content) {
+ static_cast<void>(remove(file_name.c_str()));
+
+ ofstream out(file_name.c_str(), ios::trunc);
+ EXPECT_TRUE(out.is_open());
+ out << content;
+ out.close();
+ }
+
+ /// @brief Runs timers for specified time.
+ ///
+ /// @param io_service Pointer to the IO service to be ran.
+ /// @param timeout_ms Amount of time after which the method returns.
+ /// @param cond Pointer to the function which if returns true it
+ /// stops the IO service and causes the function to return.
+ void runTimersWithTimeout(const IOServicePtr& io_service, const long timeout_ms,
+ std::function<bool()> cond = std::function<bool()>()) {
+ IntervalTimer timer(*io_service);
+ std::atomic<bool> stopped(false);
+ timer.setup([&io_service, &stopped]() {
+ stopped = true;
+ io_service->stop();
+ }, timeout_ms, IntervalTimer::ONE_SHOT);
+
+ // Run as long as the timeout hasn't occurred and the interrupting
+ // condition is not specified or not met.
+ while (!stopped && (!cond || !cond())) {
+ io_service->run_one();
+ }
+ io_service->get_io_service().reset();
+ }
+
+ /// @brief This test verifies that the timer used to fetch the configuration
+ /// updates from the database works as expected.
+ void testConfigBackendTimer(const int config_wait_fetch_time,
+ const bool throw_during_fetch = false,
+ const bool call_command = false) {
+ std::ostringstream config;
+ config <<
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},"
+ "\"lease-database\": {"
+ " \"type\": \"memfile\","
+ " \"persist\": false"
+ "},"
+ "\"config-control\": {"
+ " \"config-fetch-wait-time\": " << config_wait_fetch_time <<
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ writeFile(TEST_FILE, config.str());
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<NakedControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv6Srv()));
+ ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+ // Get the CBControlDHCPv6 object belonging to this server.
+ auto cb_control = boost::dynamic_pointer_cast<TestCBControlDHCPv6>(srv->getCBControl());
+
+ // Verify that the parameter passed to the databaseConfigFetch has an
+ // expected value.
+ cb_control->enableCheckFetchMode();
+
+ // Instruct our stub implementation of the CBControlDHCPv6 to throw as a
+ // result of fetch if desired.
+ if (throw_during_fetch) {
+ cb_control->enableThrow();
+ }
+
+ // So far there should be exactly one attempt to fetch the configuration
+ // from the backend. That's the attempt made upon startup on
+ // the staging configuration.
+ // All other fetches will be on the current configuration:
+ // - the timer makes a closure with the staging one but it is
+ // committed so becomes the current one.
+ // - the command is called outside configuration so it must
+ // be the current configuration. The test explicitly checks this.
+ EXPECT_EQ(1, cb_control->getDatabaseTotalConfigFetchCalls());
+ EXPECT_EQ(0, cb_control->getDatabaseCurrentConfigFetchCalls());
+ EXPECT_EQ(1, cb_control->getDatabaseStagingConfigFetchCalls());
+
+
+ if (call_command) {
+ // The case where there is no backend is tested in the
+ // controlled server tests so we have only to verify
+ // that the command calls the database config fetch.
+
+ // Count the startup.
+ EXPECT_EQ(cb_control->getDatabaseTotalConfigFetchCalls(), 1);
+ EXPECT_EQ(cb_control->getDatabaseCurrentConfigFetchCalls(), 0);
+ EXPECT_EQ(cb_control->getDatabaseStagingConfigFetchCalls(), 1);
+
+ ConstElementPtr result =
+ ControlledDhcpv6Srv::processCommand("config-backend-pull",
+ ConstElementPtr());
+ EXPECT_EQ(cb_control->getDatabaseTotalConfigFetchCalls(), 2);
+ std::string expected;
+
+ if (throw_during_fetch) {
+ expected = "{ \"result\": 1, \"text\": ";
+ expected += "\"On demand configuration update failed: ";
+ expected += "testing if exceptions are correctly handled\" }";
+ } else {
+ expected = "{ \"result\": 0, \"text\": ";
+ expected += "\"On demand configuration update successful.\" }";
+ }
+ EXPECT_EQ(expected, result->str());
+
+ // No good way to check the rescheduling...
+ ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 20));
+
+ if (config_wait_fetch_time > 0) {
+ EXPECT_GE(cb_control->getDatabaseTotalConfigFetchCalls(), 5);
+ EXPECT_GE(cb_control->getDatabaseCurrentConfigFetchCalls(), 4);
+ EXPECT_EQ(cb_control->getDatabaseStagingConfigFetchCalls(), 1);
+ } else {
+ EXPECT_EQ(cb_control->getDatabaseTotalConfigFetchCalls(), 2);
+ EXPECT_EQ(cb_control->getDatabaseCurrentConfigFetchCalls(), 1);
+ EXPECT_EQ(cb_control->getDatabaseStagingConfigFetchCalls(), 1);
+ }
+
+ } else if ((config_wait_fetch_time > 0) && (!throw_during_fetch)) {
+ // If we're configured to run the timer, we expect that it was
+ // invoked at least 3 times. This is sufficient to verify that
+ // the timer was scheduled and that the timer continued to run
+ // even when an exception occurred during fetch (that's why it
+ // is 3 not 2).
+ ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 500,
+ [cb_control]() {
+ // Interrupt the timers poll if we have recorded at
+ // least 3 attempts to fetch the updates.
+ return (cb_control->getDatabaseTotalConfigFetchCalls() >= 3);
+ }));
+ EXPECT_GE(cb_control->getDatabaseTotalConfigFetchCalls(), 3);
+ EXPECT_GE(cb_control->getDatabaseCurrentConfigFetchCalls(), 2);
+ EXPECT_EQ(cb_control->getDatabaseStagingConfigFetchCalls(), 1);
+
+ } else {
+ ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 500));
+
+ if (throw_during_fetch) {
+ // If we're simulating the failure condition the number
+ // of consecutive failures should not exceed 10. Therefore
+ // the number of recorded fetches should be 12. One at
+ // startup, 10 failures and one that causes the timer
+ // to stop.
+ EXPECT_EQ(12, cb_control->getDatabaseTotalConfigFetchCalls());
+ EXPECT_EQ(11, cb_control->getDatabaseCurrentConfigFetchCalls());
+ EXPECT_EQ(1, cb_control->getDatabaseStagingConfigFetchCalls());
+
+ } else {
+ // If the server is not configured to schedule the timer,
+ // we should still have one fetch attempt recorded.
+ EXPECT_EQ(1, cb_control->getDatabaseTotalConfigFetchCalls());
+ EXPECT_EQ(0, cb_control->getDatabaseCurrentConfigFetchCalls());
+ EXPECT_EQ(1, cb_control->getDatabaseStagingConfigFetchCalls());
+ }
+ }
+ }
+
+ static const char* TEST_FILE;
+ static const char* TEST_INCLUDE;
+};
+
+const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
+const char* JSONFileBackendTest::TEST_INCLUDE = "test-include.json";
+
+// This test checks if configuration can be read from a JSON file.
+TEST_F(JSONFileBackendTest, jsonFile) {
+
+ // Prepare configuration file.
+ string config = "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
+ " \"subnet\": \"2001:db8:2::/64\", "
+ " },"
+ " {"
+ " \"id\": 3, "
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
+ " \"subnet\": \"2001:db8:3::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using the config file.
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("2001:db8:1::", (*subnet)->get().first.toText());
+ EXPECT_EQ(64, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+
+ // Check subnet 2.
+ ++subnet;
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("2001:db8:2::", (*subnet)->get().first.toText());
+ EXPECT_EQ(64, (*subnet)->get().second);
+
+ // Check pools in the second subnet.
+ const PoolCollection& pools2 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools2.size());
+ EXPECT_EQ("2001:db8:2::", pools2.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:2::ffff:ffff:ffff", pools2.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools2.at(0)->getType());
+
+ // And finally check subnet 3.
+ ++subnet;
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("2001:db8:3::", (*subnet)->get().first.toText());
+ EXPECT_EQ(64, (*subnet)->get().second);
+
+ // ... and it's only pool.
+ const PoolCollection& pools3 = (*subnet)->getPools(Lease::TYPE_NA);
+ EXPECT_EQ("2001:db8:3::", pools3.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:3::ffff:ffff:ffff", pools3.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using hash (#) line comments
+TEST_F(JSONFileBackendTest, hashComments) {
+
+ string config_hash_comments = "# This is a comment. It should be \n"
+ "#ignored. Real config starts in line below\n"
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "# comments in the middle should be ignored, too\n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_hash_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config with comments.
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("2001:db8:1::", (*subnet)->get().first.toText());
+ EXPECT_EQ(64, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using C++ line (//) comments.
+TEST_F(JSONFileBackendTest, cppLineComments) {
+
+ string config_cpp_line_comments = "// This is a comment. It should be \n"
+ "//ignored. Real config starts in line below\n"
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "// comments in the middle should be ignored, too\n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_cpp_line_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config with comments.
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("2001:db8:1::", (*subnet)->get().first.toText());
+ EXPECT_EQ(64, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using C block (/* */) comments
+TEST_F(JSONFileBackendTest, cBlockComments) {
+
+ string config_c_block_comments = "/* This is a comment. It should be \n"
+ "ignored. Real config starts in line below*/\n"
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "/* comments in the middle should be ignored, too*/\n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ writeFile(TEST_FILE, config_c_block_comments);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config with comments.
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("2001:db8:1::", (*subnet)->get().first.toText());
+ EXPECT_EQ(64, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if configuration can be read from a JSON file
+// using an include file.
+TEST_F(JSONFileBackendTest, include) {
+
+ string config = "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "<?include \"" + string(TEST_INCLUDE) + "\"?>,"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ string include = "\n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ]\n";
+
+ writeFile(TEST_FILE, config);
+ writeFile(TEST_INCLUDE, include);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config with comments.
+ EXPECT_NO_THROW(srv->init(TEST_FILE));
+
+ // Now check if the configuration has been applied correctly.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(1, subnets->size());
+
+ // Check subnet 1.
+ auto subnet = subnets->begin();
+ ASSERT_TRUE(subnet != subnets->end());
+ EXPECT_EQ("2001:db8:1::", (*subnet)->get().first.toText());
+ EXPECT_EQ(64, (*subnet)->get().second);
+
+ // Check pools in the first subnet.
+ const PoolCollection& pools1 = (*subnet)->getPools(Lease::TYPE_NA);
+ ASSERT_EQ(1, pools1.size());
+ EXPECT_EQ("2001:db8:1::", pools1.at(0)->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff:ffff", pools1.at(0)->getLastAddress().toText());
+ EXPECT_EQ(Lease::TYPE_NA, pools1.at(0)->getType());
+}
+
+// This test checks if recursive include of a file is detected
+TEST_F(JSONFileBackendTest, recursiveInclude) {
+
+ string config_recursive_include = "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ <?include \"" + string(TEST_INCLUDE) + "\"?> ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ string include = "\"eth\", <?include \"" + string(TEST_INCLUDE) + "\"?>";
+ string msg = "configuration error using file '" + string(TEST_FILE) +
+ "': Too many nested include.";
+
+ writeFile(TEST_FILE, config_recursive_include);
+ writeFile(TEST_INCLUDE, include);
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // And configure it using config with comments.
+ try {
+ srv->init(TEST_FILE);
+ FAIL() << "Expected Dhcp6ParseError but nothing was raised";
+ }
+ catch (const Exception& ex) {
+ EXPECT_EQ(msg, ex.what());
+ }
+}
+
+// This test checks if configuration detects failure when trying:
+// - empty file
+// - empty filename
+// - no Dhcp6 element
+// - Config file that contains Dhcp6 but has a content error
+TEST_F(JSONFileBackendTest, configBroken) {
+
+ // Empty config is not allowed, because Dhcp6 element is missing
+ string config_empty = "";
+
+ // This config does not have mandatory Dhcp6 element
+ string config_v4 = "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.0/24\" ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " } ]}";
+
+ // This has Dhcp6 element, but it's utter nonsense
+ string config_nonsense = "{ \"Dhcp6\": { \"reviews\": \"are so much fun\" } }";
+
+ // Now initialize the server
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(0))
+ );
+
+ // Try to configure without filename. Should fail.
+ EXPECT_THROW(srv->init(""), BadValue);
+
+ // Try to configure it using empty file. Should fail.
+ writeFile(TEST_FILE, config_empty);
+ EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+ // Now try to load a config that does not have Dhcp6 component.
+ writeFile(TEST_FILE, config_v4);
+ EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+
+ // Now try to load a config with Dhcp6 full of nonsense.
+ writeFile(TEST_FILE, config_nonsense);
+ EXPECT_THROW(srv->init(TEST_FILE), BadValue);
+}
+
+// This test verifies that the DHCP server installs the timers for reclaiming
+// and flushing expired leases.
+TEST_F(JSONFileBackendTest, timers) {
+ // This is a basic configuration which enables timers for reclaiming
+ // expired leases and flushing them after 500 seconds since they expire.
+ // Both timers run at 1 second intervals.
+ string config =
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},"
+ "\"lease-database\": {"
+ " \"type\": \"memfile\","
+ " \"persist\": false"
+ "},"
+ "\"expired-leases-processing\": {"
+ " \"reclaim-timer-wait-time\": 1,"
+ " \"hold-reclaimed-time\": 500,"
+ " \"flush-reclaimed-timer-wait-time\": 1"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ],"
+ "\"preferred-lifetime\": 3000, "
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ writeFile(TEST_FILE, config);
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
+ ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+ // Create an expired lease. The lease is expired by 40 seconds ago
+ // (valid lifetime = 60, cltt = now - 100). The lease will be reclaimed
+ // but shouldn't be flushed in the database because the reclaimed are
+ // held in the database 500 seconds after reclamation, according to the
+ // current configuration.
+ DuidPtr duid_expired(new DUID(DUID::fromText("00:01:02:03:04:05:06").getDuid()));
+ Lease6Ptr lease_expired(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"),
+ duid_expired, 1, 50, 60, SubnetID(1)));
+ lease_expired->cltt_ = time(NULL) - 100;
+
+
+ // Create expired-reclaimed lease. The lease has expired 1000 - 60 seconds
+ // ago. It should be removed from the lease database when the "flush" timer
+ // goes off.
+ DuidPtr duid_reclaimed(new DUID(DUID::fromText("01:02:03:04:05:06:07").getDuid()));
+ Lease6Ptr lease_reclaimed(new Lease6(Lease::TYPE_NA, IOAddress("3000::2"),
+ duid_reclaimed, 1, 50, 60, SubnetID(1)));
+ lease_reclaimed->cltt_ = time(NULL) - 1000;
+ lease_reclaimed->state_ = Lease6::STATE_EXPIRED_RECLAIMED;
+
+ // Add leases to the database.
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
+ ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
+
+ // Make sure they have been added.
+ ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1")));
+ ASSERT_TRUE(lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2")));
+
+ // Poll the timers for a while to make sure that each of them is executed
+ // at least once.
+ ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 5000));
+
+ // Verify that the leases in the database have been processed as expected.
+
+ // First lease should be reclaimed, but not removed.
+ ASSERT_NO_THROW(
+ lease_expired = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::1"))
+ );
+ ASSERT_TRUE(lease_expired);
+ EXPECT_TRUE(lease_expired->stateExpiredReclaimed());
+
+ // Second lease should have been removed.
+ ASSERT_NO_THROW(
+ lease_reclaimed = lease_mgr.getLease6(Lease::TYPE_NA, IOAddress("3000::2"))
+ );
+ EXPECT_FALSE(lease_reclaimed);
+}
+
+// This test verifies that the DUID type can be selected.
+TEST_F(JSONFileBackendTest, serverId) {
+ string config =
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},"
+ "\"lease-database\": {"
+ " \"type\": \"memfile\","
+ " \"persist\": false"
+ "},"
+ "\"server-id\": {"
+ " \"type\": \"EN\","
+ " \"enterprise-id\": 1234"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ ],"
+ "\"preferred-lifetime\": 3000, "
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ writeFile(TEST_FILE, config);
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
+ ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+ // Check that DUID configuration is affected.
+ ConstCfgDUIDPtr duid_cfg = CfgMgr::instance().getCurrentCfg()->getCfgDUID();
+ ASSERT_TRUE(duid_cfg);
+ EXPECT_EQ(DUID::DUID_EN, duid_cfg->getType());
+ EXPECT_EQ(1234, duid_cfg->getEnterpriseId());
+}
+
+// This test verifies that the server uses default (Memfile) lease database
+// backend when no backend is explicitly specified in the configuration.
+TEST_F(JSONFileBackendTest, defaultLeaseDbBackend) {
+ // This is basic server configuration which excludes lease database
+ // backend specification. The default Memfile backend should be
+ // initialized in this case.
+ string config =
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ ],"
+ "\"preferred-lifetime\": 3000, "
+ "\"valid-lifetime\": 4000 }"
+ "}";
+ writeFile(TEST_FILE, config);
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new ControlledDhcpv6Srv(0)));
+ ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+ // The backend should have been created.
+ EXPECT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
+}
+
+// This test verifies that the timer triggering configuration updates
+// is invoked according to the configured value of the
+// config-fetch-wait-time.
+TEST_F(JSONFileBackendTest, configBackendTimer) {
+ testConfigBackendTimer(1);
+}
+
+// This test verifies that the timer for triggering configuration updates
+// is not invoked when the value of the config-fetch-wait-time is set
+// to 0.
+TEST_F(JSONFileBackendTest, configBackendTimerDisabled) {
+ testConfigBackendTimer(0);
+}
+
+// This test verifies that the server will gracefully handle exceptions
+// thrown from the CBControlDHCPv6::databaseConfigFetch, i.e. will
+// reschedule the timer.
+TEST_F(JSONFileBackendTest, configBackendTimerWithThrow) {
+ // The true value instructs the test to throw during the fetch.
+ testConfigBackendTimer(1, true);
+}
+
+// This test verifies that the server will be updated by the
+// config-backend-pull command.
+TEST_F(JSONFileBackendTest, configBackendPullCommand) {
+ testConfigBackendTimer(0, false, true);
+}
+
+// This test verifies that the server will be updated by the
+// config-backend-pull command even when updates fail.
+TEST_F(JSONFileBackendTest, configBackendPullCommandWithThrow) {
+ testConfigBackendTimer(0, true, true);
+}
+
+// This test verifies that the server will be updated by the
+// config-backend-pull command and the timer rescheduled.
+TEST_F(JSONFileBackendTest, configBackendPullCommandWithTimer) {
+ testConfigBackendTimer(1, false, true);
+}
+
+// Starting tests which require MySQL backend availability. Those tests
+// will not be executed if Kea has been compiled without the
+// --with-mysql.
+#ifdef HAVE_MYSQL
+
+/// @brief Test fixture class for the tests utilizing MySQL database
+/// backend.
+class JSONFileBackendMySQLTest : public JSONFileBackendTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Recreates MySQL schema for a test.
+ JSONFileBackendMySQLTest() : JSONFileBackendTest() {
+ // Ensure we have the proper schema with no transient data.
+ createMySQLSchema();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Destroys MySQL schema.
+ virtual ~JSONFileBackendMySQLTest() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema.
+ destroyMySQLSchema();
+ }
+
+ /// @brief Creates server configuration with specified backend type.
+ ///
+ /// @param backend Backend type or empty string to indicate that the
+ /// backend configuration should not be placed in the resulting
+ /// JSON configuration.
+ ///
+ /// @return Server configuration.
+ std::string createConfiguration(const std::string& backend) const;
+
+ /// @brief Test reconfiguration with a backend change.
+ ///
+ /// If any of the parameters is an empty string it indicates that the
+ /// created configuration should exclude backend configuration.
+ ///
+ /// @param backend_first Type of a backend to be used initially.
+ /// @param backend_second Type of a backend to be used after
+ /// reconfiguration.
+ void testBackendReconfiguration(const std::string& backend_first,
+ const std::string& backend_second);
+};
+
+std::string
+JSONFileBackendMySQLTest::createConfiguration(const std::string& backend) const {
+ // This is basic server configuration which excludes lease database
+ // backend specification. The default Memfile backend should be
+ // initialized in this case.
+ std::ostringstream config;
+ config <<
+ "{ \"Dhcp6\": {"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},";
+
+ // For non-empty lease backend type we have to add a backend configuration
+ // section.
+ if (!backend.empty()) {
+ config <<
+ "\"lease-database\": {"
+ " \"type\": \"" << backend << "\"";
+
+ // SQL backends require database credentials.
+ if (backend != "memfile") {
+ config <<
+ ","
+ " \"name\": \"keatest\","
+ " \"user\": \"keatest\","
+ " \"password\": \"keatest\"";
+ }
+ config << "},";
+ }
+
+ // Append the rest of the configuration.
+ config <<
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ ],"
+ "\"preferred-lifetime\": 3000, "
+ "\"valid-lifetime\": 4000 }"
+ "}";
+
+ return (config.str());
+}
+
+void
+JSONFileBackendMySQLTest::
+testBackendReconfiguration(const std::string& backend_first,
+ const std::string& backend_second) {
+ writeFile(TEST_FILE, createConfiguration(backend_first));
+
+ // Create an instance of the server and initialize it.
+ boost::scoped_ptr<NakedControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv6Srv()));
+ srv->setConfigFile(TEST_FILE);
+ ASSERT_NO_THROW(srv->init(TEST_FILE));
+
+ // The backend should have been created and its type should be
+ // correct.
+ ASSERT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
+ EXPECT_EQ(backend_first.empty() ? "memfile" : backend_first,
+ LeaseMgrFactory::instance().getType());
+
+ // New configuration modifies the lease database backend type.
+ writeFile(TEST_FILE, createConfiguration(backend_second));
+
+ // Explicitly calling signal handler for SIGHUP to trigger server
+ // reconfiguration.
+ raise(SIGHUP);
+
+ // Polling once to be sure that the signal handle has been called.
+ srv->getIOService()->poll();
+
+ // The backend should have been created and its type should be
+ // correct.
+ ASSERT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
+ EXPECT_EQ(backend_second.empty() ? "memfile" : backend_second,
+ LeaseMgrFactory::instance().getType());
+}
+
+
+// This test verifies that backend specification can be added on
+// server reconfiguration.
+TEST_F(JSONFileBackendMySQLTest, reconfigureBackendUndefinedToMySQL) {
+ testBackendReconfiguration("", "mysql");
+}
+
+// This test verifies that when backend specification is removed the
+// default backend is used.
+TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMySQLToUndefined) {
+ testBackendReconfiguration("mysql", "");
+}
+
+// This test verifies that backend type can be changed from Memfile
+// to MySQL.
+TEST_F(JSONFileBackendMySQLTest, reconfigureBackendMemfileToMySQL) {
+ testBackendReconfiguration("memfile", "mysql");
+}
+
+#endif
+
+} // End of anonymous namespace
diff --git a/src/bin/dhcp6/tests/marker_file.cc b/src/bin/dhcp6/tests/marker_file.cc
new file mode 100644
index 0000000..066e1f7
--- /dev/null
+++ b/src/bin/dhcp6/tests/marker_file.cc
@@ -0,0 +1,60 @@
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include "marker_file.h"
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace std;
+
+// Check the marker file.
+
+bool
+checkMarkerFile(const char* name, const char* expected) {
+ // Open the file for input
+ fstream file(name, fstream::in);
+
+ // Is it open?
+ if (!file.is_open()) {
+ ADD_FAILURE() << "Unable to open " << name << ". It was expected "
+ << "to be present and to contain the string '"
+ << expected << "'";
+ return (false);
+ }
+
+ // OK, is open, so read the data and see what we have. Compare it
+ // against what is expected.
+ string content;
+ getline(file, content);
+
+ string expected_str(expected);
+ EXPECT_EQ(expected_str, content) << "Marker file " << name
+ << "did not contain the expected data";
+ file.close();
+
+ return (expected_str == content);
+}
+
+// Check if the marker file exists - this is a wrapper for "access(2)" and
+// really tests if the file exists and is accessible
+
+bool
+checkMarkerFileExists(const char* name) {
+ return (access(name, F_OK) == 0);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp6/tests/marker_file.h.in b/src/bin/dhcp6/tests/marker_file.h.in
new file mode 100644
index 0000000..06f60d8
--- /dev/null
+++ b/src/bin/dhcp6/tests/marker_file.h.in
@@ -0,0 +1,62 @@
+// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called
+
+namespace {
+const char* const LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt";
+const char* const UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt";
+const char* const SRV_CONFIG_MARKER_FILE = "@abs_builddir@/srv_config_marker_file.txt";
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Check marker file
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Marker files are used by the load/unload functions in the hooks
+/// libraries in these tests to signal whether they have been loaded or
+/// unloaded. The file (if present) contains a single line holding
+/// a set of characters.
+///
+/// This convenience function checks the file to see if the characters
+/// are those expected.
+///
+/// @param name Name of the marker file.
+/// @param expected Characters expected. If a marker file is present,
+/// it is expected to contain characters.
+///
+/// @return true if all tests pass, false if not (in which case a failure
+/// will have been logged).
+bool
+checkMarkerFile(const char* name, const char* expected);
+
+/// @brief Check marker file exists
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Checks that the specified file does NOT exist.
+///
+/// @param name Name of the marker file.
+///
+/// @return true if file exists, false if not.
+bool
+checkMarkerFileExists(const char* name);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // MARKER_FILE_H
+
diff --git a/src/bin/dhcp6/tests/parser_unittest.cc b/src/bin/dhcp6/tests/parser_unittest.cc
new file mode 100644
index 0000000..4ea7a28
--- /dev/null
+++ b/src/bin/dhcp6/tests/parser_unittest.cc
@@ -0,0 +1,980 @@
+// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcp6/parser_context.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <testutils/io_utils.h>
+#include <testutils/log_utils.h>
+#include <testutils/test_to_element.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <set>
+
+#include <boost/algorithm/string.hpp>
+
+using namespace isc::data;
+using namespace std;
+using namespace isc::test;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief compares two JSON trees
+///
+/// If differences are discovered, gtest failure is reported (using EXPECT_EQ)
+///
+/// @param a first to be compared
+/// @param b second to be compared
+void compareJSON(ConstElementPtr a, ConstElementPtr b) {
+ ASSERT_TRUE(a);
+ ASSERT_TRUE(b);
+ EXPECT_EQ(a->str(), b->str())
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << generateDiff(prettyPrint(a), prettyPrint(b)) << "\n"
+#endif
+ ;
+}
+
+/// @brief Tests if the input string can be parsed with specific parser
+///
+/// The input text will be passed to bison parser of specified type.
+/// Then the same input text is passed to legacy JSON parser and outputs
+/// from both parsers are compared. The legacy comparison can be disabled,
+/// if the feature tested is not supported by the old parser (e.g.
+/// new comment styles)
+///
+/// @param txt text to be compared
+/// @param parser_type bison parser type to be instantiated
+/// @param compare whether to compare the output with legacy JSON parser
+void testParser(const std::string& txt, Parser6Context::ParserType parser_type,
+ bool compare = true) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ ConstElementPtr test_json;
+ ASSERT_NO_THROW({
+ try {
+ Parser6Context ctx;
+ test_json = ctx.parseString(txt, parser_type);
+ } catch (const std::exception &e) {
+ cout << "EXCEPTION: " << e.what() << endl;
+ throw;
+ }
+
+ });
+
+ if (!compare) {
+ return;
+ }
+
+ // Now compare if both representations are the same.
+ ElementPtr reference_json;
+ ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+ compareJSON(reference_json, test_json);
+}
+
+TEST(ParserTest, mapInMap) {
+ string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listInList) {
+ string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+ "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedMaps) {
+ string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedLists) {
+ string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, listsInMaps) {
+ string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelgeuse\" ], "
+ "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, mapsInLists) {
+ string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
+ " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, types) {
+ string txt = "{ \"string\": \"foo\","
+ "\"integer\": 42,"
+ "\"boolean\": true,"
+ "\"map\": { \"foo\": \"bar\" },"
+ "\"list\": [ 1, 2, 3 ],"
+ "\"null\": null }";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordJSON) {
+ string txt = "{ \"name\": \"user\","
+ "\"type\": \"password\","
+ "\"user\": \"name\","
+ "\"password\": \"type\" }";
+ testParser(txt, Parser6Context::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordDhcp6) {
+ string txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"type\", \"htype\" ] },\n"
+ "\"preferred-lifetime\": 3000,\n"
+ "\"rebind-timer\": 2000, \n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"test\" } ],\n"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser6Context::PARSER_DHCP6);
+}
+
+// Tests if bash (#) comments are supported. That's the only comment type that
+// was supported by the old parser.
+TEST(ParserTest, bashComments) {
+ string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000,\n"
+ "# this is a comment\n"
+ "\"rebind-timer\": 2000, \n"
+ "# lots of comments here\n"
+ "# and here\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser6Context::PARSER_DHCP6);
+}
+
+// Tests if C++ (//) comments can start anywhere, not just in the first line.
+TEST(ParserTest, cppComments) {
+ string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000, // this is a comment \n"
+ "\"rebind-timer\": 2000, // everything after // is ignored\n"
+ "\"renew-timer\": 1000, // this will be ignored, too\n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser6Context::PARSER_DHCP6, false);
+}
+
+// Tests if bash (#) comments can start anywhere, not just in the first line.
+TEST(ParserTest, bashCommentsInline) {
+ string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000, # this is a comment \n"
+ "\"rebind-timer\": 2000, # everything after # is ignored\n"
+ "\"renew-timer\": 1000, # this will be ignored, too\n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser6Context::PARSER_DHCP6, false);
+}
+
+// Tests if multi-line C style comments are handled correctly.
+TEST(ParserTest, multilineComments) {
+ string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
+ "that\n can \n span \n multiple \n lines */ \n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser6Context::PARSER_DHCP6, false);
+}
+
+// Tests if embedded comments are handled correctly.
+TEST(ParserTest, embbededComments) {
+ string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"comment\": \"a comment\",\n"
+ "\"preferred-lifetime\": 3000,\n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"user-context\": { \"comment\": \"indirect\" },"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"user-context\": { \"compatible\": true },"
+ "\"valid-lifetime\": 4000 } }";
+ testParser(txt, Parser6Context::PARSER_DHCP6, false);
+}
+
+/// @brief Loads specified example config file
+///
+/// This test loads specified example file twice: first, using the legacy
+/// JSON file and then second time using bison parser. Two created Element
+/// trees are then compared. The input is decommented before it is passed
+/// to legacy parser (as legacy support for comments is very limited).
+///
+/// @param fname name of the file to be loaded
+void testFile(const std::string& fname) {
+ ElementPtr json;
+ ElementPtr reference_json;
+ ConstElementPtr test_json;
+
+ string decommented = decommentJSONfile(fname);
+
+ cout << "Parsing file " << fname << "(" << decommented << ")" << endl;
+
+ EXPECT_NO_THROW_LOG(json = Element::fromJSONFile(decommented, true));
+ reference_json = moveComments(json);
+
+ // remove the temporary file
+ EXPECT_NO_THROW(::remove(decommented.c_str()));
+
+ EXPECT_NO_THROW(
+ try {
+ Parser6Context ctx;
+ test_json = ctx.parseFile(fname, Parser6Context::PARSER_DHCP6);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+
+ ASSERT_TRUE(reference_json);
+ ASSERT_TRUE(test_json);
+
+ compareJSON(reference_json, test_json);
+}
+
+// This test loads all available existing files. Each config is loaded
+// twice: first with the existing Element::fromJSONFile() and then
+// the second time with Parser6. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+ vector<string> configs;
+ configs.push_back("advanced.json");
+ configs.push_back("all-keys.json");
+ configs.push_back("all-options.json");
+ configs.push_back("backends.json");
+ configs.push_back("classify.json");
+ configs.push_back("classify2.json");
+ configs.push_back("comments.json");
+ configs.push_back("config-backend.json");
+ configs.push_back("dhcpv4-over-dhcpv6.json");
+ configs.push_back("dnr.json");
+ configs.push_back("duid.json");
+ configs.push_back("global-reservations.json");
+ configs.push_back("ha-hot-standby-server1-with-tls.json");
+ configs.push_back("ha-hot-standby-server2.json");
+ configs.push_back("hooks.json");
+ configs.push_back("iPXE.json");
+ configs.push_back("leases-expiration.json");
+ configs.push_back("multiple-options.json");
+ configs.push_back("mysql-reservations.json");
+ configs.push_back("pgsql-reservations.json");
+ configs.push_back("reservations.json");
+ configs.push_back("several-subnets.json");
+ configs.push_back("shared-network.json");
+ configs.push_back("simple.json");
+ configs.push_back("softwire46.json");
+ configs.push_back("stateless.json");
+ configs.push_back("tee-times.json");
+ configs.push_back("with-ddns.json");
+
+ for (int i = 0; i<configs.size(); i++) {
+ testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
+ }
+}
+
+// This test loads the all-keys.json file and checks global parameters.
+TEST(ParserTest, globalParameters) {
+ ConstElementPtr json;
+ Parser6Context ctx;
+ string fname = string(CFG_EXAMPLES) + "/" + "all-keys.json";
+ EXPECT_NO_THROW(json = ctx.parseFile(fname, Parser6Context::PARSER_DHCP6));
+ EXPECT_NO_THROW(json = json->get("Dhcp6"));
+ SimpleParser6 parser;
+ EXPECT_NO_THROW(parser.checkKeywords(parser.GLOBAL6_PARAMETERS, json));
+}
+
+/// @brief Tests error conditions in Dhcp6Parser
+///
+/// @param txt text to be parsed
+/// @param parser_type type of the parser to be used in the test
+/// @param msg expected content of the exception
+void testError(const std::string& txt,
+ Parser6Context::ParserType parser_type,
+ const std::string& msg) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ try {
+ Parser6Context ctx;
+ ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+ FAIL() << "Expected Dhcp6ParseError but nothing was raised (expected: "
+ << msg << ")";
+ }
+ catch (const Dhcp6ParseError& ex) {
+ EXPECT_EQ(msg, ex.what());
+ }
+ catch (...) {
+ FAIL() << "Expected Dhcp6ParseError but something else was raised";
+ }
+}
+
+// Verify that error conditions are handled correctly.
+TEST(ParserTest, errors) {
+ // no input
+ testError("", Parser6Context::PARSER_JSON,
+ "<string>:1.1: syntax error, unexpected end of file");
+ testError(" ", Parser6Context::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\n", Parser6Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("\t", Parser6Context::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\r", Parser6Context::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+
+ // comments
+ testError("# nothing\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError(" #\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("// nothing\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* nothing */\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:3.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:4.1: syntax error, unexpected end of file");
+ testError("/* nothing\n",
+ Parser6Context::PARSER_JSON,
+ "Comment not closed. (/* in line 1");
+ testError("\n\n\n/* nothing\n",
+ Parser6Context::PARSER_JSON,
+ "Comment not closed. (/* in line 4");
+ testError("{ /* */*/ }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.3-8: Invalid character: *");
+ testError("{ /* // *// }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.3-11: Invalid character: /");
+ testError("{ /* // */// }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting }");
+
+ // includes
+ testError("<?\n",
+ Parser6Context::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include\n",
+ Parser6Context::PARSER_JSON,
+ "Directive not closed.");
+ string file = string(CFG_EXAMPLES) + "/" + "stateless.json";
+ testError("<?include \"" + file + "\"\n",
+ Parser6Context::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include \"/foo/bar\" ?>/n",
+ Parser6Context::PARSER_JSON,
+ "Can't open include file /foo/bar");
+
+ // JSON keywords
+ testError("{ \"foo\": True }",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.10-13: JSON true reserved keyword is lower case only");
+ testError("{ \"foo\": False }",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.10-14: JSON false reserved keyword is lower case only");
+ testError("{ \"foo\": NULL }",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.10-13: JSON null reserved keyword is lower case only");
+ testError("{ \"foo\": Tru }",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.10: Invalid character: T");
+ testError("{ \"foo\": nul }",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.10: Invalid character: n");
+
+ // numbers
+ testError("123",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1-3: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-456",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1-4: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-0001",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1-5: syntax error, unexpected integer, "
+ "expecting {");
+ testError("1234567890123456789012345678901234567890",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-40: Failed to convert "
+ "1234567890123456789012345678901234567890"
+ " to an integer.");
+ testError("-3.14e+0",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1-8: syntax error, unexpected floating point, "
+ "expecting {");
+ testError("1e50000",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-7: Failed to convert 1e50000 "
+ "to a floating point.");
+
+ // strings
+ testError("\"aabb\"",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1-6: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("{ \"aabb\"err",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.9: Invalid character: e");
+ testError("{ err\"aabb\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.3: Invalid character: e");
+ testError("\"a\n\tb\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-6 (near 2): Invalid control in \"a\n\tb\"");
+ testError("\"a\n\\u12\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-8 (near 2): Invalid control in \"a\n\\u12\"");
+ testError("\"a\\n\\tb\"",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1-8: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("\"a\\x01b\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Bad escape in \"a\\x01b\"");
+ testError("\"a\\u0162\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-9 (near 4): Unsupported unicode escape "
+ "in \"a\\u0162\"");
+ testError("\"a\\u062z\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-9 (near 3): Bad escape in \"a\\u062z\"");
+ testError("\"abc\\\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-6 (near 6): Overflow escape in \"abc\\\"");
+ testError("\"a\\u006\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Overflow unicode escape "
+ "in \"a\\u006\"");
+ testError("\"\\u\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-4 (near 2): Overflow unicode escape in \"\\u\"");
+ testError("\"\\u\x02\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\x02\"");
+ testError("\"\\u\\\"foo\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\\\"...");
+ testError("\"\x02\\u\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1-5 (near 1): Invalid control in \"\x02\\u\"");
+
+ // from data_unittest.c
+ testError("\\a",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\\"\\\"",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+
+ // want a map
+ testError("[]\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("[]\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("{ 123 }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting }");
+ testError("{ 123 }\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting Dhcp6");
+ testError("{ \"foo\" }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.9: syntax error, unexpected }, "
+ "expecting :");
+ testError("{ \"foo\" }\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp6");
+ testError("{ \"foo\":null }\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp6");
+ testError("{ \"Logging\":null }\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.3-11: syntax error, unexpected constant string, "
+ "expecting Dhcp6");
+ testError("{ \"Dhcp6\" }\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.11: syntax error, unexpected }, "
+ "expecting :");
+ testError("{}{}\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected {, "
+ "expecting end of file");
+
+ // duplicate in map
+ testError("{ \"foo\": 1, \"foo\": true }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1:13: duplicate foo entries in "
+ "JSON map (previous at <string>:1:10)");
+
+ // bad commas
+ testError("{ , }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+ testError("{ , \"foo\":true }\n",
+ Parser6Context::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+
+ // bad type
+ testError("{ \"Dhcp6\":{\n"
+ " \"preferred-lifetime\":false }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:2.24-28: syntax error, unexpected boolean, "
+ "expecting integer");
+
+ // unknown keyword
+ testError("{ \"Dhcp6\":{\n"
+ " \"preferred_lifetime\":600 }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:2.2-21: got unexpected keyword "
+ "\"preferred_lifetime\" in Dhcp6 map.");
+
+ // missing parameter
+ testError("{ \"name\": \"foo\",\n"
+ " \"code\": 123 }\n",
+ Parser6Context::PARSER_OPTION_DEF,
+ "missing parameter 'type' (<string>:1:1) "
+ "[option-def map between <string>:1:1 and <string>:2:15]");
+
+ // user context and embedded comments
+ testError("{ \"Dhcp6\":{\n"
+ " \"comment\": true,\n"
+ " \"preferred-lifetime\": 600 }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:2.14-17: syntax error, unexpected boolean, "
+ "expecting constant string");
+
+ testError("{ \"Dhcp6\":{\n"
+ " \"user-context\": \"a comment\",\n"
+ " \"preferred-lifetime\": 600 }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:2.19-29: syntax error, unexpected constant string, "
+ "expecting {");
+
+ testError("{ \"Dhcp6\":{\n"
+ " \"comment\": \"a comment\",\n"
+ " \"comment\": \"another one\",\n"
+ " \"preferred-lifetime\": 600 }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:3)");
+
+ testError("{ \"Dhcp6\":{\n"
+ " \"user-context\": { \"version\": 1 },\n"
+ " \"user-context\": { \"one\": \"only\" },\n"
+ " \"preferred-lifetime\": 600 }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:3.3-16: duplicate user-context entries "
+ "(previous at <string>:2:19)");
+
+ testError("{ \"Dhcp6\":{\n"
+ " \"user-context\": { \"comment\": \"indirect\" },\n"
+ " \"comment\": \"a comment\",\n"
+ " \"preferred-lifetime\": 600 }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:19)");
+
+ // duplicate Dhcp6 entries
+ testError("{ \"Dhcp6\":{\n"
+ " \"comment\": \"first\" },\n"
+ " \"Dhcp6\":{\n"
+ " \"comment\": \"second\" }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:3.3-9: syntax error, unexpected Dhcp6, expecting \",\" or }");
+
+ // duplicate of not string entries
+ testError("{ \"Dhcp6\":{\n"
+ " \"subnet6\": [],\n"
+ " \"subnet6\": [] }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:3:2: duplicate subnet6 entries in "
+ "Dhcp6 map (previous at <string>:2:2)");
+
+ // duplicate of string entries
+ testError("{ \"data\": \"foo\",\n"
+ " \"data\": \"bar\" }\n",
+ Parser6Context::PARSER_OPTION_DATA,
+ "<string>:2:2: duplicate data entries in "
+ "option-data map (previous at <string>:1:11)");
+}
+
+// Check unicode escapes
+TEST(ParserTest, unicodeEscapes) {
+ ConstElementPtr result;
+ string json;
+
+ // check we can reread output
+ for (char c = -128; c < 127; ++c) {
+ string ins(" ");
+ ins[1] = c;
+ ConstElementPtr e(new StringElement(ins));
+ json = e->str();
+ ASSERT_NO_THROW(
+ try {
+ Parser6Context ctx;
+ result = ctx.parseString(json, Parser6Context::PARSER_JSON);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+ ASSERT_EQ(Element::string, result->getType());
+ EXPECT_EQ(ins, result->stringValue());
+ }
+}
+
+// This test checks that all representations of a slash is recognized properly.
+TEST(ParserTest, unicodeSlash) {
+ // check the 4 possible encodings of solidus '/'
+ ConstElementPtr result;
+ string json = "\"/\\/\\u002f\\u002F\"";
+ ASSERT_NO_THROW(
+ try {
+ Parser6Context ctx;
+ result = ctx.parseString(json, Parser6Context::PARSER_JSON);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+ ASSERT_EQ(Element::string, result->getType());
+ EXPECT_EQ("////", result->stringValue());
+}
+
+/// @brief Load a file into a JSON element.
+///
+/// @param fname The name of the file to load.
+/// @param list The JSON element list to add the parsing result to.
+void loadFile(const string& fname, ElementPtr list) {
+ Parser6Context ctx;
+ ElementPtr json;
+ EXPECT_NO_THROW(json = ctx.parseFile(fname, Parser6Context::PARSER_DHCP6));
+ ASSERT_TRUE(json);
+ list->add(json);
+}
+
+// This test checks that all map entries are in the example files.
+TEST(ParserTest, mapEntries) {
+ // Type of keyword set.
+ typedef set<string> KeywordSet;
+
+ // Get keywords from the syntax file (dhcp6_parser.yy).
+ ifstream syntax_file(SYNTAX_FILE);
+ EXPECT_TRUE(syntax_file.is_open());
+ string line;
+ KeywordSet syntax_keys = { "user-context" };
+ // Code setting the map entry.
+ const string pattern = "ctx.stack_.back()->set(\"";
+ while (getline(syntax_file, line)) {
+ // Skip comments.
+ size_t comment = line.find("//");
+ if (comment <= pattern.size()) {
+ continue;
+ }
+ if (comment != string::npos) {
+ line.resize(comment);
+ }
+ // Search for the code pattern.
+ size_t key_begin = line.find(pattern);
+ if (key_begin == string::npos) {
+ continue;
+ }
+ // Extract keywords.
+ line = line.substr(key_begin + pattern.size());
+ size_t key_end = line.find_first_of('"');
+ EXPECT_NE(string::npos, key_end);
+ string keyword = line.substr(0, key_end);
+ // Ignore result when adding the keyword to the syntax keyword set.
+ static_cast<void>(syntax_keys.insert(keyword));
+ }
+ syntax_file.close();
+
+ // Get keywords from the example files.
+ string sample_dir(CFG_EXAMPLES);
+ sample_dir += "/";
+ ElementPtr sample_json = Element::createList();
+ loadFile(sample_dir + "advanced.json", sample_json);
+ loadFile(sample_dir + "all-keys.json", sample_json);
+ loadFile(sample_dir + "duid.json", sample_json);
+ loadFile(sample_dir + "reservations.json", sample_json);
+ loadFile(sample_dir + "all-keys-netconf.json", sample_json);
+ KeywordSet sample_keys = {
+ "hosts-database",
+ "reservation-mode"
+ };
+ // Recursively extract keywords.
+ static void (*extract)(ConstElementPtr, KeywordSet&) =
+ [] (ConstElementPtr json, KeywordSet& set) {
+ if (json->getType() == Element::list) {
+ // Handle lists.
+ for (auto elem : json->listValue()) {
+ extract(elem, set);
+ }
+ } else if (json->getType() == Element::map) {
+ // Handle maps.
+ for (auto elem : json->mapValue()) {
+ static_cast<void>(set.insert(elem.first));
+ // Skip entries with free content.
+ if ((elem.first != "user-context") &&
+ (elem.first != "parameters")) {
+ extract(elem.second, set);
+ }
+ }
+ }
+ };
+ extract(sample_json, sample_keys);
+
+ // Compare.
+ auto print_keys = [](const KeywordSet& keys) {
+ string s = "{";
+ bool first = true;
+ for (auto key : keys) {
+ if (first) {
+ first = false;
+ s += " ";
+ } else {
+ s += ", ";
+ }
+ s += "\"" + key + "\"";
+ }
+ return (s + " }");
+ };
+ EXPECT_EQ(syntax_keys, sample_keys)
+ << "syntax has: " << print_keys(syntax_keys) << endl
+ << "sample has: " << print_keys(sample_keys) << endl;
+}
+
+/// @brief Tests a duplicate entry.
+///
+/// The entry was duplicated by adding a new <name>DDDD entry.
+/// An error is expected, usually it is a duplicate but there are
+/// a few syntax errors when the syntax allows only one parameter.
+///
+/// @param json the JSON configuration with the duplicate entry.
+void testDuplicate(ConstElementPtr json) {
+ string config = json->str();
+ size_t where = config.find("DDDD");
+ ASSERT_NE(string::npos, where);
+ string before = config.substr(0, where);
+ string after = config.substr(where + 4, string::npos);
+ Parser6Context ctx;
+ EXPECT_THROW(ctx.parseString(before + after,
+ Parser6Context::PARSER_DHCP6),
+ Dhcp6ParseError) << "config: " << config;
+}
+
+// This test checks that duplicate entries make parsing to fail.
+TEST(ParserTest, duplicateMapEntries) {
+ // Get the config to work with from the all keys file.
+ string sample_fname(CFG_EXAMPLES);
+ sample_fname += "/all-keys.json";
+ Parser6Context ctx;
+ ElementPtr sample_json;
+ EXPECT_NO_THROW(sample_json =
+ ctx.parseFile(sample_fname, Parser6Context::PARSER_DHCP6));
+ ASSERT_TRUE(sample_json);
+
+ // Recursively check duplicates.
+ static void (*test)(ElementPtr, ElementPtr, size_t&) =
+ [] (ElementPtr config, ElementPtr json, size_t& cnt) {
+ if (json->getType() == Element::list) {
+ // Handle lists.
+ for (auto elem : json->listValue()) {
+ test(config, elem, cnt);
+ }
+ } else if (json->getType() == Element::map) {
+ // Handle maps.
+ for (auto elem : json->mapValue()) {
+ // Skip entries with free content.
+ if ((elem.first == "user-context") ||
+ (elem.first == "parameters")) {
+ continue;
+ }
+
+ // Perform tests.
+ string dup = elem.first + "DDDD";
+ json->set(dup, elem.second);
+ testDuplicate(config);
+ json->remove(dup);
+ ++cnt;
+
+ // Recursive call.
+ ElementPtr mutable_json =
+ boost::const_pointer_cast<Element>(elem.second);
+ ASSERT_TRUE(mutable_json);
+ test(config, mutable_json, cnt);
+ }
+ }
+ };
+ size_t cnt = 0;
+ test(sample_json, sample_json, cnt);
+ cout << "checked " << cnt << " duplicated map entries\n";
+}
+
+/// @brief Test fixture for trailing commas.
+class TrailingCommasTest : public isc::dhcp::test::LogContentTest {
+public:
+ /// @brief Add a log entry.
+ ///
+ /// @param loc Location of the trailing comma.
+ void addLog(const string& loc) {
+ string log = "DHCP6_CONFIG_SYNTAX_WARNING configuration syntax ";
+ log += "warning: " + loc;
+ log += ": Extraneous comma. ";
+ log += "A piece of configuration may have been omitted.";
+ addString(log);
+ }
+};
+
+// Test that trailing commas are allowed.
+TEST_F(TrailingCommasTest, tests) {
+ string txt(R"({
+ "Dhcp6": {
+ "control-socket": {
+ "socket-name": "/tmp/kea-dhcp6-ctrl.sock",
+ "socket-type": "unix",
+ },
+ "hooks-libraries": [
+ {
+ "library": "/usr/local/lib/kea/hooks/libdhcp_dummy.so",
+ },
+ ],
+ "interfaces-config": {
+ "interfaces": [
+ "eth0",
+ ],
+ },
+ "lease-database": {
+ "name": "/tmp/kea-dhcp6.csv",
+ "persist": true,
+ "type": "memfile",
+ },
+ "loggers": [
+ {
+ "debuglevel": 99,
+ "name": "kea-dhcp6",
+ "output_options": [
+ {
+ "output": "stdout",
+ },
+ ],
+ "severity": "DEBUG",
+ },
+ ],
+ "multi-threading": {
+ "enable-multi-threading": false,
+ "packet-queue-size": 0,
+ "thread-pool-size": 0
+ },
+ "subnet6": [
+ {
+ "pools": [
+ {
+ "pool": "2001:db8:1::/64",
+ },
+ ],
+ "subnet": "2001:db8:1::/64",
+ "id": 1,
+ },
+ ],
+ },
+})");
+ testParser(txt, Parser6Context::PARSER_DHCP6, false);
+
+ addLog("<string>:5.28");
+ addLog("<string>:9.63");
+ addLog("<string>:10.8");
+ addLog("<string>:14.15");
+ addLog("<string>:15.8");
+ addLog("<string>:20.24");
+ addLog("<string>:28.31");
+ addLog("<string>:29.12");
+ addLog("<string>:31.28");
+ addLog("<string>:32.8");
+ addLog("<string>:43.38");
+ addLog("<string>:44.12");
+ addLog("<string>:47.16");
+ addLog("<string>:48.8");
+ addLog("<string>:49.6");
+ addLog("<string>:50.4");
+ EXPECT_TRUE(checkFile());
+
+ // Test with many consecutive commas.
+ boost::replace_all(txt, ",", ",,,,");
+ testParser(txt, Parser6Context::PARSER_DHCP6, false);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp6/tests/rebind_unittest.cc b/src/bin/dhcp6/tests/rebind_unittest.cc
new file mode 100644
index 0000000..48801e6
--- /dev/null
+++ b/src/bin/dhcp6/tests/rebind_unittest.cc
@@ -0,0 +1,1169 @@
+// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+#include <dhcpsrv/utils.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Rebind tests.
+///
+/// - Configuration 0:
+/// - only addresses (no prefixes)
+/// - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::/64
+/// - 1 subnet for eth0 and 1 subnet for eth1
+///
+/// - Configuration 1:
+/// - similar to Configuration 0 but different subnets
+/// - pools configured: 2001:db8:3::/64 and 2001:db8:4::/64
+///
+/// - Configuration 2:
+/// - similar to Configuration 0 and Configuration 1
+/// - pools configured: 3000:1::/64 and 3000:2::/64
+/// - this specific configuration is used by tests using relays
+///
+/// - Configuration 3:
+/// - similar to Configuration 2 but with different subnets
+/// - pools configured: 3000:3::/64 and 3000:4::/64
+/// - this specific configuration is used by tests using relays
+///
+/// - Configuration 4:
+/// - only prefixes (no addresses)
+/// - 2 subnets: 2001:db8:1::/40 and 2001:db8:2::/40
+/// - 2 prefix pools: 3000::/72 and 2001:db8:2::/72
+/// - 1 subnet for eth0 and 1 subnet for eth1
+/// - this specific configuration is used by tests which don't use relays
+///
+/// - Configuration 5:
+/// - similar to Configuration 4 but with different subnets
+/// - 2 subnets: 2001:db8:3::/40 and 2001:db8:4::/40
+/// - 2 prefix pools: 2001:db8:3::/72 and 2001:db8:4::/72
+/// - delegated length /80
+/// - this specific configuration is used by tests which don't use relays
+///
+/// - Configuration 6:
+/// - addresses and prefixes
+/// - address pool: 2001:db8:1::/64
+/// - prefix pool: 3000::/72
+///
+/// - Configuration 7:
+/// - only addresses (no prefixes)
+/// - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::/64
+/// - 1 subnet for eth0 and 1 subnet for eth1
+/// - DOCSIS vendor config file sub-option
+///
+/// - Configuration 8:
+/// - single subnet 3000::/32,
+/// - two options specified in the subnet scope,
+/// - one option specified at the global scope,
+/// - two address pools: 3000::10-3000::20, 3000::40-3000::50,
+/// - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64,
+/// - an option with unique value specified for each pool, so as it is
+/// possible to test that pool specific options can be assigned.
+///
+const char* REBIND_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:3::/64\" } ],"
+ " \"subnet\": \"2001:db8:3::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:4::/64\" } ],"
+ " \"subnet\": \"2001:db8:4::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"3000:1::/64\" } ],"
+ " \"subnet\": \"3000:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"3000:2::/64\" } ],"
+ " \"subnet\": \"3000:2::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"3000:3::/64\" } ],"
+ " \"subnet\": \"3000:3::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"3000:4::/64\" } ],"
+ " \"subnet\": \"3000:4::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/40\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:2::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " } ],"
+ " \"subnet\": \"2001:db8:2::/40\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 5
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:3:01::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " } ],"
+ " \"subnet\": \"2001:db8:3::/40\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:4:01::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " } ],"
+ " \"subnet\": \"2001:db8:4::/40\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 6
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 7
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"normal_erouter_v6.cm\""
+ "}],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::/64\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth1\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 8
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::234\""
+ "},"
+ "{"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::1\""
+ "} ],"
+ "\"subnet6\": [ { "
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::567\""
+ " },"
+ " {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::1\""
+ " } ],"
+ " \"pools\": [ { "
+ " \"pool\": \"3000::10 - 3000::20\","
+ " \"option-data\": [ {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"pool\": \"3000::40 - 3000::50\","
+ " \"option-data\": [ {"
+ " \"name\": \"nisp-servers\","
+ " \"data\": \"3000:2::3\""
+ " } ]"
+ " } ],"
+ " \"pd-pools\": [ { "
+ " \"prefix\": \"2001:db8:3::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::678\""
+ " } ]"
+ " },"
+ " {"
+ " \"prefix\": \"2001:db8:4::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"3000:1::789\""
+ " } ]"
+ " } ],"
+ " \"id\": 1, "
+ " \"subnet\": \"3000::/32\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000"
+ "}"
+};
+
+/// @brief Test fixture class for testing Rebind.
+class RebindTest : public Dhcpv6MessageTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ RebindTest()
+ : Dhcpv6MessageTest() {
+ }
+};
+
+// Test that client-id is mandatory and server-id forbidden for Rebind messages
+TEST_F(RebindTest, sanityCheck) {
+ NakedDhcpv6Srv srv(0);
+
+ // A message with no client-id should fail
+ Pkt6Ptr rebind = Pkt6Ptr(new Pkt6(DHCPV6_REBIND, 1234));
+ EXPECT_FALSE(srv.sanityCheck(rebind));
+
+ // A message with a single client-id should succeed
+ OptionPtr clientid = generateClientId();
+ rebind->addOption(clientid);
+ EXPECT_TRUE(srv.sanityCheck(rebind));
+
+ // A message with server-id present should fail
+ rebind->addOption(srv.getServerID());
+ EXPECT_FALSE(srv.sanityCheck(rebind));
+}
+
+// Test that directly connected client's Rebind message is processed and Reply
+// message is sent back.
+TEST_F(RebindTest, directClient) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[0], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+ // The client should still have one lease which belong to one of the
+ // subnets.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+ selectSubnet(lease_client2.addr_, ClientClasses()));
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+ // Make sure, that the client's lease matches the lease held by the
+ // server.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+}
+
+// Test that server allocates a lease from a new subnet when the server
+// is reconfigured such that the previous subnet is replaced with a
+// new subnet. The client should get the new lease and an old lease
+// with zero lifetimes in the Reply.
+TEST_F(RebindTest, directClientChangingSubnet) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[0], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Reconfigure the server so as the new subnet is served on the
+ // client's interface. Note that there will also be a new subnet
+ // id assigned to the subnet on this interface.
+ configure(REBIND_CONFIGS[1], *client.getServer());
+ // Try to rebind, using the address that the client had acquired using
+ // previous server configuration.
+
+ ASSERT_NO_THROW(client.doRebind());
+
+ // We are expecting that the server has allocated a lease from the new
+ // subnet and sent zero lifetimes for a previous lease.
+
+ std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+ ASSERT_EQ(1, old_leases.size());
+ EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+ std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, new_leases.size());
+
+ // Make sure, that the lease that client has, is matching the lease
+ // in the lease database.
+ Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+ EXPECT_TRUE(lease_server2);
+ // Client should have received Success status code.
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
+}
+
+// Check that the server allocates a new lease when the client sends IA_NA
+// with a new IAID.
+TEST_F(RebindTest, directClientChangingIAID) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[0], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Modify the IAID of the lease record that client stores. By adding
+ // one to IAID we guarantee that the IAID will change.
+ client.clearRequestedIAs();
+ client.config_.leases_[0].iaid_ = 1235;
+ client.requestAddress(1235);
+
+ // Try to Rebind. The server should allocate new lease for this IAID.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // The old lease should be returned with 0 lifetimes.
+ std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+ ASSERT_EQ(1, old_leases.size());
+ EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+ // The new lease should be allocated.
+ std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, new_leases.size());
+
+ Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+ EXPECT_TRUE(lease_server2);
+ // The Status code returned to the client, should be Success.
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(1235));
+}
+
+// Check that the server allocates a requested lease for the client when
+// this lease has been lost from the database.
+TEST_F(RebindTest, directClientLostLease) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[0], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // The lease has been acquired. Now, let's explicitly remove it from the
+ // lease database.
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ lease_client.addr_);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Send Rebind.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // The server should re-allocate this lease to the client.
+ std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, new_leases.size());
+ EXPECT_EQ(lease_client.addr_, new_leases[0].addr_);
+ // Status code should be Success.
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
+}
+
+/// @todo Extend tests for direct client changing address.
+
+// Check that the client can Rebind existing lease through a relay.
+TEST_F(RebindTest, relayedClient) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Configure DHCPv6 client to simulate sending the message through a relay
+ // agent. The default link-addr is 3001:1::1. This address should be used
+ // by the server to pick the suitable subnet.
+ client.useRelay();
+ // Make 4-way exchange to get the lease. Pick the configuration #2 as it
+ // specifies the subnet for the relay agent's link address.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+ // The client should still have one lease which belongs to one of the
+ // subnets.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+ selectSubnet(lease_client2.addr_, ClientClasses()));
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+ // Make sure, that the client's lease matches the lease held by the
+ // server.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+}
+
+// Check that the lease is not extended for the relayed client when the
+// configuration has changed such that the subnet that client is using
+// doesn't exist anymore.
+TEST_F(RebindTest, relayedClientChangingSubnet) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Configure DHCPv6 client to simulate sending the message through a relay
+ // agent. The default link-addr is 3001:1::1. This address should be used
+ // by the server to pick the suitable subnet.
+ client.useRelay();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Reconfigure the server so as the new subnet is served on the
+ // client's interface. Note that there will also be a new subnet
+ // id assigned to the subnet on this interface.
+ configure(REBIND_CONFIGS[3], *client.getServer());
+ // Update relay link address to match the new subnet.
+ client.relay_link_addr_ = IOAddress("3001:4::1");
+ // Try to rebind, using the address that the client had acquired using
+ // previous server configuration.
+ ASSERT_NO_THROW(client.doRebind());
+ // We are expecting that the server didn't extend the lease because
+ // the address that client is using doesn't match the new subnet.
+ ASSERT_EQ(0, client.getLeaseNum());
+ // Client should have received NoBinding status code.
+ EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(1234));
+
+}
+
+// Check that the lease is not extended for the relayed client when the IAID in
+// the Rebind message doesn't match the one recorded for the client.
+TEST_F(RebindTest, relayedClientChangingIAID) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Configure DHCPv6 client to simulate sending the message through a relay
+ // agent. The default link-addr is 3001:1::1. This address should be used
+ // by the server to pick the suitable subnet.
+ client.useRelay();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+
+ // Modify the IAID of the lease record that client stores. By adding
+ // one to IAID we guarantee that the IAID will change.
+ client.clearRequestedIAs();
+ client.config_.leases_[0].iaid_ = 1235;
+ client.requestAddress(1235);
+
+ // Try to Rebind. The server should allocate new lease for this IAID.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // The old lease should be returned with 0 lifetimes.
+ std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+ ASSERT_EQ(1, old_leases.size());
+ EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+ // The new lease should be allocated.
+ std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, new_leases.size());
+
+ Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+ EXPECT_TRUE(lease_server2);
+ // The Status code returned to the client, should be Success.
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(1235));
+}
+
+// Check that the server allocates a requested lease for the client when
+// this lease has been lost from the database.
+TEST_F(RebindTest, relayedClientLostLease) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Configure DHCPv6 client to simulate sending the message through a relay
+ // agent. The default link-addr is 3001:1::1. This address should be used
+ // by the server to pick the suitable subnet.
+ client.useRelay();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // The lease has been acquired. Now, let's explicitly remove it from the
+ // lease database.
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ lease_client.addr_);
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Send Rebind.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // The server should re-allocate this lease to the client.
+ std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, new_leases.size());
+ EXPECT_EQ(lease_client.addr_, new_leases[0].addr_);
+ // Status code should be Success.
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
+}
+
+// Check that relayed client receives the IA with lifetimes of 0, when
+// client is trying to Rebind using an address it doesn't have.
+TEST_F(RebindTest, relayedClientChangingAddress) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Modify the address of the lease record that client stores. The server
+ // should check that the address is invalid (hasn't been allocated for
+ // the particular IAID).
+ client.config_.leases_[0].addr_ = IOAddress("3000::100");
+ // Try to Rebind. The client will use correct IAID but will specify a
+ // wrong address. The server will discover that the client has a binding
+ // but the address will not match.
+ ASSERT_NO_THROW(client.doRebind());
+ // Make sure that the server has discarded client's message. In such case,
+ // the message sent back to the client should be NULL.
+ EXPECT_TRUE(client.getContext().response_)
+ << "The server discarded the Rebind message, while it should have"
+ " sent a response indicating that the client should stop using the"
+ " lease, by setting lifetime values to 0.";
+ // Get the client's leases. He should get two addresses:
+ // the first one for the bogus 3000::100 address with 0 lifetimes.
+ // the second one with the actual lease with non-zero lifetimes.
+ ASSERT_EQ(2, client.getLeaseNum());
+
+ // Let's check the first one
+ Lease6 lease_client1 = client.getLease(0);
+ Lease6 lease_client2 = client.getLease(1);
+
+ if (lease_client1.addr_.toText() != "3000::100") {
+ lease_client1 = client.getLease(1);
+ lease_client2 = client.getLease(0);
+ }
+
+ // The lifetimes should be set to 0, as an explicit notification to the
+ // client to stop using invalid prefix.
+ EXPECT_EQ(0, lease_client1.valid_lft_);
+ EXPECT_EQ(0, lease_client1.preferred_lft_);
+
+ // Let's check the second lease
+ // The lifetimes should be set to 0, as an explicit notification to the
+ // client to stop using invalid prefix.
+ EXPECT_NE(0, lease_client2.valid_lft_);
+ EXPECT_NE(0, lease_client2.preferred_lft_);
+
+ // Check that server still has the same lease.
+ Lease6Ptr lease_server = checkLease(lease_client);
+ EXPECT_TRUE(lease_server);
+ // Make sure that the lease in the data base hasn't been added.
+ EXPECT_NE(0, lease_server->valid_lft_);
+ EXPECT_NE(0, lease_server->preferred_lft_);
+}
+
+// Check that the server extends the lease for the client having a prefix.
+TEST_F(RebindTest, directClientPD) {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[4], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+ // The client should still have one lease which belong to one of the
+ // subnets.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+ // Make sure, that the client's lease matches the lease held by the
+ // server.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+}
+
+// Test that server allocates a lease from a new subnet when the server
+// is reconfigured such that the previous subnet is replaced with a
+// new subnet. The client should get the new lease and an old lease
+// with zero lifetimes in the Reply.
+TEST_F(RebindTest, directClientPDChangingSubnet) {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[4], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Reconfigure the server so as the new subnet is served on the
+ // client's interface. Note that there will also be a new subnet
+ // id assigned to the subnet on this interface.
+ configure(REBIND_CONFIGS[5], *client.getServer());
+
+ // Try to rebind, using the prefix that the client had acquired using
+ // previous server configuration.
+ client.doRebind();
+
+ // We are expecting that the server has allocated a lease from the new
+ // subnet and sent zero lifetimes for a previous lease.
+
+ std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+ ASSERT_EQ(1, old_leases.size());
+ EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+ std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, new_leases.size());
+
+ // Make sure, that the lease that client has, is matching the lease
+ // in the lease database.
+ Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+ EXPECT_TRUE(lease_server2);
+ // Client should have received Success status code.
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(5678));
+}
+
+// Check that the server allocates a new lease when the client sends IA_PD
+// with a new IAID.
+TEST_F(RebindTest, directClientPDChangingIAID) {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[4], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+
+ // Modify the IAID of the lease record that client stores. By adding
+ // one to IAID we guarantee that the IAID will change.
+ client.clearRequestedIAs();
+ client.config_.leases_[0].iaid_ = 5679;
+ client.requestPrefix(5679);
+
+ // Try to Rebind. The server should allocate new lease for this IAID.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // The old lease should be returned with 0 lifetimes.
+ std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+ ASSERT_EQ(1, old_leases.size());
+ EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+ // The new lease should be allocated.
+ std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, new_leases.size());
+
+ Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+ EXPECT_TRUE(lease_server2);
+ // The Status code returned to the client, should be Success.
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(5679));
+}
+
+// Check that the prefix lifetime is not extended for the client when the
+// prefix used in Rebind message doesn't match the one that client has.
+TEST_F(RebindTest, directClientPDChangingPrefix) {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[4], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Modify the Prefix of the lease record that client stores. The server
+ // should check that the prefix is invalid (hasn't been allocated for
+ // the particular IAID).
+ ASSERT_NE(client.config_.leases_[0].addr_,
+ IOAddress("2001:db8:1:10::"));
+ client.config_.leases_[0].addr_ = IOAddress("2001:db8:1:10::");
+ // Try to Rebind. The client will use correct IAID but will specify a
+ // wrong prefix. The server will discover that the client has a binding
+ // but the prefix will not match. According to the RFC 8415, section 18.3.5
+ // the server may return delegated prefix with lifetime of 0 when it
+ // finds that the lease entry for the particular IAID but the prefix
+ // is not appropriate. This constitutes explicit notification to the
+ // client to not use this prefix.
+ ASSERT_NO_THROW(client.doRebind());
+ // Make sure that the server has discarded client's message. In such case,
+ // the message sent back to the client should be NULL.
+ EXPECT_TRUE(client.getContext().response_)
+ << "The server discarded the Rebind message, while it should have"
+ " sent a response indicating that the client should stop using the"
+ " lease, by setting lifetime values to 0.";
+ // Get the client's lease.
+ ASSERT_EQ(2, client.getLeaseNum());
+
+ // Client should get two entries. One with the invalid address he requested
+ // with zeroed lifetimes and a second one with the actual prefix he has
+ // with non-zero lifetimes.
+
+ // Get the lease with 0 lifetimes.
+ std::vector<Lease6> invalid_leases = client.getLeasesWithZeroLifetime();
+ ASSERT_EQ(1, invalid_leases.size());
+ EXPECT_EQ(0, invalid_leases[0].valid_lft_);
+ EXPECT_EQ(0, invalid_leases[0].preferred_lft_);
+
+ // Get the valid lease with non-zero lifetime.
+ std::vector<Lease6> valid_leases = client.getLeasesWithNonZeroLifetime();
+ ASSERT_EQ(1, valid_leases.size());
+
+ // Check that server still has the same lease.
+ Lease6Ptr lease_server = checkLease(valid_leases[0]);
+ ASSERT_TRUE(lease_server);
+ // Make sure that the lease in the data base hasn't been added.
+ EXPECT_NE(0, lease_server->valid_lft_);
+ EXPECT_NE(0, lease_server->preferred_lft_);
+}
+
+/// @todo Extend PD tests for relayed messages.
+/// @todo Extend PD tests to cover same prefix by different length.
+
+// This test checks that the Rebind message is discarded by the server if it
+// has been sent to unicast address (RFC 8415, section 18.4).
+TEST_F(RebindTest, unicast) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[0], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Set the unicast destination address for the Rebind message.
+ // The Rebind should be discarded when sent to unicast address,
+ // according to section 18.4 of RFC 8415.
+ client.setDestAddress(IOAddress("2001:db8:1::1"));
+ // Send the Rebind message to a unicast address.
+ ASSERT_NO_THROW(client.doRebind());
+ // The client's lease should remain with no change (shouldn't be extended)
+ // because server is supposed to drop the message sent to a unicast address.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ ASSERT_TRUE(lease_client2 == lease_client);
+ // Check that server still has the lease.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+ // Make sure that the server has discarded client's message. In such case,
+ // the message sent back to the client should be NULL.
+ EXPECT_FALSE(client.getContext().response_);
+}
+
+// This test checks that the relayed Rebind message is processed by the server
+// when sent to unicast address.
+TEST_F(RebindTest, relayedUnicast) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Configure DHCPv6 client to simulate sending the message through a relay
+ // agent. The default link-addr is 3001:1::1. This address should be used
+ // by the server to pick the suitable subnet.
+ client.useRelay();
+ // Make 4-way exchange to get the lease. Pick the configuration #2 as it
+ // specifies the subnet for the relay agent's link address.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+ // Set the unicast destination address.
+ client.setDestAddress(IOAddress("2001:db8:1::1"));
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+ // The client should still have one lease which belongs to one of the
+ // subnets.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+ selectSubnet(lease_client2.addr_, ClientClasses()));
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+ // Make sure, that the client's lease matches the lease held by the
+ // server.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+}
+
+// This test verifies that the client can request the prefix delegation
+// while it is rebinding an address lease.
+TEST_F(RebindTest, requestPrefixInRebind) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress();
+ client.requestPrefix();
+
+ // Configure the server with NA pools only.
+ ASSERT_NO_THROW(configure(REBIND_CONFIGS[0], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+
+ // The client should not acquire a PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+ ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(5678));
+
+ // Send Rebind message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRebind());
+ ASSERT_TRUE(client.getContext().response_);
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+ ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(5678));
+
+ // Reconfigure the server to use both NA and PD pools.
+ configure(REBIND_CONFIGS[6], *client.getServer());
+
+ // Send Rebind message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na_rebound =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_rebound.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
+
+ // The lease should have been rebound.
+ EXPECT_GE(leases_client_na_rebound[0].cltt_ - leases_client_na[0].cltt_, 1000);
+
+ // The client should now also acquire a PD lease.
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(5678));
+}
+
+// This test verifies that the client can request the prefix delegation
+// while it is rebinding an address lease.
+TEST_F(RebindTest, requestAddressInRebind) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress();
+ client.requestPrefix();
+
+ // Configure the server with PD pools only.
+ ASSERT_NO_THROW(configure(REBIND_CONFIGS[4], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(5678));
+
+ // The client should not acquire a NA lease.
+ std::vector<Lease6> leases_client_na =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(0, leases_client_na.size());
+ ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(1234));
+
+ // Send Rebind message to the server, including IA_PD and requesting IA_NA.
+ // The server should return NoAddrsAvail status code in this case.
+ ASSERT_NO_THROW(client.doRebind());
+ leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(0, leases_client_na.size());
+ ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(1234));
+
+ // Reconfigure the server to use both NA and PD pools.
+ configure(REBIND_CONFIGS[6], *client.getServer());
+
+ // Send Rebind message to the server, including IA_PD and requesting IA_NA.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // Make sure that the client has renewed PD lease.
+ std::vector<Lease6> leases_client_pd_renewed =
+ client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(5678));
+ EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+ // The client should now also acquire a NA lease.
+ leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
+}
+
+// This test verifies that the client can request the DOCSIS sub-options.
+TEST_F(RebindTest, docsisORO) {
+ Dhcp6Client client;
+ // Configure client to request IA_NA.
+ client.requestAddress();
+ // Configure the DOCSIS vendor ORO for 32, 33, 34, 37 and 38.
+ client.requestDocsisOption(DOCSIS3_V6_TFTP_SERVERS);
+ client.requestDocsisOption(DOCSIS3_V6_CONFIG_FILE);
+ client.requestDocsisOption(DOCSIS3_V6_SYSLOG_SERVERS);
+ client.requestDocsisOption(DOCSIS3_V6_TIME_SERVERS);
+ client.requestDocsisOption(DOCSIS3_V6_TIME_OFFSET);
+ // Don't add it for now.
+ client.useDocsisORO(false);
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[7], 2, client));
+ // Keep the client's lease for future reference.
+ Lease6 lease_client = client.getLease(0);
+
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+ // The client should still have one lease which belong to one of the
+ // subnets.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client2 = client.getLease(0);
+ ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+ selectSubnet(lease_client2.addr_, ClientClasses()));
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+ // Make sure, that the client's lease matches the lease held by the
+ // server.
+ Lease6Ptr lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+ // No vendor option was included in the renew so there should be none
+ // in the received configuration.
+ OptionPtr opt = client.config_.findOption(D6O_VENDOR_OPTS);
+ ASSERT_FALSE(opt);
+
+ // Add a DOCSIS ORO.
+ client.useDocsisORO(true);
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+ // The client should still have one lease which belong to one of the
+ // subnets.
+ ASSERT_EQ(1, client.getLeaseNum());
+ lease_client2 = client.getLease(0);
+ ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+ selectSubnet(lease_client2.addr_, ClientClasses()));
+ // The client's lease should have been extended. The client will
+ // update the cltt to current time when the lease gets extended.
+ ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+ // Make sure, that the client's lease matches the lease held by the
+ // server.
+ lease_server2 = checkLease(lease_client2);
+ EXPECT_TRUE(lease_server2);
+
+ // Verify whether there is a vendor option.
+ opt = client.config_.findOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(opt);
+ // The vendor option must be a OptionVendor object.
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+ // The vendor-id should be DOCSIS.
+ EXPECT_EQ(VENDOR_ID_CABLE_LABS, vendor->getVendorId());
+ // There must be a config file sub-option.
+ opt = vendor->getOption(DOCSIS3_V6_CONFIG_FILE);
+ // With the expected content.
+ OptionStringPtr config_file =
+ boost::dynamic_pointer_cast<OptionString>(opt);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+}
+
+// This test verifies that the same options can be specified on the global
+// level, subnet level and pool level. The options associated with pools
+// are used when the lease is handed out from these pools.
+TEST_F(RebindTest, optionsInheritance) {
+ Dhcp6Client client;
+ // Request a single address and single prefix.
+ ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::")));
+ ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45")));
+ // Request two options configured for the pools from which the client may get
+ // a lease.
+ client.requestOption(D6O_NAME_SERVERS);
+ client.requestOption(D6O_NIS_SERVERS);
+ client.requestOption(D6O_NISP_SERVERS);
+ client.requestOption(D6O_SNTP_SERVERS);
+ ASSERT_NO_FATAL_FAILURE(configure(REBIND_CONFIGS[8], *client.getServer()));
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Send Rebind message to the server.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // We have provided hints so we should get leases appropriate
+ // for the hints we provided.
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+ // We shouldn't have leases for the prefix and address which we didn't
+ // request.
+ ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+ ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+ // We should have received options associated with a prefix pool and
+ // address pool from which we have requested the leases. We should not
+ // have received options associated with the remaining pools. Instead,
+ // we should have received options associated with a subnet.
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1"));
+
+ // Let's now also request a prefix and an address from the remaining pools.
+ ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::")));
+ ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11")));
+
+ client.fastFwdTime(1000);
+
+ // Send another Rebind.
+ ASSERT_NO_THROW(client.doRebind());
+
+ // We should now have two prefixes from two distinct pools.
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+ // We should also have two addresses from two distinct pools.
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+ // This time, options from all pools should have been assigned.
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
+}
+
+} // namespace
diff --git a/src/bin/dhcp6/tests/renew_unittest.cc b/src/bin/dhcp6/tests/renew_unittest.cc
new file mode 100644
index 0000000..644bc97
--- /dev/null
+++ b/src/bin/dhcp6/tests/renew_unittest.cc
@@ -0,0 +1,743 @@
+// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+#include <boost/pointer_cast.hpp>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Renew tests.
+///
+/// - Configuration 0:
+/// - only addresses (no prefixes)
+/// - 1 subnet with 2001:db8:1::/64 pool
+///
+/// - Configuration 1:
+/// - only prefixes (no addresses)
+/// - prefix pool: 3000::/72
+///
+/// - Configuration 2:
+/// - addresses and prefixes
+/// - 1 subnet with one address pool and one prefix pool
+/// - address pool: 2001:db8:1::/64
+/// - prefix pool: 3000::/72
+///
+/// - Configuration 3:
+/// - only addresses (no prefixes)
+/// - 1 subnet with 2001:db8:1::/64 pool
+/// - DOCSIS vendor config file sub-option
+///
+/// - Configuration 4:
+/// - single subnet 3000::/32,
+/// - two options specified in the subnet scope,
+/// - one option specified at the global scope,
+/// - two address pools: 3000::10-3000::20, 3000::40-3000::50,
+/// - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64,
+/// - an option with unique value specified for each pool, so as it is
+/// possible to test that pool specific options can be assigned.
+///
+/// - Configuration 5:
+/// - addresses and prefixes
+/// - 1 subnet with one address pool and one prefix pool
+/// - address pool: 2001:db8:1::/64
+/// - prefix pool: 3000::/72
+/// - excluded prefix 3000::1000/120 in a prefix pool.
+///
+const char* RENEW_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"normal_erouter_v6.cm\""
+ "}],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::234\""
+ "},"
+ "{"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::1\""
+ "} ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::567\""
+ " },"
+ " {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::1\""
+ " } ],"
+ " \"pools\": [ { "
+ " \"pool\": \"3000::10 - 3000::20\","
+ " \"option-data\": [ {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"pool\": \"3000::40 - 3000::50\","
+ " \"option-data\": [ {"
+ " \"name\": \"nisp-servers\","
+ " \"data\": \"3000:2::3\""
+ " } ]"
+ " } ],"
+ " \"pd-pools\": [ { "
+ " \"prefix\": \"2001:db8:3::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::678\""
+ " } ]"
+ " },"
+ " {"
+ " \"prefix\": \"2001:db8:4::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"3000:1::789\""
+ " } ]"
+ " } ],"
+ " \"subnet\": \"3000::/32\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000"
+ "}",
+
+// Configuration 5
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80,"
+ " \"excluded-prefix\": \"3000::1000\","
+ " \"excluded-prefix-len\": 120"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+};
+
+/// @brief Test fixture class for testing Renew.
+class RenewTest : public Dhcpv6MessageTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ RenewTest()
+ : Dhcpv6MessageTest(), na_iaid_(1234), pd_iaid_(5678) {
+ }
+
+ /// @brief IAID used for IA_NA.
+ uint32_t na_iaid_;
+
+ /// @brief IAID used for IA_PD.
+ uint32_t pd_iaid_;
+
+};
+
+// This test verifies that the client can request the prefix delegation
+// while it is renewing an address lease.
+TEST_F(RenewTest, requestPrefixInRenew) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress(na_iaid_);
+ client.requestPrefix(pd_iaid_);
+
+ // Configure the server with NA pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[0], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // The client should not acquire a PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+ ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+ // Send Renew message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+ ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+ std::vector<Lease6> leases_client_na_renewed =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Reconfigure the server to use both NA and PD pools.
+ configure(RENEW_CONFIGS[2], *client.getServer());
+
+ // Send Renew message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has acquired NA lease.
+ leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // The lease should have been renewed. Allow some time skew.
+#ifdef STRICT_TEST_TIMING
+ EXPECT_EQ(1000,
+ leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_);
+#else
+ EXPECT_LE(995,
+ leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_);
+ EXPECT_GE(1005,
+ leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_);
+#endif
+
+ // The client should now also acquire a PD lease.
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+}
+
+// Test that it is possible to renew a prefix lease with a Prefix Exclude
+// option being included during renew.
+TEST_F(RenewTest, renewWithExcludedPrefix) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress(na_iaid_);
+ client.requestPrefix(pd_iaid_);
+
+ // Request Prefix Exclude option.
+ client.requestOption(D6O_PD_EXCLUDE);
+
+ // Configure the server with NA pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[2], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // The client should also acquire a PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ ASSERT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // Send Renew message to the server, including IA_NA and IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ std::vector<Lease6> leases_client_na_renewed =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ std::vector<Lease6> leases_client_pd_renewed =
+ client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // Make sure that Prefix Exclude option hasn't been included.
+ OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_FALSE(option);
+
+ // Reconfigure the server to use the prefix pool with excluded prefix.
+ configure(RENEW_CONFIGS[5], *client.getServer());
+
+ // Send Renew message to the server, including IA_NA and IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has acquired NA lease.
+ leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Make sure that the client has acquired PD lease.
+ leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // The leases should have been renewed.
+ EXPECT_GE(leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_, 1000);
+ EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+ // This time, the Prefix Exclude option should be included.
+ option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_TRUE(option);
+ Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+ ASSERT_TRUE(pd_exclude);
+ EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix(IOAddress("3000::"),
+ 80).toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
+// This test verifies that the client can request a prefix delegation
+// with a hint, while it is renewing an address lease.
+TEST_F(RenewTest, requestPrefixInRenewUseHint) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress(na_iaid_);
+ client.requestPrefix(pd_iaid_);
+
+ // Configure the server with NA pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[0], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+
+ // The client should not acquire a PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+ ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+ // Send Renew message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+ ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+ std::vector<Lease6> leases_client_na_renewed =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Specify the hint used for IA_PD.
+ client.requestPrefix(pd_iaid_, 64, IOAddress::IPV6_ZERO_ADDRESS());
+
+ // Send Renew message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has acquired NA lease.
+ leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+ ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+ // Reconfigure the server to use both NA and PD pools.
+ configure(RENEW_CONFIGS[2], *client.getServer());
+
+ // Send Renew message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has acquired NA lease.
+ leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // The lease should have been renewed.
+ EXPECT_GE(leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_, 1000);
+
+ // The client should now also acquire a PD lease.
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+}
+
+// This test verifies that the client can request the prefix delegation
+// while it is renewing an address lease.
+TEST_F(RenewTest, requestAddressInRenew) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress(na_iaid_);
+ client.requestPrefix(pd_iaid_);
+
+ // Configure the server with PD pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[1], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // The client should not acquire a NA lease.
+ std::vector<Lease6> leases_client_na =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(0, leases_client_na.size());
+ ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(na_iaid_));
+
+ // Send Renew message to the server, including IA_PD and requesting IA_NA.
+ // The server should return NoAddrsAvail status code in this case.
+ ASSERT_NO_THROW(client.doRenew());
+ leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(0, leases_client_na.size());
+ ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(na_iaid_));
+
+ std::vector<Lease6> leases_client_pd_renewed =
+ client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+ EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+ // Reconfigure the server to use both NA and PD pools.
+ configure(RENEW_CONFIGS[2], *client.getServer());
+
+ // Send Renew message to the server, including IA_PD and requesting IA_NA.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has renewed PD lease.
+ leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+ EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+ // The client should now also acquire a NA lease.
+ leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+}
+
+// This test verifies that the client can request address assignment
+// while it is renewing an address lease, with a hint.
+TEST_F(RenewTest, requestAddressInRenewHint) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress(na_iaid_);
+ client.requestPrefix(pd_iaid_);
+
+ // Configure the server with PD pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[1], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // The client should not acquire a NA lease.
+ std::vector<Lease6> leases_client_na =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(0, leases_client_na.size());
+ ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(na_iaid_));
+
+ client.requestAddress(na_iaid_, IOAddress("2001:db8:1::100"));
+
+ // Send Renew message to the server, including IA_PD and requesting IA_NA.
+ // The server should return NoAddrsAvail status code in this case.
+ ASSERT_NO_THROW(client.doRenew());
+ leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ // The server should return the hint with the zero lifetimes.
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(0, leases_client_na[0].preferred_lft_);
+ EXPECT_EQ(0, leases_client_na[0].valid_lft_);
+ ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(na_iaid_));
+
+ std::vector<Lease6> leases_client_pd_renewed =
+ client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+ EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+ // Reconfigure the server to use both NA and PD pools.
+ configure(RENEW_CONFIGS[2], *client.getServer());
+
+ // Send Renew message to the server, including IA_PD and requesting IA_NA.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has renewed PD lease.
+ leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+ EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+ // The client should now also acquire a NA lease.
+ leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+}
+
+// This test verifies that the client can request the DOCSIS sub-options.
+TEST_F(RenewTest, docsisORO) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA.
+ client.requestAddress(na_iaid_);
+
+ // Configure the DOCSIS vendor ORO for 32, 33, 34, 37 and 38.
+ client.requestDocsisOption(DOCSIS3_V6_TFTP_SERVERS);
+ client.requestDocsisOption(DOCSIS3_V6_CONFIG_FILE);
+ client.requestDocsisOption(DOCSIS3_V6_SYSLOG_SERVERS);
+ client.requestDocsisOption(DOCSIS3_V6_TIME_SERVERS);
+ client.requestDocsisOption(DOCSIS3_V6_TIME_OFFSET);
+ // Don't add it for now.
+ client.useDocsisORO(false);
+
+ // Configure the server with NA pools and DOCSIS config file.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[3], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Send Renew message to the server.
+ ASSERT_NO_THROW(client.doRenew());
+
+ std::vector<Lease6> leases_client_na_renewed =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // No vendor option was included in the renew so there should be none
+ // in the received configuration.
+ OptionPtr opt = client.config_.findOption(D6O_VENDOR_OPTS);
+ ASSERT_FALSE(opt);
+
+ // Add a DOCSIS ORO.
+ client.useDocsisORO(true);
+
+ // Send Renew message to the server.
+ ASSERT_NO_THROW(client.doRenew());
+
+ leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Verify whether there is a vendor option.
+ opt = client.config_.findOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(opt);
+
+ // The vendor option must be a OptionVendor object.
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+
+ // The vendor-id should be DOCSIS.
+ EXPECT_EQ(VENDOR_ID_CABLE_LABS, vendor->getVendorId());
+
+ // There must be a config file sub-option.
+ opt = vendor->getOption(DOCSIS3_V6_CONFIG_FILE);
+
+ // With the expected content.
+ OptionStringPtr config_file =
+ boost::dynamic_pointer_cast<OptionString>(opt);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+}
+
+// This test verifies that the same options can be specified on the global
+// level, subnet level and pool level. The options associated with pools
+// are used when the lease is handed out from these pools.
+TEST_F(RenewTest, optionsInheritance) {
+ Dhcp6Client client;
+ // Request a single address and single prefix.
+ ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::")));
+ ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45")));
+ // Request two options configured for the pools from which the client may get
+ // a lease.
+ client.requestOption(D6O_NAME_SERVERS);
+ client.requestOption(D6O_NIS_SERVERS);
+ client.requestOption(D6O_NISP_SERVERS);
+ client.requestOption(D6O_SNTP_SERVERS);
+ ASSERT_NO_FATAL_FAILURE(configure(RENEW_CONFIGS[4], *client.getServer()));
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Send Renew message to the server.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // We have provided hints so we should get leases appropriate
+ // for the hints we provided.
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+ // We shouldn't have leases for the prefix and address which we didn't
+ // request.
+ ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+ ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+ // We should have received options associated with a prefix pool and
+ // address pool from which we have requested the leases. We should not
+ // have received options associated with the remaining pools. Instead,
+ // we should have received options associated with a subnet.
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1"));
+
+ // Let's now also request a prefix and an address from the remaining pools.
+ ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::")));
+ ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11")));
+
+ client.fastFwdTime(1000);
+
+ // Send another Renew.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // We should now have two prefixes from two distinct pools.
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+ // We should also have two addresses from two distinct pools.
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+ // This time, options from all pools should have been assigned.
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/sarr_unittest.cc b/src/bin/dhcp6/tests/sarr_unittest.cc
new file mode 100644
index 0000000..2d379be
--- /dev/null
+++ b/src/bin/dhcp6/tests/sarr_unittest.cc
@@ -0,0 +1,1335 @@
+// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <asiolink/io_address.h>
+#include <stats/stats_mgr.h>
+#include <set>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+
+namespace {
+
+/// @brief Set of JSON configurations used by the SARR unit tests.
+///
+/// - Configuration 0:
+/// - one subnet 3000::/32 used on eth0 interface
+/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
+/// - the delegated prefix was intentionally selected to not match the
+/// subnet prefix, to test that the delegated prefix doesn't need to
+/// match the subnet prefix
+///
+/// - Configuration 1:
+/// - two subnets 2001:db8:1::/48 and 2001:db8:2::/48
+/// - first subnet assigned to interface eth0, another one assigned to eth1
+/// - one pool for subnet in a range of 2001:db8:X::1 - 2001:db8:X::10,
+/// where X is 1 or 2
+/// - enables Rapid Commit for the first subnet and disables for the second
+/// one
+/// - DNS updates enabled
+///
+/// - Configuration 2:
+/// - single subnet 3000::/32,
+/// - two options specified in the subnet scope,
+/// - one option specified at the global scope,
+/// - two address pools: 3000::10-3000::20, 3000::40-3000::50,
+/// - two prefix pools: 2001:db8:3::/64 and 2001:db8:4::/64,
+/// - an option with unique value specified for each pool, so as it is
+/// possible to test that pool specific options can be assigned.
+///
+/// - Configuration 3:
+/// - one subnet 3000::/32 used on eth0 interface
+/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
+/// - Excluded Prefix specified (RFC 6603).
+///
+/// - Configuration 4:
+/// - Simple configuration with a single subnet
+/// - Two host reservations, one out of the pool, another one in pool
+/// - The reservations-in-subnet and reservations-out-of-pool flags are set to
+/// true to test that only out of pool reservations are honored.
+///
+/// - Configuration 5:
+/// - Selects random allocator for addresses.
+/// - One subnet with three distinct pools.
+/// - Random allocator enabled globally for addresses.
+/// - Iterative allocator for prefix delegation.
+///
+/// - Configuration 6:
+/// - Selects random allocator for delegated prefixes.
+/// - One subnet with three distinct pools.
+/// - Random allocator enabled globally for delegated prefixes.
+/// - Iterative allocator for address allocation.
+const char* CONFIGS[] = {
+ // Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:3::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64"
+ " } ],"
+ " \"subnet\": \"3000::/32\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\","
+ " \"rapid-commit\": true"
+ " },"
+ " {"
+ " \"id\": 2, "
+ " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],"
+ " \"subnet\": \"2001:db8:2::/48\", "
+ " \"interface\": \"eth1\","
+ " \"rapid-commit\": false"
+ " } ],"
+ "\"valid-lifetime\": 4000,"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"qualifying-suffix\" : \"example.com\" }"
+ "}",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::234\""
+ "},"
+ "{"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::1\""
+ "} ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::567\""
+ " },"
+ " {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::1\""
+ " } ],"
+ " \"pools\": [ { "
+ " \"pool\": \"3000::10 - 3000::20\","
+ " \"option-data\": [ {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"3000:2::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"pool\": \"3000::40 - 3000::50\","
+ " \"option-data\": [ {"
+ " \"name\": \"nisp-servers\","
+ " \"data\": \"3000:2::3\""
+ " } ]"
+ " } ],"
+ " \"pd-pools\": [ { "
+ " \"prefix\": \"2001:db8:3::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3000:1::678\""
+ " } ]"
+ " },"
+ " {"
+ " \"prefix\": \"2001:db8:4::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 64,"
+ " \"option-data\": [ {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"3000:1::789\""
+ " } ]"
+ " } ],"
+ " \"subnet\": \"3000::/32\", "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000"
+ "}",
+
+ // Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:3::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64,"
+ " \"excluded-prefix\": \"2001:db8:3::1000\","
+ " \"excluded-prefix-len\": 120"
+ " } ],"
+ " \"subnet\": \"3000::/32\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000"
+ "}",
+
+ // Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth0\", "
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true,"
+ " \"reservations\": [ "
+ " {"
+ " \"duid\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [\"2001:db8:1::20\"]"
+ " },"
+ " {"
+ " \"duid\": \"11:22:33:44:55:66\","
+ " \"ip-addresses\": [\"2001:db8:1::5\"]"
+ " }"
+ " ]"
+ "} ]"
+ "}",
+
+ // Configuration 5
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"allocator\": \"random\","
+ "\"pd-allocator\": \"iterative\","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": ["
+ " {"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"3000::20 - 3000::60\""
+ " }"
+ " ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"2001:db8:3::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64"
+ " }"
+ " ],"
+ " \"id\": 1, "
+ " \"subnet\": \"3000::/32\", "
+ " \"interface\": \"eth0\""
+ " }"
+ "],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 6
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"allocator\": \"iterative\","
+ "\"pd-allocator\": \"random\","
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": ["
+ " {"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"3000::20 - 3000::60\""
+ " }"
+ " ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"2001:db8:3::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64"
+ " }"
+ " ],"
+ " \"id\": 1, "
+ " \"subnet\": \"3000::/32\", "
+ " \"interface\": \"eth0\""
+ " }"
+ "],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 7
+ R"({
+ "cache-max-age": 600,
+ "cache-threshold": .50,
+ "interfaces-config": {
+ "interfaces": [ "*" ]
+ },
+ "subnet6": [
+ {
+ "id": 1,
+ "interface": "eth0",
+ "pools": [
+ {
+ "pool": "2001:db8::10 - 2001:db8::20"
+ },
+ ],
+ "pd-pools": [
+ {
+ "prefix": "2001:db8:1::",
+ "prefix-len": 64,
+ "delegated-len": 96
+ },
+ ],
+ "subnet": "2001:db8::/32"
+ },
+ {
+ "id": 2,
+ "subnet": "3001:db8::/32"
+ }
+ ],
+ "valid-lifetime": 600
+ })",
+};
+
+/// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
+/// Request-Reply and 2-way exchange: Solicit-Reply.
+class SARRTest : public Dhcpv6SrvTest {
+public:
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ SARRTest()
+ : Dhcpv6SrvTest(),
+ iface_mgr_test_config_(true) {
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Clear the DHCP-DDNS configuration.
+ virtual ~SARRTest() {
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(cfg);
+
+ // Let's wipe all existing statistics.
+ isc::stats::StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Check that server processes correctly a prefix hint sent by the
+ /// client. This test checks that the server doesn't allocate colliding
+ /// prefixes as a result of receiving hints from two clients which set the
+ /// non-significant bytes of the prefix in their hints. The server should
+ /// zero the non-significant bytes of the hint and allocate the prefix of
+ /// the correct (configured) length.
+ void directClientPrefixHint();
+
+ /// @brief Check that the server assigns a delegated prefix that is later
+ /// returned to the client for various prefix hints.
+ ///
+ /// When the client renews the lease, it sends a prefix hint with the same
+ /// prefix but with a different prefix length. In another case, the client
+ /// asks for the same prefix length but different prefix. In both cases,
+ /// the server should return an existing lease.
+ void directClientPrefixLengthHintRenewal();
+
+ /// @brief This test verifies that the same options can be specified on the
+ /// global level, subnet level and pool level. The options associated with
+ /// pools are used when the lease is handed out from these pools.
+ void optionsInheritance();
+
+ /// @brief This test verifies that it is possible to specify an excluded
+ /// prefix (RFC 6603) and send it back to the client requesting prefix
+ /// delegation.
+ void directClientExcludedPrefix();
+
+ /// @brief Check that when the client includes the Rapid Commit option in
+ /// its Solicit, the server responds with Reply and commits the lease.
+ void rapidCommitEnable();
+
+ /// @brief Check that the server responds with Advertise if the client
+ /// hasn't included the Rapid Commit option in the Solicit.
+ void rapidCommitNoOption();
+
+ /// @brief Check that when the Rapid Commit support is disabled for the
+ /// subnet the server replies with an Advertise and ignores the Rapid Commit
+ /// option sent by the client.
+ void rapidCommitDisable();
+
+ /// @brief This test verifies that regular Solicit/Adv/Request/Reply
+ /// exchange will result in appropriately set statistics.
+ void sarrStats();
+
+ /// @brief This test verifies that pkt6-receive-drop is increased properly
+ /// when the client's packet is rejected due to mismatched server-id value.
+ void pkt6ReceiveDropStat1();
+
+ /// @brief This test verifies that pkt6-receive-drop is increased properly
+ /// when the client's packet is rejected due to being unicast communication.
+ void pkt6ReceiveDropStat2();
+
+ /// @brief This test verifies that pkt6-receive-drop is increased properly
+ /// when the client's packet is rejected due to having too many client-id
+ /// options (exactly one is expected).
+ void pkt6ReceiveDropStat3();
+
+ /// @brief This test verifies that in pool reservations are ignored when the
+ /// reservations-out-of-pool flag is set to true.
+ void reservationModeOutOfPool();
+
+ /// @brief This test verifies that the in-pool reservation can be assigned
+ /// to a client not owning this reservation when the
+ /// reservations-out-of-pool flag is set to true.
+ void reservationIgnoredInOutOfPoolMode();
+
+ /// @brief This test verifies that random allocator is used according
+ /// to the configuration and it allocates random addresses.
+ void randomAddressAllocation();
+
+ /// @brief This test verifies that random allocator is used according
+ /// to the configuration and it allocates random prefixes.
+ void randomPrefixAllocation();
+
+ /// @brief Checks that features related to lease caching (such as lease reuse statistics) work.
+ void leaseCaching();
+
+ /// @brief Checks the value of a statistic.
+ ///
+ /// @param name name of statistic to check
+ /// @param expected_size expected number of statistic samples
+ /// @param expected_value expected value of the latest statistic sample
+ void checkStat(string const& name,
+ size_t const expected_size,
+ int64_t const expected_value);
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+void
+SARRTest::directClientPrefixHint() {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Append IAPREFIX option to the client's message.
+ ASSERT_NO_THROW(client.requestPrefix(5678, 64, asiolink::IOAddress("2001:db8:3:33::33")));
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ // The server should correctly deal with the least significant bytes
+ // of the hint being set. It should set them to zero and use the
+ // valid portion of the hint.
+ EXPECT_EQ("2001:db8:3:33::", lease_client.addr_.toText());
+ // Server ignores other parts of the IAPREFIX option.
+ EXPECT_EQ(64, lease_client.prefixlen_);
+ EXPECT_EQ(3000, lease_client.preferred_lft_);
+ EXPECT_EQ(4000, lease_client.valid_lft_);
+ Lease6Ptr lease_server = checkLease(lease_client);
+ // Check that the server recorded the lease.
+ ASSERT_TRUE(lease_server);
+
+ // Remove existing lease and modify the DUID of the client to simulate
+ // the case that different client is trying to get the prefix.
+ client.clearConfig();
+ client.modifyDUID();
+
+ // Use the hint with some least significant bytes set.
+ client.clearRequestedIAs();
+ ASSERT_NO_THROW(client.requestPrefix(5678, 64, IOAddress("2001:db8:3:33::34")));
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should assign a lease.
+ ASSERT_EQ(1, client.getLeaseNum());
+ lease_client = client.getLease(0);
+ // The hint collides with the existing lease, so the server should not
+ // assign for the second client.
+ EXPECT_NE("2001:db8:3:33::", lease_client.addr_.toText());
+ EXPECT_NE("2001:db8:3:33::34", lease_client.addr_.toText());
+ // Check that the assigned prefix belongs to the pool.
+ ASSERT_TRUE(!subnets->empty());
+ (*subnets->begin())->inPool(Lease::TYPE_PD, lease_client.addr_);
+ EXPECT_EQ(64, lease_client.prefixlen_);
+ EXPECT_EQ(3000, lease_client.preferred_lft_);
+ EXPECT_EQ(4000, lease_client.valid_lft_);
+ lease_server = checkLease(lease_client);
+ ASSERT_TRUE(lease_server);
+}
+
+TEST_F(SARRTest, directClientPrefixHint) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ directClientPrefixHint();
+}
+
+TEST_F(SARRTest, directClientPrefixHintMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ directClientPrefixHint();
+}
+
+void
+SARRTest::directClientPrefixLengthHintRenewal() {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Append IAPREFIX option to the client's message.
+ ASSERT_NO_THROW(client.requestPrefix(5678, 64, asiolink::IOAddress("2001:db8:3:36::")));
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ // The server should respect the prefix hint.
+ EXPECT_EQ("2001:db8:3:36::", lease_client.addr_.toText());
+ // Server ignores other parts of the IAPREFIX option.
+ EXPECT_EQ(64, lease_client.prefixlen_);
+ EXPECT_EQ(3000, lease_client.preferred_lft_);
+ EXPECT_EQ(4000, lease_client.valid_lft_);
+ Lease6Ptr lease_server = checkLease(lease_client);
+ // Check that the server recorded the lease.
+ ASSERT_TRUE(lease_server);
+
+ // Request the same prefix with a different length. The server should
+ // return an existing lease.
+ client.clearRequestedIAs();
+ ASSERT_NO_THROW(client.requestPrefix(5678, 80, IOAddress("2001:db8:3:36::")));
+ ASSERT_NO_THROW(client.doSARR());
+ ASSERT_EQ(1, client.getLeaseNum());
+ lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:3:36::", lease_client.addr_.toText());
+ EXPECT_EQ(64, lease_client.prefixlen_);
+
+ // Try to request another prefix. The client should still get the existing
+ // lease.
+ client.clearRequestedIAs();
+ ASSERT_NO_THROW(client.requestPrefix(5678, 64, IOAddress("2001:db8:3:37::")));
+ ASSERT_NO_THROW(client.doSARR());
+ ASSERT_EQ(1, client.getLeaseNum());
+ lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:3:36::", lease_client.addr_.toText());
+ EXPECT_EQ(64, lease_client.prefixlen_);
+}
+
+TEST_F(SARRTest, directClientPrefixLengthHintRenewal) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ directClientPrefixLengthHintRenewal();
+}
+
+TEST_F(SARRTest, directClientPrefixLengthHintRenewalMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ directClientPrefixLengthHintRenewal();
+}
+
+void
+SARRTest::optionsInheritance() {
+ Dhcp6Client client;
+ // Request a single address and single prefix.
+ ASSERT_NO_THROW(client.requestPrefix(0xabac, 64, IOAddress("2001:db8:4::")));
+ ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3000::45")));
+ // Request two options configured for the pools from which the client may get
+ // a lease.
+ client.requestOption(D6O_NAME_SERVERS);
+ client.requestOption(D6O_NIS_SERVERS);
+ client.requestOption(D6O_NISP_SERVERS);
+ client.requestOption(D6O_SNTP_SERVERS);
+ ASSERT_NO_FATAL_FAILURE(configure(CONFIGS[2], *client.getServer()));
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // We have provided hints so we should get leases appropriate
+ // for the hints we provided.
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+ // We shouldn't have leases for the prefix and address which we didn't
+ // request.
+ ASSERT_FALSE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+ ASSERT_FALSE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+ // We should have received options associated with a prefix pool and
+ // address pool from which we have requested the leases. We should not
+ // have received options associated with the remaining pools. Instead,
+ // we should have received options associated with a subnet.
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::567"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::1"));
+
+ // Let's now also request a prefix and an address from the remaining pools.
+ ASSERT_NO_THROW(client.requestPrefix(0x6806, 64, IOAddress("2001:db8:3::")));
+ ASSERT_NO_THROW(client.requestAddress(0x6860, IOAddress("3000::11")));
+
+ // Perform 4-way exchange again.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // We should now have two prefixes from two distinct pools.
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:3::"), 64));
+ ASSERT_TRUE(client.hasLeaseForPrefix(IOAddress("2001:db8:4::"), 64));
+ // We should also have two addresses from two distinct pools.
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::45")));
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("3000::11")));
+
+ // This time, options from all pools should have been assigned.
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "3000:1::678"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NIS_SERVERS, "3000:1::789"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NISP_SERVERS, "3000:2::3"));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_SNTP_SERVERS, "3000:2::2"));
+}
+
+TEST_F(SARRTest, optionsInheritance) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ optionsInheritance();
+}
+
+TEST_F(SARRTest, optionsInheritanceMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ optionsInheritance();
+}
+
+void
+SARRTest::directClientExcludedPrefix() {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ client.requestOption(D6O_PD_EXCLUDE);
+ configure(CONFIGS[3], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ(64, lease_client.prefixlen_);
+ EXPECT_EQ(3000, lease_client.preferred_lft_);
+ EXPECT_EQ(4000, lease_client.valid_lft_);
+ Lease6Ptr lease_server = checkLease(lease_client);
+ // Check that the server recorded the lease.
+ ASSERT_TRUE(lease_server);
+
+ OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(option);
+ ASSERT_TRUE(ia);
+ option = ia->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast<Option6IAPrefix>(option);
+ ASSERT_TRUE(pd_option);
+ option = pd_option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_TRUE(option);
+ Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+ ASSERT_TRUE(pd_exclude);
+ EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix(IOAddress("2001:db8:3::"),
+ 64).toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
+TEST_F(SARRTest, directClientExcludedPrefix) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ directClientExcludedPrefix();
+}
+
+TEST_F(SARRTest, directClientExcludedPrefixMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ directClientExcludedPrefix();
+}
+
+void
+SARRTest::rapidCommitEnable() {
+ Dhcp6Client client;
+ // Configure client to request IA_NA
+ client.requestAddress();
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+ // Perform 2-way exchange.
+ client.useRapidCommit(true);
+ // Include FQDN to trigger generation of name change requests.
+ ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S,
+ "client-name.example.org",
+ Option6ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(client.doSolicit());
+ // Server should have committed a lease.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ // Make sure that the address belongs to the subnet configured.
+ ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+ selectSubnet(lease_client.addr_, ClientClasses()));
+ // Make sure that the server responded with Reply.
+ ASSERT_TRUE(client.getContext().response_);
+ EXPECT_EQ(DHCPV6_REPLY, client.getContext().response_->getType());
+ // Rapid Commit option should be included.
+ EXPECT_TRUE(client.getContext().response_->getOption(D6O_RAPID_COMMIT));
+ // Check that the lease has been committed.
+ Lease6Ptr lease_server = checkLease(lease_client);
+ EXPECT_TRUE(lease_server);
+ // There should be one name change request generated.
+ EXPECT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+}
+
+TEST_F(SARRTest, rapidCommitEnable) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ rapidCommitEnable();
+}
+
+TEST_F(SARRTest, rapidCommitEnableMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ rapidCommitEnable();
+}
+
+void
+SARRTest::rapidCommitNoOption() {
+ Dhcp6Client client;
+ // Configure client to request IA_NA
+ client.requestAddress();
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+ // Include FQDN to test that the server will not create name change
+ // requests when it sends Advertise (Rapid Commit disabled).
+ ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S,
+ "client-name.example.org",
+ Option6ClientFqdn::FULL));
+ ASSERT_NO_THROW(client.doSolicit());
+ // There should be no lease because the server should have responded
+ // with Advertise.
+ ASSERT_EQ(0, client.getLeaseNum());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ EXPECT_EQ(DHCPV6_ADVERTISE, client.getContext().response_->getType());
+ // Make sure that the Rapid Commit option is not included.
+ EXPECT_FALSE(client.getContext().response_->getOption(D6O_RAPID_COMMIT));
+ // There should be no name change request generated.
+ EXPECT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+}
+
+TEST_F(SARRTest, rapidCommitNoOption) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ rapidCommitNoOption();
+}
+
+TEST_F(SARRTest, rapidCommitNoOptionMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ rapidCommitNoOption();
+}
+
+void
+SARRTest::rapidCommitDisable() {
+ Dhcp6Client client;
+ // The subnet assigned to eth1 has Rapid Commit disabled.
+ client.setInterface("eth1");
+ // Configure client to request IA_NA
+ client.requestAddress();
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+ // Send Rapid Commit option to the server.
+ client.useRapidCommit(true);
+ // Include FQDN to test that the server will not create name change
+ // requests when it sends Advertise (Rapid Commit disabled).
+ ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S,
+ "client-name.example.org",
+ Option6ClientFqdn::FULL));
+ ASSERT_NO_THROW(client.doSolicit());
+ // There should be no lease because the server should have responded
+ // with Advertise.
+ ASSERT_EQ(0, client.getLeaseNum());
+ // Make sure that the server responded.
+ ASSERT_TRUE(client.getContext().response_);
+ EXPECT_EQ(DHCPV6_ADVERTISE, client.getContext().response_->getType());
+ // Make sure that the Rapid Commit option is not included.
+ EXPECT_FALSE(client.getContext().response_->getOption(D6O_RAPID_COMMIT));
+ // There should be no name change request generated.
+ EXPECT_EQ(0, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+}
+
+TEST_F(SARRTest, rapidCommitDisable) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ rapidCommitDisable();
+}
+
+TEST_F(SARRTest, rapidCommitDisableMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ rapidCommitDisable();
+}
+
+void
+SARRTest::sarrStats() {
+
+ // Let's use one of the existing configurations and tell the client to
+ // ask for an address.
+ Dhcp6Client client;
+ configure(CONFIGS[1], *client.getServer());
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(2, subnets->size());
+
+ // Check that the tested statistics is initially set to 0
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+ ObservationPtr pkt6_rcvd = mgr.getObservation("pkt6-received");
+ ObservationPtr pkt6_solicit_rcvd = mgr.getObservation("pkt6-solicit-received");
+ ObservationPtr pkt6_adv_sent = mgr.getObservation("pkt6-advertise-sent");
+ ObservationPtr pkt6_request_rcvd = mgr.getObservation("pkt6-request-received");
+ ObservationPtr pkt6_reply_sent = mgr.getObservation("pkt6-reply-sent");
+ ObservationPtr pkt6_sent = mgr.getObservation("pkt6-sent");
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(pkt6_solicit_rcvd);
+ ASSERT_TRUE(pkt6_adv_sent);
+ ASSERT_TRUE(pkt6_request_rcvd);
+ ASSERT_TRUE(pkt6_reply_sent);
+ ASSERT_TRUE(pkt6_sent);
+ EXPECT_EQ(0, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(0, pkt6_solicit_rcvd->getInteger().first);
+ EXPECT_EQ(0, pkt6_adv_sent->getInteger().first);
+ EXPECT_EQ(0, pkt6_request_rcvd->getInteger().first);
+ EXPECT_EQ(0, pkt6_reply_sent->getInteger().first);
+ EXPECT_EQ(0, pkt6_sent->getInteger().first);
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+
+ // All expected statistics must be present now.
+ pkt6_rcvd = mgr.getObservation("pkt6-received");
+ pkt6_solicit_rcvd = mgr.getObservation("pkt6-solicit-received");
+ pkt6_adv_sent = mgr.getObservation("pkt6-advertise-sent");
+ pkt6_request_rcvd = mgr.getObservation("pkt6-request-received");
+ pkt6_reply_sent = mgr.getObservation("pkt6-reply-sent");
+ pkt6_sent = mgr.getObservation("pkt6-sent");
+ ASSERT_TRUE(pkt6_rcvd);
+ ASSERT_TRUE(pkt6_solicit_rcvd);
+ ASSERT_TRUE(pkt6_adv_sent);
+ ASSERT_TRUE(pkt6_request_rcvd);
+ ASSERT_TRUE(pkt6_reply_sent);
+ ASSERT_TRUE(pkt6_sent);
+
+ // They also must have expected values.
+ EXPECT_EQ(2, pkt6_rcvd->getInteger().first);
+ EXPECT_EQ(1, pkt6_solicit_rcvd->getInteger().first);
+ EXPECT_EQ(1, pkt6_adv_sent->getInteger().first);
+ EXPECT_EQ(1, pkt6_request_rcvd->getInteger().first);
+ EXPECT_EQ(1, pkt6_reply_sent->getInteger().first);
+ EXPECT_EQ(2, pkt6_sent->getInteger().first);
+}
+
+TEST_F(SARRTest, sarrStats) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ sarrStats();
+}
+
+TEST_F(SARRTest, sarrStatsMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ sarrStats();
+}
+
+void
+SARRTest::pkt6ReceiveDropStat1() {
+
+ // Dummy server-id (0xff repeated 10 times)
+ std::vector<uint8_t> data(10, 0xff);
+ OptionPtr bogus_srv_id(new Option(Option::V6, D6O_SERVERID, data));
+
+ // Let's use one of the existing configurations and tell the client to
+ // ask for an address.
+ Dhcp6Client client;
+ configure(CONFIGS[1], *client.getServer());
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ client.doSolicit();
+ client.useServerId(bogus_srv_id);
+ client.doRequest();
+
+ // Ok, let's check the statistic. pkt6-receive-drop should be set to 1.
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+
+ ObservationPtr pkt6_recv_drop = mgr.getObservation("pkt6-receive-drop");
+ ASSERT_TRUE(pkt6_recv_drop);
+
+ EXPECT_EQ(1, pkt6_recv_drop->getInteger().first);
+}
+
+TEST_F(SARRTest, pkt6ReceiveDropStat1) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ pkt6ReceiveDropStat1();
+}
+
+TEST_F(SARRTest, pkt6ReceiveDropStat1MultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ pkt6ReceiveDropStat1();
+}
+
+void
+SARRTest::pkt6ReceiveDropStat2() {
+
+ // Let's use one of the existing configurations and tell the client to
+ // ask for an address.
+ Dhcp6Client client;
+ configure(CONFIGS[1], *client.getServer());
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ client.setDestAddress(asiolink::IOAddress("2001:db8::1")); // Pretend it's unicast
+ client.doSolicit();
+
+ // Ok, let's check the statistic. pkt6-receive-drop should be set to 1.
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+
+ ObservationPtr pkt6_recv_drop = mgr.getObservation("pkt6-receive-drop");
+ ASSERT_TRUE(pkt6_recv_drop);
+
+ EXPECT_EQ(1, pkt6_recv_drop->getInteger().first);
+}
+
+TEST_F(SARRTest, pkt6ReceiveDropStat2) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ pkt6ReceiveDropStat2();
+}
+
+TEST_F(SARRTest, pkt6ReceiveDropStat2MultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ pkt6ReceiveDropStat2();
+}
+
+void
+SARRTest::pkt6ReceiveDropStat3() {
+
+ // Let's use one of the existing configurations and tell the client to
+ // ask for an address.
+ Dhcp6Client client;
+ configure(CONFIGS[1], *client.getServer());
+ client.setInterface("eth1");
+ client.requestAddress();
+
+ // Let's send our client-id as server-id. That will result in the
+ // packet containing the client-id twice. That should cause RFCViolation
+ // exception.
+ client.useServerId(client.getClientId());
+ client.doSolicit();
+
+ // Ok, let's check the statistic. pkt6-receive-drop should be set to 1.
+ using namespace isc::stats;
+ StatsMgr& mgr = StatsMgr::instance();
+
+ ObservationPtr pkt6_recv_drop = mgr.getObservation("pkt6-receive-drop");
+ ASSERT_TRUE(pkt6_recv_drop);
+
+ EXPECT_EQ(1, pkt6_recv_drop->getInteger().first);
+}
+
+TEST_F(SARRTest, pkt6ReceiveDropStat3) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ pkt6ReceiveDropStat3();
+}
+
+TEST_F(SARRTest, pkt6ReceiveDropStat3MultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ pkt6ReceiveDropStat3();
+}
+
+void
+SARRTest::reservationModeOutOfPool() {
+ // Create the first client for which we have a reservation out of the
+ // dynamic pool.
+ Dhcp6Client client;
+ configure(CONFIGS[4], *client.getServer());
+ client.setDUID("aa:bb:cc:dd:ee:ff");
+ client.setInterface("eth0");
+ client.requestAddress(1234, IOAddress("2001:db8:1::3"));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+
+ Lease6 lease = client.getLease(0);
+ // Check that the server allocated the reserved address.
+ ASSERT_EQ("2001:db8:1::20", lease.addr_.toText());
+
+ client.clearConfig();
+ // Create another client which has a reservation within the pool.
+ // The server should ignore this reservation in the current mode.
+ client.setDUID("11:22:33:44:55:66");
+ // This client is requesting a different address than reserved. The
+ // server should allocate this address to the client.
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+
+ lease = client.getLease(0);
+ // Check that the requested address was assigned.
+ ASSERT_EQ("2001:db8:1::3", lease.addr_.toText());
+}
+
+TEST_F(SARRTest, reservationModeOutOfPool) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ reservationModeOutOfPool();
+}
+
+TEST_F(SARRTest, reservationModeOutOfPoolMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ reservationModeOutOfPool();
+}
+
+void
+SARRTest::reservationIgnoredInOutOfPoolMode() {
+ // Create the first client for which we have a reservation out of the
+ // dynamic pool.
+ Dhcp6Client client;
+ configure(CONFIGS[4], *client.getServer());
+ client.setDUID("12:34:56:78:9A:BC");
+ client.setInterface("eth0");
+ client.requestAddress(1234, IOAddress("2001:db8:1::5"));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+
+ Lease6 lease = client.getLease(0);
+ // Check that the server allocated the reserved address.
+ ASSERT_EQ("2001:db8:1::5", lease.addr_.toText());
+}
+
+TEST_F(SARRTest, reservationIgnoredInOutOfPoolMode) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ reservationIgnoredInOutOfPoolMode();
+}
+
+TEST_F(SARRTest, reservationIgnoredInOutOfPoolModeMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ reservationIgnoredInOutOfPoolMode();
+}
+
+void
+SARRTest::randomAddressAllocation() {
+ // Create the base client and server configuration.
+ Dhcp6Client client;
+ configure(CONFIGS[5], *client.getServer());
+
+ // Record what addresses have been allocated and in what order.
+ std::set<std::string> allocated_na_set;
+ std::vector<IOAddress> allocated_na_vector;
+ std::set<std::string> allocated_pd_set;
+ std::vector<IOAddress> allocated_pd_vector;
+ // Simulate allocations from different clients.
+ for (auto i = 0; i < 30; ++i) {
+ // Create a client from the base client.
+ Dhcp6Client next_client(client.getServer());
+ next_client.requestAddress();
+ next_client.requestPrefix();
+ // Run 4-way exchange.
+ ASSERT_NO_THROW(next_client.doSARR());
+ // We should have one IA_NA and one IA_PD.
+ auto leases_na = next_client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_na.size());
+ auto leases_pd = next_client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_pd.size());
+ // Remember allocated address and delegated prefix uniqueness
+ // and order.
+ allocated_na_set.insert(leases_na[0].toText());
+ allocated_na_vector.push_back(leases_na[0].addr_);
+ allocated_pd_set.insert(leases_pd[0].toText());
+ allocated_pd_vector.push_back(leases_pd[0].addr_);
+ }
+ // Make sure that we have 30 distinct allocations for each lease type.
+ ASSERT_EQ(30, allocated_na_set.size());
+ ASSERT_EQ(30, allocated_na_vector.size());
+ ASSERT_EQ(30, allocated_pd_set.size());
+ ASSERT_EQ(30, allocated_pd_vector.size());
+
+ // Make sure that the addresses are not allocated iteratively.
+ int consecutives = 0;
+ for (auto i = 1; i < allocated_na_vector.size(); ++i) {
+ // Record the cases when the previously allocated address is
+ // lower by 1 (iterative allocation). Some cases like this are
+ // possible even with the random allocation but they should be
+ // very rare.
+ if (IOAddress::increase(allocated_na_vector[i-1]) == allocated_na_vector[i]) {
+ ++consecutives;
+ }
+ }
+ EXPECT_LT(consecutives, 10);
+
+ // Make sure that delegated prefixes have been allocated iteratively.
+ consecutives = 0;
+ for (auto i = 1; i < allocated_pd_vector.size(); ++i) {
+ if (IOAddress::subtract(allocated_pd_vector[i], allocated_pd_vector[i-1]) == IOAddress("0:0:0:1::")) {
+ ++consecutives;
+ }
+ }
+ EXPECT_EQ(29, consecutives);
+}
+
+TEST_F(SARRTest, randomAddressAllocation) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ randomAddressAllocation();
+}
+
+TEST_F(SARRTest, randomAddressAllocationMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ randomAddressAllocation();
+}
+
+void
+SARRTest::randomPrefixAllocation() {
+ // Create the base client and server configuration.
+ Dhcp6Client client;
+ configure(CONFIGS[6], *client.getServer());
+
+ // Record what addresses have been allocated and in what order.
+ std::set<std::string> allocated_na_set;
+ std::vector<IOAddress> allocated_na_vector;
+ std::set<std::string> allocated_pd_set;
+ std::vector<IOAddress> allocated_pd_vector;
+ // Simulate allocations from different clients.
+ for (auto i = 0; i < 30; ++i) {
+ // Create a client from the base client.
+ Dhcp6Client next_client(client.getServer());
+ next_client.requestAddress();
+ next_client.requestPrefix();
+ // Run 4-way exchange.
+ ASSERT_NO_THROW(next_client.doSARR());
+ // We should have one IA_NA and one IA_PD.
+ auto leases_na = next_client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_na.size());
+ auto leases_pd = next_client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_pd.size());
+ // Remember allocated address and delegated prefix uniqueness
+ // and order.
+ allocated_na_set.insert(leases_na[0].toText());
+ allocated_na_vector.push_back(leases_na[0].addr_);
+ allocated_pd_set.insert(leases_pd[0].toText());
+ allocated_pd_vector.push_back(leases_pd[0].addr_);
+ }
+ // Make sure that we have 30 distinct allocations for each lease type.
+ ASSERT_EQ(30, allocated_na_set.size());
+ ASSERT_EQ(30, allocated_na_vector.size());
+ ASSERT_EQ(30, allocated_pd_set.size());
+ ASSERT_EQ(30, allocated_pd_vector.size());
+
+ // Make sure that the addresses have been allocated iteratively.
+ int consecutives = 0;
+ for (auto i = 1; i < allocated_na_vector.size(); ++i) {
+ // Record the cases when the previously allocated address is
+ // lower by 1 (iterative allocation).
+ if (IOAddress::increase(allocated_na_vector[i-1]) == allocated_na_vector[i]) {
+ ++consecutives;
+ }
+ }
+
+ // Make sure that addresses have been allocated iteratively.
+ EXPECT_EQ(29, consecutives);
+
+ // Make sure that delegated prefixes have been allocated randomly.
+ consecutives = 0;
+ for (auto i = 1; i < allocated_pd_vector.size(); ++i) {
+ if (IOAddress::subtract(allocated_pd_vector[i], allocated_pd_vector[i-1]) == IOAddress("0:0:0:1::")) {
+ ++consecutives;
+ }
+ }
+ EXPECT_LT(consecutives, 10);
+}
+
+TEST_F(SARRTest, randomPrefixAllocation) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ randomPrefixAllocation();
+}
+
+TEST_F(SARRTest, randomPrefixAllocationMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ randomPrefixAllocation();
+}
+
+void
+SARRTest::leaseCaching() {
+ // Configure a DHCP client.
+ Dhcp6Client client;
+
+ // Configure a DHCP server.
+ configure(CONFIGS[7], *client.getServer());
+
+ // Statistics should have default values.
+ checkStat("v6-ia-na-lease-reuses", 1, 0);
+ checkStat("subnet[1].v6-ia-na-lease-reuses", 1, 0);
+ checkStat("subnet[2].v6-ia-na-lease-reuses", 1, 0);
+ checkStat("v6-ia-pd-lease-reuses", 1, 0);
+ checkStat("subnet[1].v6-ia-pd-lease-reuses", 1, 0);
+ checkStat("subnet[2].v6-ia-pd-lease-reuses", 1, 0);
+
+ // Append IAADDR and IAPREFIX options to the client's message.
+ ASSERT_NO_THROW(client.requestAddress(1234, asiolink::IOAddress("2001:db8::10")));
+ ASSERT_NO_THROW(client.requestPrefix(5678, 32, asiolink::IOAddress("2001:db8:1::")));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Server should have assigned an address and a prefix.
+ ASSERT_EQ(2, client.getLeaseNum());
+
+ // The server should respect the hints.
+ Lease6 lease_client(client.getLease(0));
+ EXPECT_EQ("2001:db8::10", lease_client.addr_.toText());
+ EXPECT_EQ(128, lease_client.prefixlen_);
+ Lease6Ptr lease_server(checkLease(lease_client));
+ EXPECT_TRUE(lease_server);
+ lease_client = client.getLease(1);
+ EXPECT_EQ("2001:db8:1::", lease_client.addr_.toText());
+ EXPECT_EQ(96, lease_client.prefixlen_);
+ lease_server = checkLease(lease_client);
+ EXPECT_TRUE(lease_server);
+
+ // Check statistics.
+ checkStat("v6-ia-na-lease-reuses", 1, 0);
+ checkStat("subnet[1].v6-ia-na-lease-reuses", 1, 0);
+ checkStat("subnet[2].v6-ia-na-lease-reuses", 1, 0);
+ checkStat("v6-ia-pd-lease-reuses", 1, 0);
+ checkStat("subnet[1].v6-ia-pd-lease-reuses", 1, 0);
+ checkStat("subnet[2].v6-ia-pd-lease-reuses", 1, 0);
+
+ // Request the same prefix with a different length. The server should
+ // return an existing lease.
+ client.clearRequestedIAs();
+ ASSERT_NO_THROW(client.requestAddress(1234, asiolink::IOAddress("2001:db8::10")));
+ ASSERT_NO_THROW(client.requestPrefix(5678, 80, IOAddress("2001:db8:1::")));
+ ASSERT_NO_THROW(client.doSARR());
+ ASSERT_EQ(2, client.getLeaseNum());
+ lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8::10", lease_client.addr_.toText());
+ EXPECT_EQ(128, lease_client.prefixlen_);
+ lease_client = client.getLease(1);
+ EXPECT_EQ("2001:db8:1::", lease_client.addr_.toText());
+ EXPECT_EQ(96, lease_client.prefixlen_);
+
+ // Check statistics.
+ checkStat("v6-ia-na-lease-reuses", 2, 1);
+ checkStat("subnet[1].v6-ia-na-lease-reuses", 2, 1);
+ checkStat("subnet[2].v6-ia-na-lease-reuses", 1, 0);
+ checkStat("v6-ia-pd-lease-reuses", 2, 1);
+ checkStat("subnet[1].v6-ia-pd-lease-reuses", 2, 1);
+ checkStat("subnet[2].v6-ia-pd-lease-reuses", 1, 0);
+
+ // Try to request another prefix. The client should still get the existing
+ // lease.
+ client.clearRequestedIAs();
+ ASSERT_NO_THROW(client.requestAddress(1234, asiolink::IOAddress("2001:db8::10")));
+ ASSERT_NO_THROW(client.requestPrefix(5678, 64, IOAddress("2001:db8:2::")));
+ ASSERT_NO_THROW(client.doRequest());
+ ASSERT_EQ(2, client.getLeaseNum());
+ lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8::10", lease_client.addr_.toText());
+ EXPECT_EQ(128, lease_client.prefixlen_);
+ lease_client = client.getLease(1);
+ EXPECT_EQ("2001:db8:1::", lease_client.addr_.toText());
+ EXPECT_EQ(96, lease_client.prefixlen_);
+
+ // Check statistics.
+ checkStat("v6-ia-na-lease-reuses", 3, 2);
+ checkStat("subnet[1].v6-ia-na-lease-reuses", 3, 2);
+ checkStat("subnet[2].v6-ia-na-lease-reuses", 1, 0);
+ checkStat("v6-ia-pd-lease-reuses", 3, 2);
+ checkStat("subnet[1].v6-ia-pd-lease-reuses", 3, 2);
+ checkStat("subnet[2].v6-ia-pd-lease-reuses", 1, 0);
+}
+
+TEST_F(SARRTest, leaseCaching) {
+ Dhcpv6SrvMTTestGuard guard(*this, false);
+ leaseCaching();
+}
+
+TEST_F(SARRTest, leaseCachingMultiThreading) {
+ Dhcpv6SrvMTTestGuard guard(*this, true);
+ leaseCaching();
+}
+
+/// @brief Checks the value of a statistic.
+///
+/// @param name name of statistic to check
+/// @param expected_size expected number of statistic samples
+/// @param expected_value expected value of the latest statistic sample
+void SARRTest::checkStat(string const& name,
+ size_t const expected_size,
+ int64_t const expected_value) {
+ ObservationPtr const stats(StatsMgr::instance().getObservation(name));
+ ASSERT_TRUE(stats) << "no such stat: " << name;
+ EXPECT_EQ(expected_size, stats->getSize())
+ << name << " stat has wrong size: found " << stats->getSize() << ", expected "
+ << expected_size;
+ EXPECT_EQ(expected_value, stats->getInteger().first)
+ << name << " stat has wrong value: found " << stats->getInteger().first << ", expected "
+ << expected_value;
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/shared_network_unittest.cc b/src/bin/dhcp6/tests/shared_network_unittest.cc
new file mode 100644
index 0000000..aaa7ff1
--- /dev/null
+++ b/src/bin/dhcp6/tests/shared_network_unittest.cc
@@ -0,0 +1,3052 @@
+// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <cc/command_interpreter.h>
+#include <stats/stats_mgr.h>
+#include <boost/pointer_cast.hpp>
+#include <functional>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+
+namespace {
+
+/// @brief Array of server configurations used throughout the tests.
+const char* NETWORKS_CONFIG[] = {
+// Configuration #0.
+// - one shared network with two subnets, each with address and prefix pools
+// - one plain subnet
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"comment\": \"example\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"4000::\","
+ " \"prefix-len\": 96,"
+ " \"delegated-len\": 96"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"5000::\","
+ " \"prefix-len\": 96,"
+ " \"delegated-len\": 96"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"3000::/96\","
+ " \"id\": 1000,"
+ " \"interface\": \"eth0\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"3000::1 - 3000::1\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #1.
+// - one shared network with relay-ip specified and one subnet with address pool
+// - one plain subnet with relay-ip specified and one address pool
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"3001::1\""
+ " },"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 1000,"
+ " \"relay\": {"
+ " \"ip-address\": \"3001::2\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #2.
+// - two classes specified
+// - one shared network with two subnets (the first has class restrictions)
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ],"
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #3.
+// - two classes defined
+// - one shared network with two subnets, each with a different class restriction
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ],"
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ],"
+ " \"client-class\": \"b-devices\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #4.
+// - one shared network with two subnets. Each subnet has:
+// - address and prefix pool
+// - reservation
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"4000::\","
+ " \"prefix-len\": 96,"
+ " \"delegated-len\": 96"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::1 - 2001:db8:2::64\""
+ " }"
+ " ],"
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"5000::\","
+ " \"prefix-len\": 96,"
+ " \"delegated-len\": 112"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:11:22:33:44:55:66\","
+ " \"ip-addresses\": [ \"2001:db8:2::28\" ],"
+ " \"prefixes\": [ \"5000::8:0000/112\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #5 (similar to #4, but without prefix pool and using different
+// DUID for reservations)
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:11:22:33:44:55:66\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::1 - 2001:db8:2::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:2::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #6.
+// - one class
+// - one shared network with two subnets:
+// - first subnet has address pool, class restriction and a reservation
+// - second subnet has just an address pool
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"client-class\": \"a-devices\","
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::16 - 2001:db8:2::16\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #7.
+// - option defined on global level
+// - one shared network with two options and two subnets
+// - the first subnet has its own options defined as well
+// - plain subnet with its own options
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"nis-servers\","
+ " \"data\": \"3000::20\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"3001::21\""
+ " },"
+ " {"
+ " \"name\": \"nisp-servers\","
+ " \"data\": \"3002::34\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"sntp-servers\","
+ " \"data\": \"4004::22\""
+ " },"
+ " {"
+ " \"name\": \"nisp-servers\","
+ " \"data\": \"3003::33\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " \{"
+ " \"subnet\": \"3000::/96\","
+ " \"id\": 1000,"
+ " \"interface\": \"eth0\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"nisp-servers\","
+ " \"data\": \"4000::5\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"3000::1 - 3000::1\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #8.
+// - two shared networks
+// - first network with two subnets, each with its own address pool
+// - second network with two subnets, each with its own address pool
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"interface\": \"eth0\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:3::/64\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:3::20 - 2001:db8:3::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:4::/64\","
+ " \"id\": 10000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:4::20 - 2001:db8:4::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #9 (similar to #8, but with relay-ip addresses specified)
+// - two shared networks, each with relay IP addresses specified
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"relay\": {"
+ " \"ip-address\": \"3000::1\""
+ " },"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"relay\": {"
+ " \"ip-address\": \"3000::2\""
+ " },"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:3::/64\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:3::20 - 2001:db8:3::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:4::/64\","
+ " \"id\": 10000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:4::20 - 2001:db8:4::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #10.
+// - one class with an option (and not test expression)
+// - one shared network with two subnets
+// - first subnet with one address pool
+// - second with a pool and reservation that assigns client to a class
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"class-with-dns-servers\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::50\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::30\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:11:22:33:44:55:66\","
+ " \"ip-addresses\": [ \"2001:db8:2::20\" ],"
+ " \"hostname\": \"test.example.org\","
+ " \"client-classes\": [ \"class-with-dns-servers\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #11.
+// - two classes defined
+// - two shared networks, each with one subnet and class restriction
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"client-class\": \"a-devices\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"interface\": \"eth1\","
+ " \"client-class\": \"b-devices\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #12.
+// - one client class
+// - one shared network with two subnets, the second subnet has class restriction
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ],"
+ " \"client-class\": \"b-devices\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #13.
+// - one shared network, with two subnets, each with the same relay-ip addresses
+// - one plain subnet, with its own (different) relay-ip address
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"relay\": {"
+ " \"ip-address\": \"3001::1\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"relay\": {"
+ " \"ip-address\": \"3001::1\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:3::/64\","
+ " \"id\": 1000,"
+ " \"relay\": {"
+ " \"ip-address\": \"3001::2\""
+ " },"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:3::20 - 2001:db8:3::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #14.
+// - one share network with interface-id specified and one subnet
+// - one plain subnet, with its own interface-id
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface-id\": \"vlan10\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 1000,"
+ " \"interface-id\": \"vlan1000\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #15.
+// - one shared network, with two subnets, each with the same interface-id
+// - one plain subnet, with its own interface-id
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"interface-id\": \"vlan10\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 11,"
+ " \"interface-id\": \"vlan10\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::10 - 2001:db8:2::10\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:2::1/64\","
+ " \"id\": 1000,"
+ " \"interface-id\": \"vlan1000\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #16.
+// - one shared network with three subnets, each with different option value
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"4004::22\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"5555::33\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:3::/64\","
+ " \"id\": 1000,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"1234::23\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:3::20 - 2001:db8:3::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #17.
+// - one shared network with two subnets, both have rapid-commit enabled
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"rapid-commit\": true,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"rapid-commit\": true,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+
+// Configuration #18.
+// - one shared network with rapid-commit enabled
+// - two subnets (which should derive the rapid-commit setting)
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"rapid-commit\": true,"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"id\": 100,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:2::20 - 2001:db8:2::20\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #19.
+// - one shared network with one subnet and two pools (the first has
+// class restrictions)
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #20.
+// - one shared network with one subnet and two pools (each with class
+// restriction)
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\","
+ " \"client-class\": \"b-devices\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #21.
+// - one plain subnet with two pools (the first has class restrictions)
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"interface\": \"eth1\","
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #22.
+// - one plain subnet with two pools (each with class restriction)
+ "{"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"a-devices\","
+ " \"test\": \"option[1234].hex == 0x0001\""
+ " },"
+ " {"
+ " \"name\": \"b-devices\","
+ " \"test\": \"option[1234].hex == 0x0002\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
+ " \"client-class\": \"a-devices\""
+ " },"
+ " {"
+ " \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\","
+ " \"client-class\": \"b-devices\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+
+// Configuration #23.
+// - a shared network with two subnets
+// - first subnet uses the FLQ allocator
+// - second subnet uses the random allocator
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"id\": 100, "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-allocator\": \"flq\","
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"2001:db8:1::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 68"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"id\": 10, "
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"pd-allocator\": \"random\","
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"2001:db8:2::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 68"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}",
+// Configuration #24.
+// - a shared network with two subnets
+// - first subnet uses the random allocator
+// - second subnet uses the FLQ allocator
+ "{"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"id\": 100, "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"pd-allocator\": \"random\","
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"2001:db8:1::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 68"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"id\": 10, "
+ " \"subnet\": \"2001:db8:2::/64\","
+ " \"pd-allocator\": \"flq\","
+ " \"pd-pools\": ["
+ " {"
+ " \"prefix\": \"2001:db8:2::\","
+ " \"prefix-len\": 64,"
+ " \"delegated-len\": 68"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}"
+};
+
+/// @Brief Test fixture class for DHCPv6 server using shared networks.
+class Dhcpv6SharedNetworkTest : public Dhcpv6SrvTest {
+public:
+
+ /// @brief Indicates how test functions should check presence of a lease on
+ /// the server.
+ enum class LeaseOnServer{
+ MUST_EXIST,
+ MUST_NOT_EXIST,
+ };
+
+ /// @brief Constructor.
+ Dhcpv6SharedNetworkTest()
+ : Dhcpv6SrvTest(),
+ iface_mgr_test_config_(true) {
+ IfaceMgr::instance().openSockets6();
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Verifies lease statistics against values held by StatsMgr.
+ ///
+ /// This method retrieves lease statistics from the database and then compares it
+ /// against values held by the StatsMgr. The compared statistics are number of
+ /// assigned addresses and prefixes for a subnet.
+ void verifyAssignedStats() {
+ LeaseStatsQueryPtr query = LeaseMgrFactory::instance().startLeaseStatsQuery6();
+ LeaseStatsRow row;
+ while (query->getNextRow(row)) {
+ // Only check valid leases.
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ std::string stat_name;
+ // Addresses
+ if (row.lease_type_ == Lease::TYPE_NA) {
+ stat_name = StatsMgr::generateName("subnet", row.subnet_id_,
+ "assigned-nas");
+ // Prefixes.
+ } else if (row.lease_type_ == Lease::TYPE_PD) {
+ stat_name = StatsMgr::generateName("subnet", row.subnet_id_,
+ "assigned-pds");
+ }
+
+ // Number of leases held in the database should match the information
+ // held in the Stats Manager.
+ if (!stat_name.empty()) {
+ ASSERT_EQ(row.state_count_, getStatsAssignedLeases(stat_name))
+ << "test failed for statistic " << stat_name;
+ }
+ }
+ }
+ }
+
+ /// @brief Retrieves statistics for a subnet.
+ ///
+ /// @param stat_name Name of the statistics to be retrieved, e.g. subnet[1234].assigned-nas.
+ /// @return Number of assigned leases for a subnet.
+ int64_t getStatsAssignedLeases(const std::string& stat_name) const {
+ // Top element is a map with a subnet[id].assigned-addresses parameter.
+ ConstElementPtr top_element = StatsMgr::instance().get(stat_name);
+ if (top_element && (top_element->getType() == Element::map)) {
+ // It contains two lists (nested).
+ ConstElementPtr first_list = top_element->get(stat_name);
+ if (first_list && (first_list->getType() == Element::list) &&
+ (first_list->size() > 0)) {
+ // Get the nested list which should have two elements, of which first
+ // is the statistics value we're looking for.
+ ConstElementPtr second_list = first_list->get(0);
+ if (second_list && (second_list->getType() == Element::list)) {
+ ConstElementPtr addresses_element = second_list->get(0);
+ if (addresses_element && (addresses_element->getType() == Element::integer)) {
+ return (addresses_element->intValue());
+ }
+ }
+ }
+ }
+
+ // Statistics invalid or not found.
+ return (0);
+ }
+
+ /// @brief Launches specific operation and verifies lease statistics before and
+ /// after this operation.
+ ///
+ /// @param operation Operation to be launched.
+ void testAssigned(const std::function<void()>& operation) {
+ ASSERT_NO_FATAL_FAILURE(verifyAssignedStats());
+ operation();
+ ASSERT_NO_FATAL_FAILURE(verifyAssignedStats());
+ }
+
+ /// @brief Returns subnet having specified address or prefix in range.
+ ///
+ /// @param type Resource type: NA or PD.
+ /// @param resource Address or prefix for which subnet is being searched.
+ /// @return Pointer to the subnet having an resource in range or null pointer
+ /// if no subnet found.
+ Subnet6Ptr getConfiguredSubnet(const Lease::Type& type, const IOAddress& resource) const {
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
+ const Subnet6Collection* subnets = cfg->getAll();
+ for (auto subnet_it = subnets->cbegin(); subnet_it != subnets->cend(); ++subnet_it) {
+ if ((*subnet_it)->inPool(type, resource)) {
+ return (*subnet_it);
+ }
+ }
+
+ return (Subnet6Ptr());
+
+ }
+
+ /// @brief Check if client has a lease for the specified address.
+ ///
+ /// Apart from checking whether the client has got the lease it also
+ /// checks whether this lease is stored in the lease database and that
+ /// it holds valid subnet identifier.
+ ///
+ /// @param client Reference to the client.
+ /// @param address Leased address.
+ /// @param lease_on_server Specify whether the lease should be also present or
+ /// absent in the lease database.
+ ///
+ /// @return true if the lease for the client has been found both in the
+ /// database and in the server's response.
+ bool hasLeaseForAddress(Dhcp6Client& client, const IOAddress& address,
+ const LeaseOnServer& lease_on_server = LeaseOnServer::MUST_EXIST) {
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, address);
+ // Sanity check the lease.
+ if (lease) {
+ Subnet6Ptr subnet = getConfiguredSubnet(Lease::TYPE_NA, address);
+ if (!subnet) {
+ ADD_FAILURE() << "unable to find configured subnet for the"
+ " address " << address;
+ return (false);
+ }
+ // Make sure that the subnet id is not messed up in the lease.
+ if (subnet->getID() != lease->subnet_id_) {
+ ADD_FAILURE() << "invalid subnet identifier found in the lease for"
+ " address " << address << ", expected " << subnet->getID()
+ << ", got " << lease->subnet_id_;
+ return (false);
+ }
+ }
+ return ((((lease_on_server == LeaseOnServer::MUST_EXIST) && lease) ||
+ ((lease_on_server == LeaseOnServer::MUST_NOT_EXIST) && !lease)) &&
+ client.hasLeaseForAddress(address));
+ }
+
+ /// @brief Check if client has a lease for the specified prefix.
+ ///
+ /// Apart from checking whether the client has got the lease it also
+ /// checks whether this lease is stored in the lease database and that
+ /// it holds valid subnet identifier.
+ ///
+ /// @param client Reference to the client.
+ /// @param prefix Leased prefix.
+ /// @param prefix_len Leased prefix length.
+ /// @param lease_on_server Specify whether the lease should be also present or
+ /// absent in the lease database.
+ ///
+ /// @return true if the lease for the client has been found both in the
+ /// database and in the server's response.
+ bool hasLeaseForPrefix(Dhcp6Client& client, const IOAddress& prefix,
+ const uint8_t prefix_len, const IAID& iaid,
+ const LeaseOnServer& lease_on_server = LeaseOnServer::MUST_EXIST) {
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+
+ // Sanity check the lease.
+ if (lease) {
+ Subnet6Ptr subnet = getConfiguredSubnet(Lease::TYPE_PD, prefix);
+ if (!subnet) {
+ ADD_FAILURE() << "unable to find configured subnet for the"
+ " prefix " << prefix;
+ return (false);
+ }
+ // Make sure that the subnet id is not messed up in the lease.
+ if (subnet->getID() != lease->subnet_id_) {
+ ADD_FAILURE() << "invalid subnet identifier found in the lease for"
+ " prefix " << prefix;
+ return (false);
+ }
+ }
+
+ return ((((lease_on_server == LeaseOnServer::MUST_EXIST) && lease &&
+ (lease->prefixlen_ = prefix_len) && (lease->iaid_ == iaid)) ||
+ ((lease_on_server == LeaseOnServer::MUST_NOT_EXIST) && !lease)) &&
+ client.hasLeaseForPrefix(prefix, prefix_len, iaid));
+ }
+
+ /// @brief Check if client has a lease belonging to address range.
+ ///
+ /// Apart from checking whether the client has got the lease it also
+ /// checks whether this lease is stored in the lease database.
+ ///
+ /// @param client Reference to the client.
+ /// @param first Lower bound of the address range.
+ /// @param last Upper bound of the address range.
+ /// @param lease_on_server Specify whether the lease should be also present or
+ /// absent in the lease database.
+ bool hasLeaseForAddressRange(Dhcp6Client& client, const IOAddress& first, const IOAddress& last,
+ const LeaseOnServer& lease_on_server = LeaseOnServer::MUST_EXIST) {
+ std::vector<Lease6> leases = client.getLeasesByAddressRange(first, last);
+ for (auto lease_it = leases.cbegin(); lease_it != leases.cend(); ++lease_it) {
+ // Take into account only valid leases.
+ if (lease_it->valid_lft_ == 0) {
+ continue;
+ }
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, lease_it->addr_);
+ if ((lease && (lease_on_server == LeaseOnServer::MUST_NOT_EXIST)) ||
+ (!lease && (lease_on_server == LeaseOnServer::MUST_EXIST))) {
+ return (false);
+ }
+ }
+
+ return (!leases.empty());
+ }
+
+ /// @brief Check if client has a lease belonging to a prefix pool.
+ ///
+ /// Apart from checking whether the client has got the lease it also
+ /// checks whether this lease is stored in the lease database.
+ ///
+ /// @param client Reference to the client.
+ /// @param prefix Pool prefix.
+ /// @param prefix_len Prefix length.
+ /// @param delegated_len Delegated prefix length.
+ /// @param lease_on_server Specify whether the lease should be also present or
+ /// absent in the lease database.
+ ///
+ /// @return true if client has a lease belonging to specified pool,
+ /// false otherwise.
+ bool hasLeaseForPrefixPool(Dhcp6Client& client, const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len, const uint8_t delegated_len,
+ const LeaseOnServer& lease_on_server = LeaseOnServer::MUST_EXIST) {
+ std::vector<Lease6> leases = client.getLeasesByPrefixPool(prefix, prefix_len, delegated_len);
+
+ for (auto lease_it = leases.cbegin(); lease_it != leases.cend(); ++lease_it) {
+ // Take into account only valid leases.
+ if (lease_it->valid_lft_ == 0) {
+ continue;
+ }
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, lease_it->addr_);
+ if ((lease && (lease->prefixlen_ == lease->prefixlen_) &&
+ (lease_on_server == LeaseOnServer::MUST_NOT_EXIST)) ||
+ (!lease && (lease_on_server == LeaseOnServer::MUST_EXIST))) {
+ return (false);
+ }
+ }
+
+ return (!leases.empty());
+
+ }
+
+ /// @brief Tests that for a given configuration the rapid-commit works (or not)
+ ///
+ /// The provided configuration is expected to be able to handle two clients.
+ /// The second parameter governs whether rapid-commit is expected to be enabled
+ /// or disabled. Third and fourth parameters are text representations of expected
+ /// leases to be assigned (if rapid-commit is enabled)
+ ///
+ /// @param config - text version of the configuration to be tested
+ /// @param enabled - true = rapid-commit is expected to work
+ /// @param exp_addr1 - an address the first client is expected to get (if
+ /// rapid-commit is enabled).
+ /// @param exp_addr2 - an address the second client is expected to get (if
+ /// rapid-commit is enabled).
+ void testRapidCommit(const std::string& config, bool enabled,
+ const std::string& exp_addr1,
+ const std::string& exp_addr2) {
+
+ // Create client #1. This clients wants to use rapid-commit.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+ client1.useRapidCommit(true);
+
+ Dhcp6Client client2;
+ client2.setInterface("eth1");
+ client2.useRapidCommit(true);
+
+ // Configure the server with a shared network.
+ ASSERT_NO_FATAL_FAILURE(configure(config, *client1.getServer()));
+
+ // Ok, client should have one
+ EXPECT_EQ(0, client1.getLeaseNum());
+
+ // Client #1 should be assigned an address from shared network. The first
+ // subnet has rapid-commit enabled, so the address should be assigned.
+ // We provide a hint for this allocation to make sure that the address
+ // from the first subnet is allocated. In theory, an address from the
+ // second subnet could be allocated as well if the hint was not provided.
+ IOAddress requested_address = exp_addr1.empty() ? IOAddress::IPV6_ZERO_ADDRESS() :
+ IOAddress(exp_addr1);
+ ASSERT_NO_THROW(client1.requestAddress(0xabca0, requested_address));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSolicit());
+ });
+
+ // Make sure something was sent back.
+ ASSERT_TRUE(client1.getContext().response_);
+
+ if (enabled) {
+ // rapid-commit enabled.
+
+ // Make sure that REPLY was sent back.
+ EXPECT_EQ(DHCPV6_REPLY, client1.getContext().response_->getType());
+
+ // Just make sure the client didn't get an address.
+ EXPECT_TRUE(hasLeaseForAddress(client1, IOAddress(exp_addr1),
+ LeaseOnServer::MUST_EXIST));
+ } else {
+ // rapid-commit disabled.
+
+ // Make sure that ADVERTISE was sent back.
+ EXPECT_EQ(DHCPV6_ADVERTISE, client1.getContext().response_->getType());
+
+ // And that it doesn't have any leases.
+ EXPECT_EQ(0, client1.getLeaseNum());
+ }
+
+ // Create client #2. This client behaves the same as the first one, but the
+ // first subnet is already full (it's a really small subnet) and the second
+ // subnet does not allow rapid-commit.
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSolicit());
+ });
+
+ // Make sure something was sent back.
+ ASSERT_TRUE(client2.getContext().response_);
+
+ if (enabled) {
+ // rapid-commit enabled.
+
+ // Make sure that REPLY was sent back.
+ EXPECT_EQ(DHCPV6_REPLY, client2.getContext().response_->getType());
+
+ // Just make sure the client didn't get an address.
+ EXPECT_TRUE(hasLeaseForAddress(client2, IOAddress(exp_addr2),
+ LeaseOnServer::MUST_EXIST));
+ } else {
+ // rapid-commit disabled.
+
+ // Make sure that ADVERTISE was sent back.
+ EXPECT_EQ(DHCPV6_ADVERTISE, client1.getContext().response_->getType());
+
+ // And that it doesn't have any leases.
+ EXPECT_EQ(0, client1.getLeaseNum());
+ }
+ }
+
+ /// @brief Check precedence.
+ ///
+ /// @param config the configuration.
+ /// @param ns_address expected name server address.
+ void testPrecedence(const std::string& config, const std::string& ns_address) {
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers.
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration.
+ configure(config, *client.getServer());
+
+ // Perform SARR.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response.
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option.
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ(ns_address, addrs[0].toText());
+ }
+
+ // @brief Test that different allocator types can be used within a shared network.
+ //
+ // All available prefixes should be delegated from the subnets belonging to
+ // the shared network.
+ //
+ /// @param config server configuration that should contain a shared network with
+ /// two subnets. Each subnet should contain a prefix pool with 16 prefixes.
+ void testDifferentAllocatorsInNetwork(const std::string& config) {
+ // Create the base client and server configuration.
+ Dhcp6Client client;
+ ASSERT_NO_FATAL_FAILURE(configure(config, *client.getServer()));
+
+ // Record what prefixes have been allocated.
+ std::set<std::string> allocated_set;
+
+ // Simulate allocations from different clients.
+ for (auto i = 0; i < 32; ++i) {
+ // Create a client from the base client.
+ Dhcp6Client next_client(client.getServer());
+ next_client.setInterface("eth1");
+ next_client.requestPrefix();
+ // Run 4-way exchange.
+ ASSERT_NO_THROW(next_client.doSARR());
+ // Make sure that the server responded.
+ ASSERT_TRUE(next_client.getContext().response_);
+ auto leases = next_client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases.size());
+ // Make sure that the prefix is not zero.
+ ASSERT_FALSE(leases[0].addr_.isV6Zero());
+ // Remember the allocated prefix uniqueness.
+ allocated_set.insert(leases[0].addr_.toText());
+ }
+ // Make sure that we have 32 distinct allocations.
+ ASSERT_EQ(32, allocated_set.size());
+
+ // Try one more time. This time no leases should be allocated because
+ // the pools are exhausted.
+ Dhcp6Client next_client(client.getServer());
+ next_client.setInterface("eth1");
+ next_client.requestPrefix();
+ ASSERT_NO_THROW(next_client.doSARR());
+ ASSERT_TRUE(next_client.getContext().response_);
+ auto leases = next_client.getLeasesByType(Lease::TYPE_PD);
+ EXPECT_TRUE(leases.empty());
+ }
+
+ /// @brief Destructor.
+ virtual ~Dhcpv6SharedNetworkTest() {
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+};
+
+// Check user-context parsing
+TEST_F(Dhcpv6SharedNetworkTest, parse) {
+ // Create client
+ Dhcp6Client client1;
+
+ // Don't use configure from utils
+ Parser6Context ctx;
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP6(NETWORKS_CONFIG[0], true));
+ ConstElementPtr status;
+ disableIfacesReDetect(json);
+ EXPECT_NO_THROW(status = configureDhcp6Server(*client1.getServer(), json));
+ ASSERT_TRUE(status);
+ int rcode;
+ ConstElementPtr comment = config::parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode);
+ CfgMgr::instance().commit();
+
+ CfgSharedNetworks6Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSharedNetworks6();
+ SharedNetwork6Ptr network = cfg->getByName("frog");
+ ConstElementPtr context = network->getContext();
+ ASSERT_TRUE(context);
+ ASSERT_EQ(1, context->size());
+ ASSERT_TRUE(context->get("comment"));
+ EXPECT_EQ("\"example\"", context->get("comment")->str());
+}
+
+// Running out of addresses within a subnet in a shared network.
+TEST_F(Dhcpv6SharedNetworkTest, addressPoolInSharedNetworkShortage) {
+ // Create client #1.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+
+ // Configure the server with one shared network including two subnets and
+ // one subnet outside of the shared network.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[0], *client1.getServer()));
+
+ // Client #1 requests an address in first subnet within a shared network.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca0, IOAddress("2001:db8:1::20")));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Client #2 The second client will request a lease and should be assigned
+ // an address from the second subnet.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::20")));
+
+ // Client #3. It sends Solicit which should result in NoAddrsAvail status
+ // code because all addresses available for this link have been assigned.
+ Dhcp6Client client3(client1.getServer());
+ client3.setInterface("eth1");
+ ASSERT_NO_THROW(client3.requestAddress(0xabca0));
+ testAssigned([&client3] {
+ ASSERT_NO_THROW(client3.doSolicit(true));
+ });
+ EXPECT_EQ(0, client3.getLeaseNum());
+
+ // Client #3 should be assigned an address if subnet 3 is selected for it.
+ client3.setInterface("eth0");
+ testAssigned([&client3] {
+ ASSERT_NO_THROW(client3.doSolicit(true));
+ });
+ EXPECT_EQ(1, client3.getLeaseNum());
+
+ // Client #1 should be able to renew its lease.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doRenew());
+ });
+ EXPECT_EQ(1, client1.getLeaseNum());
+ EXPECT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Client #2 should be able to renew its lease too.
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ EXPECT_EQ(1, client2.getLeaseNum());
+ EXPECT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::20")));
+}
+
+// Shared network is selected based on relay link address.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkSelectedByRelay) {
+ // Create client #1. This is a relayed client which is using relay address
+ // matching configured shared network.
+ Dhcp6Client client1;
+ client1.useRelay(true, IOAddress("3001::1"));
+
+ // Configure the server with one shared network and one subnet outside of the
+ // shared network.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[1], *client1.getServer()));
+
+ // Client #1 should be assigned an address from shared network.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca0));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Create client #2. This is a relayed client which is using relay
+ // address matching subnet outside of the shared network.
+ Dhcp6Client client2(client1.getServer());
+ client2.useRelay(true, IOAddress("3001::2"));
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::20")));
+}
+
+// Providing a hint for any address belonging to a shared network.
+TEST_F(Dhcpv6SharedNetworkTest, hintWithinSharedNetwork) {
+ // Create client #1.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+
+ // Configure the server with one shared network including two subnets and
+ // one subnet outside of the shared network.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[0], *client.getServer()));
+
+ // Provide a hint to an existing address within first subnet. This address
+ // should be offered out of this subnet.
+ ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSolicit(true));
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:1::20"),
+ LeaseOnServer::MUST_NOT_EXIST));
+
+ // Similarly, we should be offered an address from another subnet within
+ // the same shared network when we ask for it.
+ client.clearRequestedIAs();
+ ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("2001:db8:2::20")));
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSolicit(true));
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::20"),
+ LeaseOnServer::MUST_NOT_EXIST));
+
+ // Asking for an address that is not in address pool should result in getting
+ // an address from one of the subnets, but generally hard to tell from which one.
+ client.clearRequestedIAs();
+ ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("3002::123")));
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSolicit(true));
+ });
+ std::vector<Lease6> leases = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases.size());
+ if (!hasLeaseForAddress(client, IOAddress("2001:db8:1::20"),
+ LeaseOnServer::MUST_NOT_EXIST) &&
+ !hasLeaseForAddress(client, IOAddress("2001:db8:2::20"),
+ LeaseOnServer::MUST_NOT_EXIST)) {
+ ADD_FAILURE() << "Unexpected address advertised by the server " << leases.at(0).addr_;
+ }
+}
+
+// Shared network is selected based on the client class specified.
+TEST_F(Dhcpv6SharedNetworkTest, subnetInSharedNetworkSelectedByClass) {
+ // Create client #1.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+
+ // Configure the server with one shared network including two subnets and
+ // one subnet outside of the shared network.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[2], *client1.getServer()));
+
+ // Client #1 requests an address in the restricted subnet but can't be assigned
+ // this address because the client doesn't belong to a certain class.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:2::20")));
+
+ // Release the lease that the client has got, because we'll need this address
+ // further in the test.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doRelease());
+ });
+
+ // Add option 1234 which would cause the client to be classified as "a-devices".
+ OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0001));
+ client1.addExtraOption(option1234);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Client 2 should be assigned an address from the unrestricted subnet.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::20")));
+
+ // Now, let's reconfigure the server to also apply restrictions on the
+ // subnet to which client2 now belongs.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[3], *client1.getServer()));
+
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ EXPECT_EQ(0, client2.getLeaseNum());
+
+ // If we add option 1234 with a value matching this class, the lease should
+ // get renewed.
+ OptionPtr option1234_bis(new OptionUint16(Option::V6, 1234, 0x0002));
+ client2.addExtraOption(option1234_bis);
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ EXPECT_EQ(1, client2.getLeaseNum());
+}
+
+// IPv6 address reservation exists in one of the subnets within shared network.
+TEST_F(Dhcpv6SharedNetworkTest, reservationInSharedNetwork) {
+ // Create client #1. Explicitly set client's DUID to the one that has a
+ // reservation in the second subnet within shared network.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+ client1.setDUID("00:03:00:01:11:22:33:44:55:66");
+
+ // Create server configuration with a shared network including two subnets. There
+ // is an IP address reservation in each subnet for two respective clients.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[4], *client1.getServer()));
+
+ // Client #1 should get his reserved address from the second subnet.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:2::28")));
+
+ // Create client #2.
+ Dhcp6Client client2;
+ client2.setInterface("eth1");
+ client2.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+
+ // Client #2 should get its reserved address from the first subnet.
+ ASSERT_NO_THROW(client2.requestAddress(0xabca, IOAddress("2001:db8:1::30")));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:1::28")));
+
+ // Reconfigure the server. Now, the first client get's second client's
+ // reservation and vice versa.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[5], *client1.getServer(), true, true, false));
+
+ // The first client is trying to renew the lease but should get a different lease
+ // because its lease is now reserved for some other client. The client won't be
+ // assigned a lease for which it has a reservation because another client holds
+ // this lease.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doRenew());
+ });
+ ASSERT_TRUE(client1.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:2::28")));
+ ASSERT_FALSE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::28")));
+
+ // The client should be allocated a lease from one of the dynamic pools.
+ if (!hasLeaseForAddressRange(client1, IOAddress("2001:db8:2::1"), IOAddress("2001:db8:2::64")) &&
+ !hasLeaseForAddressRange(client1, IOAddress("2001:db8:1::1"), IOAddress("2001:db8:1::64"))) {
+ ADD_FAILURE() << "unexpected lease allocated for renewing client";
+ }
+
+ // Client #2 is now renewing its lease and should get its newly reserved address.
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ ASSERT_TRUE(client2.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:1::28")));
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::28")));
+
+ // Same for client #1.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doRenew());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::28")));
+}
+
+// Reserved address can't be assigned as long as access to a subnet is
+// restricted by classification.
+TEST_F(Dhcpv6SharedNetworkTest, reservationAccessRestrictedByClass) {
+ // Create client #1. Explicitly set client's DUID to the one that has a
+ // reservation in the firstsubnet within shared network.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+
+ // Create server configuration with a shared network including two subnets. Access to
+ // one of the subnets is restricted by client classification.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[6], *client.getServer()));
+
+ // Assigned address should be allocated from the second subnet, because the
+ // client doesn't belong to the "a-devices" class.
+ ASSERT_NO_THROW(client.requestAddress(0xabca));
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::16")));
+
+ // Add option 1234 which would cause the client to be classified as "a-devices".
+ OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0001));
+ client.addExtraOption(option1234);
+
+ // The client should now be assigned the reserved address from the first subnet.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doRenew());
+ });
+ ASSERT_TRUE(client.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:2::16")));
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:1::28")));
+}
+
+// Subnet in which the client is renewing an address is restricted by classification.
+TEST_F(Dhcpv6SharedNetworkTest, renewalRestrictedByClass) {
+ // Create client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+
+ // Create server configuration with a shared network including two subnets. Access to
+ // the second subnet is restricted by client classification.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[12], *client.getServer()));
+
+ // Add option 1234 to cause the client to belong to the class.
+ OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0002));
+ client.addExtraOption(option1234);
+
+ // Client requests an address from the second subnet which should be successful.
+ ASSERT_NO_THROW(client.requestAddress(0xabca, IOAddress("2001:db8:2::20")));
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::20")));
+
+ // Now remove the client from this class.
+ client.clearExtraOptions();
+
+ // The client should not be able to renew the existing lease because it is now
+ // prohibited by the classification. Instead, the client should get a lease from the
+ // unrestricted subnet.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doRenew());
+ });
+ ASSERT_TRUE(client.hasLeaseWithZeroLifetimeForAddress(IOAddress("2001:db8:2::20")));
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:1::20")));
+}
+
+// Some options are specified on the shared subnet level, some on the
+// subnets level.
+TEST_F(Dhcpv6SharedNetworkTest, optionsDerivation) {
+ // Client #1.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[7], *client1.getServer()));
+
+ // Client #1 belongs to shared network. By providing a hint "2001:db8:1::20 we force
+ // the server to select first subnet within the shared network for this client.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+
+ // Request all configured options.
+ ASSERT_NO_THROW(client1.requestOption(D6O_NIS_SERVERS));
+ ASSERT_NO_THROW(client1.requestOption(D6O_NISP_SERVERS));
+ ASSERT_NO_THROW(client1.requestOption(D6O_NAME_SERVERS));
+ ASSERT_NO_THROW(client1.requestOption(D6O_SNTP_SERVERS));
+
+ // Perform 4-way exchange and make sure we have been assigned address from the
+ // subnet we wanted.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // This option is specified on the global level.
+ ASSERT_TRUE(client1.hasOptionWithAddress(D6O_NIS_SERVERS, "3000::20"));
+
+ // Subnet specific value should override a value specified on the shared network level.
+ ASSERT_TRUE(client1.hasOptionWithAddress(D6O_NISP_SERVERS, "3003::33"));
+
+ // Shared network level value should be derived to the subnet.
+ ASSERT_TRUE(client1.hasOptionWithAddress(D6O_NAME_SERVERS, "3001::21"));
+
+ // This option is only specified in the subnet level.
+ ASSERT_TRUE(client1.hasOptionWithAddress(D6O_SNTP_SERVERS, "4004::22"));
+
+ // Client #2.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+
+ // Request an address from the second subnet within the shared network.
+ ASSERT_NO_THROW(client2.requestAddress(0xabca, IOAddress("2001:db8:2::20")));
+
+ // Request all configured options.
+ ASSERT_NO_THROW(client2.requestOption(D6O_NIS_SERVERS));
+ ASSERT_NO_THROW(client2.requestOption(D6O_NISP_SERVERS));
+ ASSERT_NO_THROW(client2.requestOption(D6O_NAME_SERVERS));
+ ASSERT_NO_THROW(client2.requestOption(D6O_SNTP_SERVERS));
+
+ // Perform 4-way exchange and make sure we have been assigned address from the
+ // subnet we wanted.
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::20")));
+
+ // This option is specified on the global level.
+ ASSERT_TRUE(client2.hasOptionWithAddress(D6O_NIS_SERVERS, "3000::20"));
+
+ // Shared network level value should be derived to the subnet.
+ ASSERT_TRUE(client2.hasOptionWithAddress(D6O_NAME_SERVERS, "3001::21"));
+ ASSERT_TRUE(client2.hasOptionWithAddress(D6O_NISP_SERVERS, "3002::34"));
+
+ // Client #3.
+ Dhcp6Client client3(client1.getServer());
+ client3.setInterface("eth0");
+
+ // Request an address from the subnet outside of the shared network.
+ ASSERT_NO_THROW(client3.requestAddress(0xabca, IOAddress("3000::1")));
+
+ // Request all configured options.
+ ASSERT_NO_THROW(client3.requestOption(D6O_NIS_SERVERS));
+ ASSERT_NO_THROW(client3.requestOption(D6O_NISP_SERVERS));
+ ASSERT_NO_THROW(client3.requestOption(D6O_NAME_SERVERS));
+ ASSERT_NO_THROW(client3.requestOption(D6O_SNTP_SERVERS));
+
+ // Perform 4-way exchange and make sure we have been assigned address from the
+ // subnet we wanted.
+ testAssigned([&client3] {
+ ASSERT_NO_THROW(client3.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client3, IOAddress("3000::1")));
+
+ // This option is specified on the global level.
+ ASSERT_TRUE(client3.hasOptionWithAddress(D6O_NIS_SERVERS, "3000::20"));
+
+ // Subnet specific value should be assigned.
+ ASSERT_TRUE(client3.hasOptionWithAddress(D6O_NISP_SERVERS, "4000::5"));
+}
+
+// The same option is specified differently for each subnet belonging to the
+// same shared network.
+TEST_F(Dhcpv6SharedNetworkTest, optionsFromSelectedSubnet) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+
+ // Create configuration with one shared network including three subnets with
+ // the same option having different values.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[16], *client.getServer()));
+
+ // Client provides no hint and any subnet can be picked from the shared network.
+ ASSERT_NO_THROW(client.requestAddress(0xabca));
+
+ // Request Name Servers option.
+ ASSERT_NO_THROW(client.requestOption(D6O_NAME_SERVERS));
+
+ // Send solicit without a hint. The client should be offered an address from the
+ // shared network. Depending on the subnet from which the address has been allocated
+ // a specific value of the Name Servers option should be returned.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSolicit(true));
+ });
+
+ if (client.hasLeaseForAddress(IOAddress("2001:db8:1::20"))) {
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "4004::22"));
+
+ } else if (client.hasLeaseForAddress(IOAddress("2001:db8:2::20"))) {
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "5555::33"));
+
+ } else if (client.hasLeaseForAddress(IOAddress("2001:db8:3::20"))) {
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "1234::23"));
+ }
+
+ // This time let's provide a hint.
+ client.clearRequestedIAs();
+ client.requestAddress(0xabca, IOAddress("2001:db8:2::20"));
+
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSolicit(true));
+ });
+
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:2::20")));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "5555::33"));
+
+ // This time, let's do the 4-way exchange.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSARR());
+ });
+
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:2::20")));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "5555::33"));
+
+ // And renew the lease.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doRenew());
+ });
+ ASSERT_TRUE(client.hasLeaseForAddress(IOAddress("2001:db8:2::20")));
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "5555::33"));
+}
+
+// Different shared network is selected for different local interface.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkSelectionByInterface) {
+ // Create client #1. The server receives requests from this client
+ // via interface eth1 and should assign shared network "frog" for
+ // this client.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+ client1.requestAddress(0xabca);
+
+ // Create server configuration with two shared networks selected
+ // by the local interface: eth1 and eth0.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[8], *client1.getServer()));
+
+ // Client #1 should be assigned an address from one of the two subnets
+ // belonging to the first shared network.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ if (!hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")) &&
+ !hasLeaseForAddress(client1, IOAddress("2001:db8:2::20"))) {
+ ADD_FAILURE() << "unexpected shared network selected for the client";
+ }
+
+ // Client #2.
+ Dhcp6Client client2;
+ client2.setInterface("eth0");
+ client2.requestAddress(0xabca);
+
+ // Client #2 should be assigned an address from one of the two subnets
+ // belonging to the second shared network.
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ if (!hasLeaseForAddress(client2, IOAddress("2001:db8:3::20")) &&
+ !hasLeaseForAddress(client2, IOAddress("2001:db8:4::20"))) {
+ ADD_FAILURE() << "unexpected shared network selected for the client";
+ }
+}
+
+// Different shared network is selected for different relay address.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkSelectionByRelay) {
+ // Create relayed client #1.
+ Dhcp6Client client1;
+ client1.useRelay(true, IOAddress("3000::1"));
+ client1.requestAddress(0xabcd);
+
+ // Create server configuration with two shared networks selected
+ // by the relay address.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[9], *client1.getServer()));
+
+ // Client #1 should be assigned an address from one of the two subnets
+ // belonging to the first shared network.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ if (!hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")) &&
+ !hasLeaseForAddress(client1, IOAddress("2001:db8:2::20"))) {
+ ADD_FAILURE() << "unexpected shared network selected for the client";
+ }
+
+ // Create relayed client #2.
+ Dhcp6Client client2;
+ client2.useRelay(true, IOAddress("3000::2"));
+ client2.requestAddress(0xabca);
+
+ // Client #2 should be assigned an address from one of the two subnets
+ // belonging to the second shared network
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ if (!hasLeaseForAddress(client2, IOAddress("2001:db8:3::20")) &&
+ !hasLeaseForAddress(client2, IOAddress("2001:db8:4::20"))) {
+ ADD_FAILURE() << "unexpected shared network selected for the client";
+ }
+}
+
+// Host reservations include hostname and client class.
+TEST_F(Dhcpv6SharedNetworkTest, variousFieldsInReservation) {
+ // Create client #1.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:11:22:33:44:55:66");
+ ASSERT_NO_THROW(client.requestAddress(0xabcd));
+ ASSERT_NO_THROW(client.requestOption(D6O_NAME_SERVERS));
+
+ ASSERT_NO_THROW(client.useFQDN(Option6ClientFqdn::FLAG_S,
+ "bird.example.org",
+ Option6ClientFqdn::FULL));
+
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[10], *client.getServer()));
+
+ // Perform 4-way exchange.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSARR());
+ });
+
+ // The client should get an FQDN from the reservation, rather than
+ // the FQDN it has sent to the server. If there is a logic error,
+ // the server would use the first subnet from the shared network to
+ // assign the FQDN. This subnet has no reservation so it would
+ // return the same FQDN that the client has sent. We expect
+ // that the FQDN being sent is the one that is included in the
+ // reservations.
+ ASSERT_TRUE(client.getContext().response_);
+ OptionPtr opt_fqdn = client.getContext().response_->getOption(D6O_CLIENT_FQDN);
+ ASSERT_TRUE(opt_fqdn);
+ Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<Option6ClientFqdn>(opt_fqdn);
+ ASSERT_TRUE(fqdn);
+ ASSERT_EQ("test.example.org.", fqdn->getDomainName());
+
+ // Make sure that the correct hostname has been stored in the database.
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:2::20"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("test.example.org.", lease->hostname_);
+
+ // The DNS servers option should be derived from the client class based on the
+ // static class reservations.
+ ASSERT_TRUE(client.hasOptionWithAddress(D6O_NAME_SERVERS, "2001:db8:1::50"));
+}
+
+// Shared network is selected based on the client class specified.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkSelectedByClass) {
+ // Create client #1.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+ client1.requestAddress(0xabcd);
+
+ // Add option 1234 which would cause the client1 to be classified as "b-devices".
+ OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0002));
+ client1.addExtraOption(option1234);
+
+ // Configure the server with two shared networks which can be accessed
+ // by clients belonging to "a-devices" and "b-devices" classes
+ // respectively.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[11], *client1.getServer()));
+
+ // The client 1 should be offered an address from the second subnet.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSolicit(true));
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:2::20"),
+ LeaseOnServer::MUST_NOT_EXIST));
+
+ // Create another client which will belong to a different class.
+ Dhcp6Client client2;
+ client2.setInterface("eth1");
+ client2.requestAddress(0xabcd);
+
+ /// Add option 1234 which will cause the client 2 to be classified as "a-devices".
+ option1234.reset(new OptionUint16(Option::V6, 1234, 0x0001));
+ client2.addExtraOption(option1234);
+
+ // Client 2 should be offered an address from the first subnet.
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSolicit(true));
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:1::20"),
+ LeaseOnServer::MUST_NOT_EXIST));
+}
+
+// Client requests two addresses and two prefixes and obtains them from two
+// different subnets.
+TEST_F(Dhcpv6SharedNetworkTest, assignmentsFromDifferentSubnets) {
+ // Create client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabcd);
+ client.requestAddress(0x1234);
+ client.requestPrefix(0x1111);
+ client.requestPrefix(0x2222);
+
+ // Configure the server with a shared network including two subnets. Each
+ // subnet has an address and prefix pool with a single available address
+ // and prefix respectively.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[0], *client.getServer()));
+
+ // 4-way exchange.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSARR());
+ });
+ // The two addresses should come from different subnets.
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:1::20")));
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::20")));
+ // Same for prefixes.
+ ASSERT_TRUE(hasLeaseForPrefixPool(client, IOAddress("4000::"), 96, 96));
+ ASSERT_TRUE(hasLeaseForPrefixPool(client, IOAddress("5000::"), 96, 96));
+
+ // Try to renew.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doRenew());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:1::20")));
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::20")));
+ ASSERT_TRUE(hasLeaseForPrefixPool(client, IOAddress("4000::"), 96, 96));
+ ASSERT_TRUE(hasLeaseForPrefixPool(client, IOAddress("5000::"), 96, 96));
+}
+
+// Client requests 2 addresses and 2 prefixes. There is one address and one prefix
+// reserved for the client.
+TEST_F(Dhcpv6SharedNetworkTest, reservedAddressAndPrefix) {
+ // Create client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:11:22:33:44:55:66");
+
+ // Client will request two addresses and two prefixes.
+ client.requestAddress(0xabcd);
+ client.requestAddress(0x1234);
+ client.requestPrefix(0x1111);
+ client.requestPrefix(0x2222);
+
+ // The server configuration contains a shared network with two subnets. Each
+ // subnet has an address and prefix pool. One of the subnets includes a reservation
+ // for an address and prefix.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[4], *client.getServer()));
+
+ // 4-way exchange.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSARR());
+ });
+ ASSERT_EQ(4, client.getLeaseNum());
+ // The client should have got one reserved address and one reserved prefix.
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::28")));
+ ASSERT_TRUE(hasLeaseForPrefix(client, IOAddress("5000::8:0000"), 112, IAID(0x1111)));
+
+ // The client should have got dynamically allocated address too and it must be
+ // different than the reserved address.
+ std::vector<Lease6> leases_1234 = client.getLeasesByIAID(0x1234);
+ ASSERT_EQ(1, leases_1234.size());
+ ASSERT_NE("2001:db8:2::28", leases_1234[0].addr_.toText());
+
+ // Same for prefix.
+ std::vector<Lease6> leases_2222 = client.getLeasesByIAID(0x2222);
+ ASSERT_EQ(1, leases_2222.size());
+ ASSERT_NE("1234::", leases_2222[0].addr_.toText());
+
+ // Try to renew and check this again.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doRenew());
+ });
+ ASSERT_EQ(4, client.getLeaseNum());
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::28")));
+ ASSERT_TRUE(hasLeaseForPrefix(client, IOAddress("5000::8:0000"), 112, IAID(0x1111)));
+
+ leases_1234 = client.getLeasesByIAID(0x1234);
+ ASSERT_EQ(1, leases_1234.size());
+ ASSERT_NE("2001:db8:2::28", leases_1234[0].addr_.toText());
+
+ leases_2222 = client.getLeasesByIAID(0x2222);
+ ASSERT_EQ(1, leases_2222.size());
+ ASSERT_NE(IOAddress("5000::8:0000").toText(), leases_2222[0].addr_.toText());
+}
+
+// Relay address is specified for each subnet within shared network.
+TEST_F(Dhcpv6SharedNetworkTest, relaySpecifiedForEachSubnet) {
+ // Create client.
+ Dhcp6Client client;
+ client.useRelay(true, IOAddress("3001::1"));
+
+ // Client will request two addresses.
+ client.requestAddress(0xabcd);
+ client.requestAddress(0x1234);
+
+ // Configure the server with three subnets. Two of them belong to a shared network.
+ // Each subnet is configured with relay info, i.e. IP address of the relay agent
+ // for which the shared network is used.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[13], *client.getServer()));
+
+ // 4-way exchange.
+ testAssigned([&client] {
+ ASSERT_NO_THROW(client.doSARR());
+ });
+ ASSERT_EQ(2, client.getLeaseNum());
+
+ // The client should have got two leases, one from each subnet within the
+ // shared network.
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:1::20")));
+ ASSERT_TRUE(hasLeaseForAddress(client, IOAddress("2001:db8:2::20")));
+}
+
+// Shared network is selected based on interface id.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkSelectedByInterfaceId) {
+ // Create client #1. This is a relayed client for which interface id
+ // has been specified and this interface id is matching the one specified
+ // for the shared network.
+ Dhcp6Client client1;
+ client1.useRelay(true, IOAddress("3001::1"));
+ client1.useInterfaceId("vlan10");
+
+ // Configure the server with one shared network and one subnet outside of the
+ // shared network.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[14], *client1.getServer()));
+
+ // Client #1 should be assigned an address from shared network.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca0));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Create client #2. This is a relayed client which is using interface id
+ // matching a subnet outside of the shared network.
+ Dhcp6Client client2(client1.getServer());
+ client2.useRelay(true, IOAddress("3001::2"));
+ client2.useInterfaceId("vlan1000");
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::20")));
+}
+
+// Shared network is selected based on interface id specified for a subnet
+// belonging to a shared network.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkSelectedByInterfaceIdInSubnet) {
+ // Create client #1. This is a relayed client for which interface id
+ // has been specified and this interface id is matching the one specified
+ // for the shared network.
+ Dhcp6Client client1;
+ client1.useRelay(true, IOAddress("3001::1"));
+ client1.useInterfaceId("vlan10");
+
+ // Configure the server with one shared network and one subnet outside of the
+ // shared network.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[15], *client1.getServer()));
+
+ // Client #1 should be assigned an address from shared network.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca0));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Create client #2. This is a relayed client which is using interface id
+ // matching a subnet outside of the shared network.
+ Dhcp6Client client2(client1.getServer());
+ client2.useRelay(true, IOAddress("3001::2"));
+ client2.useInterfaceId("vlan1000");
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:2::20")));
+}
+
+// Check that the rapid-commit works with shared networks. Rapid-commit
+// enabled on each subnet separately.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkRapidCommit1) {
+ testRapidCommit(NETWORKS_CONFIG[17], true, "2001:db8:1::20", "2001:db8:2::20");
+}
+
+// Check that the rapid-commit works with shared networks. Rapid-commit
+// enabled for the whole shared network. This should be applied to both
+// subnets.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkRapidCommit2) {
+ testRapidCommit(NETWORKS_CONFIG[18], true, "2001:db8:1::20", "2001:db8:2::20");
+}
+
+// Check that the rapid-commit is disabled by default.
+TEST_F(Dhcpv6SharedNetworkTest, sharedNetworkRapidCommit3) {
+ testRapidCommit(NETWORKS_CONFIG[1], false, "", "");
+}
+
+// Pool is selected based on the client class specified.
+TEST_F(Dhcpv6SharedNetworkTest, poolInSharedNetworkSelectedByClass) {
+ // Create client #1.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+
+ // Configure the server with one shared network including one subnet and
+ // two pools. The access to one of the pools is restricted by
+ // by client classification.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[19], *client1.getServer()));
+
+ // Client #1 requests an address in the restricted pool but can't be assigned
+ // this address because the client doesn't belong to a certain class.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::50")));
+
+ // Release the lease that the client has got, because we'll need this address
+ // further in the test.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doRelease());
+ });
+
+ // Add option 1234 which would cause the client to be classified as "a-devices".
+ OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0001));
+ client1.addExtraOption(option1234);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Client 2 should be assigned an address from the unrestricted pool.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:1::50")));
+
+ // Now, let's reconfigure the server to also apply restrictions on the
+ // pool to which client2 now belongs.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[20], *client1.getServer()));
+
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ EXPECT_EQ(0, client2.getLeasesWithNonZeroLifetime().size());
+
+ // If we add option 1234 with a value matching this class, the lease should
+ // get renewed.
+ OptionPtr option1234_bis(new OptionUint16(Option::V6, 1234, 0x0002));
+ client2.addExtraOption(option1234_bis);
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ EXPECT_EQ(1, client2.getLeaseNum());
+ EXPECT_EQ(1, client2.getLeasesWithNonZeroLifetime().size());
+}
+
+// Pool is selected based on the client class specified using a plain subnet.
+TEST_F(Dhcpv6SharedNetworkTest, poolInSubnetSelectedByClass) {
+ // Create client #1.
+ Dhcp6Client client1;
+ client1.setInterface("eth1");
+
+ // Configure the server with one plain subnet including two pools.
+ // The access to one of the pools is restricted by client classification.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[21], *client1.getServer()));
+
+ // Client #1 requests an address in the restricted pool but can't be assigned
+ // this address because the client doesn't belong to a certain class.
+ ASSERT_NO_THROW(client1.requestAddress(0xabca, IOAddress("2001:db8:1::20")));
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::50")));
+
+ // Release the lease that the client has got, because we'll need this address
+ // further in the test.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doRelease());
+ });
+
+ // Add option 1234 which would cause the client to be classified as "a-devices".
+ OptionPtr option1234(new OptionUint16(Option::V6, 1234, 0x0001));
+ client1.addExtraOption(option1234);
+
+ // This time, the allocation of the address provided as hint should be successful.
+ testAssigned([&client1] {
+ ASSERT_NO_THROW(client1.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client1, IOAddress("2001:db8:1::20")));
+
+ // Client 2 should be assigned an address from the unrestricted pool.
+ Dhcp6Client client2(client1.getServer());
+ client2.setInterface("eth1");
+ ASSERT_NO_THROW(client2.requestAddress(0xabca0));
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doSARR());
+ });
+ ASSERT_TRUE(hasLeaseForAddress(client2, IOAddress("2001:db8:1::50")));
+
+ // Now, let's reconfigure the server to also apply restrictions on the
+ // pool to which client2 now belongs.
+ ASSERT_NO_FATAL_FAILURE(configure(NETWORKS_CONFIG[22], *client1.getServer()));
+
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ EXPECT_EQ(0, client2.getLeasesWithNonZeroLifetime().size());
+
+ // If we add option 1234 with a value matching this class, the lease should
+ // get renewed.
+ OptionPtr option1234_bis(new OptionUint16(Option::V6, 1234, 0x0002));
+ client2.addExtraOption(option1234_bis);
+ testAssigned([&client2] {
+ ASSERT_NO_THROW(client2.doRenew());
+ });
+ EXPECT_EQ(1, client2.getLeaseNum());
+ EXPECT_EQ(1, client2.getLeasesWithNonZeroLifetime().size());
+}
+
+// Test that different allocator types can be used within a shared network.
+// The first subnet uses the random allocator. The second subnet uses the FLQ
+// allocator.
+TEST_F(Dhcpv6SharedNetworkTest, randomAndFlqAllocation) {
+ testDifferentAllocatorsInNetwork(NETWORKS_CONFIG[23]);
+}
+
+// Test that different allocator types can be used within a shared network.
+// The first subnet uses the FLQ allocator. The second subnet uses the random
+// allocator.
+TEST_F(Dhcpv6SharedNetworkTest, flqAndRandomAllocation) {
+ testDifferentAllocatorsInNetwork(NETWORKS_CONFIG[24]);
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceGlobal) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::1");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceClass) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceClasses) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"beta\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Class order is the insert order
+ testPrecedence(config, "2001:db8:1::2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceNetworkClass) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::3");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceSubnet) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::4");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedencePool) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::5");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceReservation) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ],"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::6\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::6");
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/simple_parser6_unittest.cc b/src/bin/dhcp6/tests/simple_parser6_unittest.cc
new file mode 100644
index 0000000..a7ebb65
--- /dev/null
+++ b/src/bin/dhcp6/tests/simple_parser6_unittest.cc
@@ -0,0 +1,261 @@
+// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <gtest/gtest.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <cc/data.h>
+#include <util/doubles.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief DHCP Parser test fixture class
+class SimpleParser6Test : public ::testing::Test {
+public:
+ /// @brief Checks if specified map has an integer parameter with expected value
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ /// @param exp_value expected value of the parameter.
+ void checkIntegerValue(const ConstElementPtr& map, const std::string& param_name,
+ int64_t exp_value) {
+
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_TRUE(elem) << "param not found: " << param_name;
+
+ // Now check if it's indeed integer
+ ASSERT_EQ(Element::integer, elem->getType());
+
+ // Finally, check if its value meets expectation.
+ EXPECT_EQ(exp_value, elem->intValue());
+ }
+
+ /// @brief Checks if specified map has a string parameter with expected value
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ /// @param exp_value expected value of the parameter.
+ void checkStringValue(const ConstElementPtr& map, const std::string& param_name,
+ std::string exp_value) {
+
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_TRUE(elem) << "param not found: " << param_name;
+
+ // Now check if it's indeed integer
+ ASSERT_EQ(Element::string, elem->getType());
+
+ // Finally, check if its value meets expectation.
+ EXPECT_EQ(exp_value, elem->stringValue());
+ }
+
+ /// @brief Checks if specified map has a boolean parameter with expected value
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ /// @param exp_value expected value of the parameter.
+ void checkBoolValue(const ConstElementPtr& map, const std::string& param_name,
+ bool exp_value) {
+
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_TRUE(elem) << "param not found: " << param_name;
+
+ // Now check if it's indeed integer
+ ASSERT_EQ(Element::boolean, elem->getType());
+
+ // Finally, check if its value meets expectation.
+ EXPECT_EQ(exp_value, elem->boolValue());
+ }
+
+ /// @brief Checks if specified map has a double parameter with expected value
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ /// @param exp_value expected value of the parameter.
+ void checkDoubleValue(const ConstElementPtr& map, const std::string& param_name,
+ double exp_value) {
+
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_TRUE(elem) << "param not found: " << param_name;
+
+ // Now check if it's indeed integer
+ ASSERT_EQ(Element::real, elem->getType());
+
+ // Finally, check if its value meets expectation.
+ EXPECT_TRUE(util::areDoublesEquivalent(exp_value, elem->doubleValue()))
+ << "exp_value: " << std::fixed << ", actual: "
+ << std::fixed << elem->doubleValue();
+ }
+
+ /// @brief Checks if specified map does not contain the given parameter
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ void checkNoValue(const ConstElementPtr& map, const std::string& param_name) {
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_FALSE(elem) << "param was found but not expected: " << param_name;
+ }
+
+};
+
+// This test checks if global defaults are properly set for DHCPv6.
+TEST_F(SimpleParser6Test, globalDefaults6) {
+
+ ElementPtr empty = parseJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = SimpleParser6::setAllDefaults(empty));
+
+ // We expect at least 5 parameters to be inserted.
+ EXPECT_TRUE(num >= 5);
+
+ checkIntegerValue(empty, "valid-lifetime", 7200);
+ checkNoValue(empty, "preferred-lifetime");
+ checkBoolValue(empty, "calculate-tee-times", true);
+ checkDoubleValue(empty, "t1-percent", 0.5);
+ checkDoubleValue(empty, "t2-percent", 0.8);
+
+ // Timers should not be specified by default.
+ checkNoValue(empty, "rebind-timer");
+ checkNoValue(empty, "renew-timer");
+}
+
+// This test checks if the parameters can be inherited from the global
+// scope to the subnet scope.
+TEST_F(SimpleParser6Test, inheritGlobalToSubnet6) {
+ ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"preferred-lifetime\": 3,"
+ " \"min-preferred-lifetime\": 2,"
+ " \"max-preferred-lifetime\": 4,"
+ " \"valid-lifetime\": 4,"
+ " \"min-valid-lifetime\": 3,"
+ " \"max-valid-lifetime\": 5,"
+ " \"subnet6\": [ { \"renew-timer\": 100 } ] "
+ "}");
+
+ ConstElementPtr subnets = global->find("subnet6");
+ ASSERT_TRUE(subnets);
+ ConstElementPtr subnet = subnets->get(0);
+ ASSERT_TRUE(subnet);
+
+ // we should inherit 7 parameters. Renew-timer should remain intact,
+ // as it was already defined in the subnet scope.
+ size_t num;
+ EXPECT_NO_THROW(num = SimpleParser6::deriveParameters(global));
+ EXPECT_EQ(7, num);
+
+ // Check the values. 3 of them are inherited, while the fourth one
+ // was already defined in the subnet, so should not be inherited.
+ checkIntegerValue(subnet, "renew-timer", 100);
+ checkIntegerValue(subnet, "rebind-timer", 2);
+ checkIntegerValue(subnet, "preferred-lifetime", 3);
+ checkIntegerValue(subnet, "min-preferred-lifetime", 2);
+ checkIntegerValue(subnet, "max-preferred-lifetime", 4);
+ checkIntegerValue(subnet, "valid-lifetime", 4);
+ checkIntegerValue(subnet, "min-valid-lifetime", 3);
+ checkIntegerValue(subnet, "max-valid-lifetime", 5);
+}
+
+// This test checks if the parameters in "subnet6" are assigned default values
+// if not explicitly specified.
+TEST_F(SimpleParser6Test, subnetDefaults6) {
+ ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"preferred-lifetime\": 3,"
+ " \"valid-lifetime\": 4,"
+ " \"subnet6\": [ { } ] "
+ "}");
+
+ size_t num = 0;
+ EXPECT_NO_THROW(num = SimpleParser6::setAllDefaults(global));
+ EXPECT_LE(1, num); // at least 1 parameter has to be modified
+
+ ConstElementPtr subnets = global->find("subnet6");
+ ASSERT_TRUE(subnets);
+ ConstElementPtr subnet = subnets->get(0);
+ ASSERT_TRUE(subnet);
+
+ // we should have "id" parameter with the default value of 0 added for us.
+ checkIntegerValue(subnet, "id", 0);
+}
+
+// This test checks if the parameters in option-data are assigned default values
+// if not explicitly specified.
+TEST_F(SimpleParser6Test, optionDataDefaults6) {
+ ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"preferred-lifetime\": 3,"
+ " \"valid-lifetime\": 4,"
+ " \"option-data\": [ { } ] "
+ "}");
+
+ size_t num = 0;
+ EXPECT_NO_THROW(num = SimpleParser6::setAllDefaults(global));
+ EXPECT_LE(1, num); // at least 1 parameter has to be modified
+
+ ConstElementPtr options = global->find("option-data");
+ ASSERT_TRUE(options);
+ ConstElementPtr option = options->get(0);
+ ASSERT_TRUE(option);
+
+ // we should have appropriate default value set. See
+ // SimpleParser6::OPTION6_DEFAULTS for a list of default values.
+ checkStringValue(option, "space", "dhcp6");
+ checkBoolValue(option, "csv-format", true);
+}
+
+// This test checks if the parameters in option-data are assigned default values
+// if not explicitly specified.
+TEST_F(SimpleParser6Test, optionDefDefaults6) {
+ ElementPtr global = parseJSON("{ "
+ " \"option-def\": [ { } ] "
+ "}");
+
+ size_t num = 0;
+ EXPECT_NO_THROW(num = SimpleParser6::setAllDefaults(global));
+ EXPECT_LE(1, num); // at least 1 parameter has to be modified
+
+ ConstElementPtr defs = global->find("option-def");
+ ASSERT_TRUE(defs);
+ ASSERT_EQ(1, defs->size());
+ ConstElementPtr def = defs->get(0);
+ ASSERT_TRUE(def);
+
+ // we should have appropriate default value set. See
+ // SimpleParser6::OPTION6_DEFAULTS for a list of default values.
+ checkStringValue(def, "record-types", "");
+ checkStringValue(def, "space", "dhcp6");
+ checkStringValue(def, "encapsulate", "");
+ checkBoolValue(def, "array", false);
+}
+
+}
diff --git a/src/bin/dhcp6/tests/tee_times_unittest.cc b/src/bin/dhcp6/tests/tee_times_unittest.cc
new file mode 100644
index 0000000..1caec5a
--- /dev/null
+++ b/src/bin/dhcp6/tests/tee_times_unittest.cc
@@ -0,0 +1,245 @@
+// Copyright (C) 2019-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/option_string.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+#include <dhcpsrv/utils.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Rebind tests.
+///
+/// - Configuration 0:
+/// - only addresses (no prefixes)
+/// - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::/64
+/// - 1 subnet for eth0 and 1 subnet for eth1
+///
+const char* TEE_CONFIGS[] = {
+ // Configuration 0, Timers explicitly set
+ "{ \n"
+ " \"renew-timer\": 1000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"preferred-lifetime\": 3000, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"subnet6\": [ { \n"
+ " \"interface\": \"eth0\", \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+ " \"pd-pools\": [ \n"
+ " { \n"
+ " \"prefix\": \"3000::\", \n "
+ " \"prefix-len\": 72, \n"
+ " \"delegated-len\": 80 \n"
+ " }] \n"
+ " }] \n"
+ "} \n"
+ , // Configuration 1, Calculate default timers
+ "{ \n"
+ " \"preferred-lifetime\": 3000, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"subnet6\": [ { \n"
+ " \"interface\": \"eth0\", \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+ " \"pd-pools\": [ \n"
+ " { \n"
+ " \"prefix\": \"3000::\", \n "
+ " \"prefix-len\": 72, \n"
+ " \"delegated-len\": 80 \n"
+ " }] \n"
+ " }] \n"
+ "} \n"
+ , // Configuration 2, Calculate custom timers
+ "{ \n"
+ " \"preferred-lifetime\": 3000, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"t1-percent\": .45, \n"
+ " \"t2-percent\": .70, \n"
+ " \"subnet6\": [ { \n"
+ " \"interface\": \"eth0\", \n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"id\": 1, \n"
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
+ " \"pd-pools\": [ \n"
+ " { \n"
+ " \"prefix\": \"3000::\", \n "
+ " \"prefix-len\": 72, \n"
+ " \"delegated-len\": 80 \n"
+ " }] \n"
+ " }] \n"
+ "} \n"
+};
+
+/// @brief Test fixture class for testing Rebind.
+class TeeTest : public Dhcpv6MessageTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ TeeTest()
+ : Dhcpv6MessageTest() {
+ }
+
+ void genRequest(const std::string& config, Dhcp6Client& client,
+ uint32_t exp_leases) {
+ // Configure the server.
+ ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+ // Do the actual 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Make sure that we go the expected number of leases.
+ ASSERT_EQ(exp_leases, client.getLeaseNum());
+
+ // Simulate aging of leases, by moving their cltt_ back by 1000s.
+ client.fastFwdTime(1000);
+ }
+};
+
+// This test verifies that explicit values for renew-timer and
+// rebind-timer are used when given.
+TEST_F(TeeTest, explicitTimers) {
+ Dhcp6Client client;
+
+ uint32_t na_iaid = 2222;
+ client.requestAddress(na_iaid);
+
+ uint32_t pd_iaid = 3333;
+ client.requestPrefix(pd_iaid);
+
+ uint32_t exp_leases = 2;
+
+ // Configure client to request IA_NA.
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(genRequest(TEE_CONFIGS[0], client, exp_leases));
+
+ // Make sure the timers are right for both IAs
+ uint32_t actual_t1;
+ uint32_t actual_t2;
+
+ ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1000, actual_t1);
+ EXPECT_EQ(2000, actual_t2);
+
+ ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1000, actual_t1);
+ EXPECT_EQ(2000, actual_t2);
+
+ // Let's renew the leases.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Now check the timers again.
+ ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1000, actual_t1);
+ EXPECT_EQ(2000, actual_t2);
+
+ ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1000, actual_t1);
+ EXPECT_EQ(2000, actual_t2);
+}
+
+// This test verifies that T1 and T2 are calculated by
+// default when explicit values for renew-timer
+// and rebind-timer are not present.
+TEST_F(TeeTest, defaultTimers) {
+ Dhcp6Client client;
+
+ uint32_t na_iaid = 2222;
+ client.requestAddress(na_iaid);
+
+ uint32_t pd_iaid = 3333;
+ client.requestPrefix(pd_iaid);
+
+ uint32_t exp_leases = 2;
+
+ // Configure client to request IA_NA.
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(genRequest(TEE_CONFIGS[1], client, exp_leases));
+
+ // Make sure the timers are right for both IAs
+ uint32_t actual_t1;
+ uint32_t actual_t2;
+
+ ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1500, actual_t1);
+ EXPECT_EQ(2400, actual_t2);
+
+ ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1500, actual_t1);
+ EXPECT_EQ(2400, actual_t2);
+
+ // Let's renew the leases.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Now check the timers again.
+ ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1500, actual_t1);
+ EXPECT_EQ(2400, actual_t2);
+
+ ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1500, actual_t1);
+ EXPECT_EQ(2400, actual_t2);
+}
+
+// This test verifies that custom percentages for T1 and T2
+// can be used for calculation.
+TEST_F(TeeTest, calculateTimers) {
+ Dhcp6Client client;
+
+ uint32_t na_iaid = 2222;
+ client.requestAddress(na_iaid);
+
+ uint32_t pd_iaid = 3333;
+ client.requestPrefix(pd_iaid);
+
+ uint32_t exp_leases = 2;
+
+ // Configure client to request IA_NA.
+ // Make 4-way exchange to get the lease.
+ ASSERT_NO_FATAL_FAILURE(genRequest(TEE_CONFIGS[2], client, exp_leases));
+
+ // Make sure the timers are right for both IAs
+ uint32_t actual_t1;
+ uint32_t actual_t2;
+
+ ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1350, actual_t1);
+ EXPECT_EQ(2100, actual_t2);
+
+ ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1350, actual_t1);
+ EXPECT_EQ(2100, actual_t2);
+
+ // Let's renew the leases.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Now check the timers again.
+ ASSERT_TRUE(client.getTeeTimes(na_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1350, actual_t1);
+ EXPECT_EQ(2100, actual_t2);
+
+ ASSERT_TRUE(client.getTeeTimes(pd_iaid, actual_t1, actual_t2));
+ EXPECT_EQ(1350, actual_t1);
+ EXPECT_EQ(2100, actual_t2);
+}
+
+
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/test_data_files_config.h.in b/src/bin/dhcp6/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..278744f
--- /dev/null
+++ b/src/bin/dhcp6/tests/test_data_files_config.h.in
@@ -0,0 +1,15 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @brief Path to DHCP6 source dir so tests against the dhcp6.spec file
+/// can find it reliably.
+
+#ifndef TEST_DATA_FILES_CONFIG_H
+#define TEST_DATA_FILES_CONFIG_H
+
+#define DHCP6_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp6"
+
+#endif
diff --git a/src/bin/dhcp6/tests/test_libraries.h.in b/src/bin/dhcp6/tests/test_libraries.h.in
new file mode 100644
index 0000000..95d7459
--- /dev/null
+++ b/src/bin/dhcp6/tests/test_libraries.h.in
@@ -0,0 +1,30 @@
+// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+const char* const CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1.so";
+const char* const CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2.so";
+const char* const CALLOUT_LIBRARY_3 = "@abs_builddir@/.libs/libco3.so";
+
+// Name of a library which is not present.
+const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
+
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/bin/dhcp6/tests/vendor_opts_unittest.cc b/src/bin/dhcp6/tests/vendor_opts_unittest.cc
new file mode 100644
index 0000000..db613e1
--- /dev/null
+++ b/src/bin/dhcp6/tests/vendor_opts_unittest.cc
@@ -0,0 +1,1921 @@
+// Copyright (C) 2019-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// This file is dedicated to testing vendor options in DHCPv6. There
+// are several related options:
+//
+// client-class (15) - this specifies (as a plain string) what kind of device
+// this is.
+// vendor-class (16) - contains an enterprise-id followed by zero or more of
+// vendor-class data.
+// vendor-option (17) - contains an enterprise-id followed by zero or more
+// vendor suboptions.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option_string.h>
+#include <cc/command_interpreter.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Class dedicated to testing vendor options in DHCPv6
+class VendorOptsTest : public Dhcpv6SrvTest {
+public:
+ /// @brief Called before each test
+ void SetUp() override {
+ iface_mgr_test_config_.reset(new IfaceMgrTestConfig(true));
+ IfaceMgr::instance().openSockets6();
+ }
+
+ /// @brief Called after each test
+ void TearDown() override {
+ iface_mgr_test_config_.reset();
+ IfaceMgr::instance().closeSockets();
+ }
+
+ /// @brief Checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+ /// vendor options is parsed correctly and the requested options are
+ /// actually assigned. Also covers negative tests that options are not
+ /// provided when a different vendor ID is given.
+ ///
+ /// @note Kea only knows how to process VENDOR_ID_CABLE_LABS DOCSIS3_V4_ORO
+ /// (suboption 1).
+ ///
+ /// @param configured_vendor_ids The vendor IDs that are configured in the
+ /// server: 4491 or both 4491 and 3561.
+ /// @param requested_vendor_ids Then vendor IDs that are present in ORO.
+ /// @param requested_options The requested options in ORO.
+ void testVendorOptionsORO(std::set<uint32_t> configured_vendor_ids,
+ std::set<uint32_t> requested_vendor_ids,
+ std::set<uint16_t> requested_options) {
+ std::set<uint32_t> result_vendor_ids;
+ ASSERT_FALSE(configured_vendor_ids.empty());
+ ASSERT_TRUE(configured_vendor_ids.find(VENDOR_ID_CABLE_LABS) != configured_vendor_ids.end());
+ for (uint32_t req : requested_vendor_ids) {
+ if (req == VENDOR_ID_CABLE_LABS) {
+ result_vendor_ids.insert(req);
+ }
+ }
+ // Create a config with custom options.
+ string config = R"(
+ {
+ "interfaces-config": {
+ "interfaces": [ "*" ]
+ },
+ "preferred-lifetime": 3000,
+ "rebind-timer": 2000,
+ "renew-timer": 1000,
+ "valid-lifetime": 4000,
+ "option-data": [
+ {
+ "code": 33,
+ "data": "normal_erouter_v6.cm",
+ "name": "config-file",
+ "space": "vendor-4491"
+ },
+ {
+ "code": 12,
+ "data": "first",
+ "name": "payload",
+ "space": "vendor-4491"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 33,
+ "data": "special_erouter_v6.cm",
+ "name": "custom",
+ "space": "vendor-3561",
+ },
+ {
+ "code": 12,
+ "data": "last",
+ "name": "special",
+ "space": "vendor-3561"
+ )";
+ }
+ config += R"(
+ }
+ ],
+ "option-def": [
+ {
+ "code": 12,
+ "name": "payload",
+ "space": "vendor-4491",
+ "type": "string"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 33,
+ "name": "custom",
+ "space": "vendor-3561",
+ "type": "string"
+ },
+ {
+ "code": 12,
+ "name": "special",
+ "space": "vendor-3561",
+ "type": "string"
+ )";
+ }
+ config += R"(
+ }
+ ],
+ "subnet6": [
+ {
+ "interface": "eth0",
+ "pools": [
+ {
+ "pool": "2001:db8:1::/64"
+ }
+ ],
+ "subnet": "2001:db8:1::/48",
+ "id": 1,
+ "interface-id": ""
+ }
+ ]
+ }
+ )";
+
+ ASSERT_NO_THROW(configure(config));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise.
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_.processSolicit(ctx);
+
+ // Check if we get a response at all.
+ ASSERT_TRUE(adv);
+
+ // We did not include any vendor opts in SOLICIT, so there should be none
+ // in ADVERTISE.
+ ASSERT_FALSE(adv->getOption(D6O_VENDOR_OPTS));
+
+ // Let's add a vendor-option (vendor-id=4491) with a single sub-option.
+ // That suboption has code 1 and is a docsis ORO option.
+ OptionUint16ArrayPtr vendor_oro(new OptionUint16Array(Option::V6, DOCSIS3_V6_ORO));
+ for (uint16_t option : requested_options) {
+ vendor_oro->addValue(option);
+ }
+
+ for (uint32_t vendor_id : requested_vendor_ids) {
+ OptionVendorPtr vendor(new OptionVendor(Option::V6, vendor_id));
+ vendor->addOption(vendor_oro);
+ sol->addOption(vendor);
+ }
+
+ // Need to process SOLICIT again after requesting new option.
+ AllocEngine::ClientContext6 ctx2;
+ drop = !srv_.earlyGHRLookup(sol, ctx2);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx2, drop);
+ ASSERT_FALSE(drop);
+ adv = srv_.processSolicit(ctx2);
+ ASSERT_TRUE(adv);
+
+ // Check if there is a vendor option in the response, if the Cable Labs
+ // vendor ID was provided in the request. Otherwise, check that there is
+ // no vendor and stop processing since the following checks are built on
+ // top of the now-absent options.
+ OptionCollection tmp = adv->getOptions(D6O_VENDOR_OPTS);
+ ASSERT_EQ(tmp.size(), result_vendor_ids.size());
+ if (result_vendor_ids.empty()) {
+ return;
+ }
+
+ for (auto const& opt : tmp) {
+ // The response should be an OptionVendor.
+ OptionVendorPtr vendor_resp;
+
+ for (uint32_t vendor_id : result_vendor_ids) {
+ vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ ASSERT_TRUE(vendor_resp);
+ if (vendor_resp->getVendorId() == vendor_id) {
+ break;
+ }
+ vendor_resp.reset();
+ }
+ ASSERT_TRUE(vendor_resp);
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ for (uint16_t option : requested_options) {
+ if (option == DOCSIS3_V6_CONFIG_FILE) {
+ // Option 33 should be present.
+ OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
+ ASSERT_TRUE(docsis33);
+
+ // Check that the provided content match the one in configuration.
+ OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+ ASSERT_TRUE(config_file);
+ EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+ }
+
+ if (option == 12) {
+ // Option 12 should be present.
+ OptionPtr custom = vendor_resp->getOption(12);
+ ASSERT_TRUE(custom);
+
+ // It should be an OptionString.
+ OptionStringPtr tag = boost::dynamic_pointer_cast<OptionString>(custom);
+ ASSERT_TRUE(tag);
+
+ // Check that the provided value match the ones in configuration.
+ EXPECT_EQ(tag->getValue(), "first");
+ }
+ }
+ } else {
+ // If explicitly sending OptionVendor and the vendor is not
+ // VENDOR_ID_CABLE_LABS, options should not be present. Kea only
+ // knows how to process VENDOR_ID_CABLE_LABS DOCSIS3_V6_ORO
+ // (suboption 1).
+ // Option 33 should not be present.
+ OptionPtr docsis33 = vendor_resp->getOption(33);
+ ASSERT_FALSE(docsis33);
+
+ // Option 12 should not be present.
+ OptionPtr custom = vendor_resp->getOption(12);
+ ASSERT_FALSE(custom);
+ }
+ }
+ }
+
+ /// @brief Checks if vendor options are parsed correctly and the persistent
+ /// options are actually assigned. Also covers negative tests that options
+ /// are not provided when a different vendor ID is given.
+ ///
+ /// @param configured_vendor_ids The vendor IDs that are configured in the
+ /// server: 4491 or both 4491 and 3561.
+ /// @param requested_vendor_ids Then vendor IDs that are present in ORO.
+ /// @param configured_options The configured options.
+ /// @param add_vendor_option The flag which indicates if the request should
+ /// contain a OptionVendor option or should the server always send all the
+ /// OptionVendor options and suboptions.
+ void testVendorOptionsPersistent(std::set<uint32_t> configured_vendor_ids,
+ std::set<uint32_t> requested_vendor_ids,
+ std::set<uint16_t> configured_options,
+ bool add_vendor_option) {
+ std::set<uint32_t> result_vendor_ids;
+ ASSERT_FALSE(configured_vendor_ids.empty());
+ ASSERT_TRUE(configured_vendor_ids.find(VENDOR_ID_CABLE_LABS) != configured_vendor_ids.end());
+ if (add_vendor_option) {
+ for (uint32_t req : requested_vendor_ids) {
+ if (configured_vendor_ids.find(req) != configured_vendor_ids.end()) {
+ result_vendor_ids.insert(req);
+ }
+ }
+ } else {
+ result_vendor_ids = configured_vendor_ids;
+ }
+ ASSERT_FALSE(configured_options.empty());
+ ASSERT_TRUE(configured_options.find(DOCSIS3_V6_CONFIG_FILE) != configured_options.end());
+ // Create a config with custom options.
+ string config = R"(
+ {
+ "interfaces-config": {
+ "interfaces": [ "*" ]
+ },
+ "preferred-lifetime": 3000,
+ "rebind-timer": 2000,
+ "renew-timer": 1000,
+ "valid-lifetime": 4000,
+ "option-data": [
+ {
+ "always-send": true,
+ "code": 33,
+ "data": "normal_erouter_v6.cm",
+ "name": "config-file",
+ "space": "vendor-4491"
+ )";
+ if (configured_options.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 12,
+ "data": "first",
+ "name": "payload",
+ "space": "vendor-4491"
+ )";
+ }
+ if (!add_vendor_option) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "name": "vendor-opts",
+ "data": "4491",
+ "space": "dhcp6"
+ )";
+ }
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 33,
+ "data": "special_erouter_v6.cm",
+ "name": "custom",
+ "space": "vendor-3561"
+ )";
+ if (configured_options.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 12,
+ "data": "last",
+ "name": "special",
+ "space": "vendor-3561"
+ )";
+ }
+ if (!add_vendor_option) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "name": "vendor-opts",
+ "data": "3561",
+ "space": "dhcp6"
+ )";
+ }
+ }
+ config += R"(
+ }
+ ],
+ "option-def": [
+ {
+ "code": 12,
+ "name": "payload",
+ "space": "vendor-4491",
+ "type": "string"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 33,
+ "name": "custom",
+ "space": "vendor-3561",
+ "type": "string"
+ },
+ {
+ "code": 12,
+ "name": "special",
+ "space": "vendor-3561",
+ "type": "string"
+ )";
+ }
+ config += R"(
+ }
+ ],
+ "subnet6": [
+ {
+ "interface": "eth0",
+ "pools": [
+ {
+ "pool": "2001:db8:1::/64"
+ }
+ ],
+ "subnet": "2001:db8:1::/48",
+ "id": 1,
+ "interface-id": ""
+ }
+ ]
+ }
+ )";
+
+ ASSERT_NO_THROW(configure(config));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ if (add_vendor_option) {
+ for (uint32_t vendor_id : requested_vendor_ids) {
+ // Let's add a vendor-option (vendor-id=4491).
+ OptionVendorPtr vendor(new OptionVendor(Option::V6, vendor_id));
+
+ sol->addOption(vendor);
+ }
+ }
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_.processSolicit(ctx);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check if there is a vendor option response
+ OptionCollection tmp = adv->getOptions(D6O_VENDOR_OPTS);
+ ASSERT_EQ(tmp.size(), result_vendor_ids.size());
+
+ for (auto const& opt : tmp) {
+ // The response should be an OptionVendor.
+ OptionVendorPtr vendor_resp;
+
+ for (uint32_t vendor_id : result_vendor_ids) {
+ vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ ASSERT_TRUE(vendor_resp);
+ if (vendor_resp->getVendorId() == vendor_id) {
+ break;
+ }
+ }
+ ASSERT_TRUE(vendor_resp);
+
+ for (uint16_t option : configured_options) {
+ if (add_vendor_option &&
+ requested_vendor_ids.find(vendor_resp->getVendorId()) == requested_vendor_ids.end()) {
+ // If explicitly sending OptionVendor and the vendor is not
+ // configured, options should not be present.
+ if (option == DOCSIS3_V6_CONFIG_FILE) {
+ // Option 33 should not be present.
+ OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
+ ASSERT_FALSE(docsis33);
+ }
+ if (option == 12) {
+ // Option 12 should not be present.
+ OptionPtr custom = vendor_resp->getOption(12);
+ ASSERT_FALSE(custom);
+ }
+ } else {
+ if (option == DOCSIS3_V6_CONFIG_FILE) {
+ // Option 33 should be present.
+ OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
+ ASSERT_TRUE(docsis33);
+
+ OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+ ASSERT_TRUE(config_file);
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+ } else {
+ EXPECT_EQ("special_erouter_v6.cm", config_file->getValue());
+ }
+ }
+
+ if (option == 12) {
+ // Option 12 should be present.
+ OptionPtr custom = vendor_resp->getOption(12);
+ ASSERT_TRUE(custom);
+
+ // It should be an OptionString.
+ // The option is serialized as Option so it needs to be converted to
+ // OptionString.
+ auto const& buffer = custom->toBinary();
+ OptionStringPtr tag(new OptionString(Option::V6, 12,
+ buffer.begin(), buffer.end()));
+ ASSERT_TRUE(tag);
+
+ // Check that the provided value match the ones in configuration.
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ EXPECT_EQ(tag->getValue(), "first");
+ } else {
+ EXPECT_EQ(tag->getValue(), "last");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /// @brief Checks if vendor options are parsed correctly and the persistent
+ /// options are actually assigned. Also covers negative tests that options
+ /// are not provided when a different vendor ID is given.
+ ///
+ /// @param configured_vendor_ids The vendor IDs that are configured in the
+ /// server: 4491 or both 4491 and 3561.
+ /// @param requested_vendor_ids Then vendor IDs that are present in ORO.
+ /// @param requested_options The requested options in ORO.
+ /// @param configured_options The configured options. The suboption 22 has
+ /// always send flag set to true so it will always be sent.
+ void testVendorOptionsOROAndPersistent(std::set<uint32_t> configured_vendor_ids,
+ std::set<uint32_t> requested_vendor_ids,
+ std::set<uint16_t> requested_options,
+ std::set<uint16_t> configured_options) {
+ std::set<uint32_t> result_vendor_ids;
+ ASSERT_FALSE(configured_vendor_ids.empty());
+ ASSERT_TRUE(configured_vendor_ids.find(VENDOR_ID_CABLE_LABS) != configured_vendor_ids.end());
+ result_vendor_ids = configured_vendor_ids;
+ ASSERT_FALSE(configured_options.empty());
+ ASSERT_TRUE(configured_options.find(DOCSIS3_V6_CONFIG_FILE) != configured_options.end());
+ // Create a config with custom options.
+ string config = R"(
+ {
+ "interfaces-config": {
+ "interfaces": [ "*" ]
+ },
+ "preferred-lifetime": 3000,
+ "rebind-timer": 2000,
+ "renew-timer": 1000,
+ "valid-lifetime": 4000,
+ "option-data": [
+ {
+ "code": 33,
+ "data": "normal_erouter_v6.cm",
+ "name": "config-file",
+ "space": "vendor-4491"
+ )";
+ if (configured_options.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 12,
+ "data": "first",
+ "name": "payload",
+ "space": "vendor-4491"
+ )";
+ }
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "name": "vendor-opts",
+ "data": "4491",
+ "space": "dhcp6"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 33,
+ "data": "special_erouter_v6.cm",
+ "name": "custom",
+ "space": "vendor-3561"
+ )";
+ if (configured_options.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 12,
+ "data": "last",
+ "name": "special",
+ "space": "vendor-3561"
+ )";
+ }
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "name": "vendor-opts",
+ "data": "3561",
+ "space": "dhcp6"
+ )";
+ }
+ config += R"(
+ }
+ ],
+ "option-def": [
+ {
+ "code": 12,
+ "name": "payload",
+ "space": "vendor-4491",
+ "type": "string"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 33,
+ "name": "custom",
+ "space": "vendor-3561",
+ "type": "string"
+ },
+ {
+ "code": 12,
+ "name": "special",
+ "space": "vendor-3561",
+ "type": "string"
+ )";
+ }
+ config += R"(
+ }
+ ],
+ "subnet6": [
+ {
+ "interface": "eth0",
+ "pools": [
+ {
+ "pool": "2001:db8:1::/64"
+ }
+ ],
+ "subnet": "2001:db8:1::/48",
+ "id": 1,
+ "interface-id": ""
+ }
+ ]
+ }
+ )";
+
+ ASSERT_NO_THROW(configure(config));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Let's add a vendor-option (vendor-id=4491) with a single sub-option.
+ // That suboption has code 1 and is a docsis ORO option.
+ OptionUint16ArrayPtr vendor_oro(new OptionUint16Array(Option::V6, DOCSIS3_V6_ORO));
+ for (uint16_t option : requested_options) {
+ vendor_oro->addValue(option);
+ }
+
+ for (uint32_t vendor_id : requested_vendor_ids) {
+ OptionVendorPtr vendor(new OptionVendor(Option::V6, vendor_id));
+ vendor->addOption(vendor_oro);
+ sol->addOption(vendor);
+ }
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_.processSolicit(ctx);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check if there is a vendor option response
+ OptionCollection tmp = adv->getOptions(D6O_VENDOR_OPTS);
+ ASSERT_EQ(tmp.size(), result_vendor_ids.size());
+
+ for (auto const& opt : tmp) {
+ // The response should be an OptionVendor.
+ OptionVendorPtr vendor_resp;
+
+ for (uint32_t vendor_id : result_vendor_ids) {
+ vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ ASSERT_TRUE(vendor_resp);
+ if (vendor_resp->getVendorId() == vendor_id) {
+ break;
+ }
+ }
+ ASSERT_TRUE(vendor_resp);
+
+ for (uint16_t option : configured_options) {
+ if (option == DOCSIS3_V6_CONFIG_FILE) {
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS &&
+ requested_options.find(option) != requested_options.end() &&
+ requested_vendor_ids.find(vendor_resp->getVendorId()) != requested_vendor_ids.end()) {
+ // Option 33 should be present.
+ OptionPtr docsis33 = vendor_resp->getOption(DOCSIS3_V6_CONFIG_FILE);
+ ASSERT_TRUE(docsis33);
+
+ OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+ ASSERT_TRUE(config_file);
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+ } else {
+ EXPECT_EQ("special_erouter_v6.cm", config_file->getValue());
+ }
+ } else {
+ // If explicitly sending OptionVendor and the vendor is not
+ // VENDOR_ID_CABLE_LABS, or the option is not explicitly
+ // requested, options should not be present. Kea only knows
+ // how to process VENDOR_ID_CABLE_LABS DOCSIS3_V4_ORO
+ // (suboption 1).
+ // Option 2 should not be present.
+ OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_FALSE(docsis2);
+ }
+ }
+
+ if (option == 12) {
+ // Option 12 should be present.
+ OptionPtr custom = vendor_resp->getOption(12);
+ ASSERT_TRUE(custom);
+
+ // It should be an OptionString.
+ // The option is serialized as Option so it needs to be converted to
+ // OptionString.
+ auto const& buffer = custom->toBinary();
+ OptionStringPtr tag(new OptionString(Option::V6, 12,
+ buffer.begin(), buffer.end()));
+ ASSERT_TRUE(tag);
+
+ // Check that the provided value match the ones in configuration.
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ EXPECT_EQ(tag->getValue(), "first");
+ } else {
+ EXPECT_EQ(tag->getValue(), "last");
+ }
+ }
+ }
+ }
+ }
+
+ /// @brief Test what options a client can use to request vendor options.
+ void testRequestingOfVendorOptions(vector<int16_t> const& client_options) {
+ Dhcp6Client client;
+
+ EXPECT_NO_THROW(configure(config_, *client.getServer()));
+
+ bool should_yield_response(false);
+ for (int16_t i : client_options) {
+ OptionPtr vendor_option;
+
+ if (i == D6O_USER_CLASS) {
+ // An option that should not trigger a response containing
+ // vendor options.
+ vendor_option = boost::make_shared<OptionString>(Option::V6,
+ D6O_USER_CLASS,
+ "hello");
+ } else if (i == D6O_VENDOR_CLASS) {
+ vendor_option =
+ boost::make_shared<OptionVendorClass>(Option::V6,
+ vendor_id_);
+ should_yield_response = true;
+ } else if (i == D6O_VENDOR_OPTS) {
+ vendor_option.reset(new OptionVendor(Option::V6, vendor_id_));
+ should_yield_response = true;
+ } else {
+ continue;
+ }
+ client.addExtraOption(vendor_option);
+ }
+
+ // Let's check whether the server is able to process this packet
+ // and include the appropriate options.
+ EXPECT_NO_THROW(client.doSolicit());
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check that there is a response if an option was properly requested.
+ // Otherwise check that a response has not been provided and stop here.
+ OptionPtr response(
+ client.getContext().response_->getOption(D6O_VENDOR_OPTS));
+ if (should_yield_response) {
+ ASSERT_TRUE(response);
+ } else {
+ ASSERT_FALSE(response);
+ return;
+ }
+
+ // Check that it includes vendor opts with the right vendor ID.
+ OptionVendorPtr response_vendor_options(
+ boost::dynamic_pointer_cast<OptionVendor>(response));
+ ASSERT_TRUE(response_vendor_options);
+ EXPECT_EQ(vendor_id_, response_vendor_options->getVendorId());
+
+ // Check that it contains requested option with the appropriate content.
+ OptionPtr suboption(response_vendor_options->getOption(option_));
+ ASSERT_TRUE(suboption);
+ vector<uint8_t> binary_suboption = suboption->toBinary(false);
+ string text(binary_suboption.begin(), binary_suboption.end());
+ EXPECT_EQ("2001:db8::1234:5678", text);
+ }
+
+private:
+ /// @brief Configured option data
+ string data_ = "2001:db8::1234:5678";
+
+ /// @brief Configured option code
+ int32_t option_ = 32;
+
+ /// @brief Configured and requested vendor ID
+ uint32_t vendor_id_ = 32768;
+
+ /// @brief Server configuration
+ string config_ = R"(
+ {
+ "option-data": [
+ {
+ "always-send": true,
+ "code": )" + to_string(option_) + R"(,
+ "data": ")" + data_ + R"(",
+ "name": "tftp-address",
+ "space": "vendor-)" + to_string(vendor_id_) + R"("
+ }
+ ],
+ "option-def": [
+ {
+ "code": )" + to_string(option_) + R"(,
+ "name": "tftp-address",
+ "space": "vendor-)" + to_string(vendor_id_) + R"(",
+ "type": "string"
+ }
+ ],
+ "subnet6": [
+ {
+ "interface": "eth0",
+ "pools": [
+ {
+ "pool": "2001:db8::/64"
+ }
+ ],
+ "subnet": "2001:db8::/64",
+ "id": 1
+ }
+ ]
+ }
+ )";
+
+ // @brief Test configuration for IfaceMgr.
+ std::unique_ptr<IfaceMgrTestConfig> iface_mgr_test_config_;
+};
+
+TEST_F(VendorOptsTest, dontRequestVendorID) {
+ testRequestingOfVendorOptions({});
+}
+
+TEST_F(VendorOptsTest, negativeTestRequestVendorIDWithOption15) {
+ testRequestingOfVendorOptions({ D6O_USER_CLASS });
+}
+
+TEST_F(VendorOptsTest, requestVendorIDWithOption16) {
+ testRequestingOfVendorOptions({ D6O_VENDOR_CLASS });
+}
+
+TEST_F(VendorOptsTest, requestVendorIDWithOption17) {
+ testRequestingOfVendorOptions({ D6O_VENDOR_OPTS });
+}
+
+TEST_F(VendorOptsTest, requestVendorIDWithOptions16And17) {
+ testRequestingOfVendorOptions({ D6O_VENDOR_CLASS, D6O_VENDOR_OPTS });
+}
+
+TEST_F(VendorOptsTest, requestVendorIDWithOptions17And16) {
+ testRequestingOfVendorOptions({ D6O_VENDOR_OPTS, D6O_VENDOR_CLASS });
+}
+
+// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
+TEST_F(VendorOptsTest, docsisVendorOptionsParse) {
+
+ // Let's get a traffic capture from DOCSIS3.0 modem
+ Pkt6Ptr sol = PktCaptures::captureDocsisRelayedSolicit();
+ EXPECT_NO_THROW(sol->unpack());
+
+ // Check if the packet contain
+ OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(opt);
+
+ boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+ ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
+
+ EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_ORO));
+ EXPECT_TRUE(vendor->getOption(36));
+ EXPECT_TRUE(vendor->getOption(35));
+ EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_DEVICE_TYPE));
+ EXPECT_TRUE(vendor->getOption(3));
+ EXPECT_TRUE(vendor->getOption(4));
+ EXPECT_TRUE(vendor->getOption(5));
+ EXPECT_TRUE(vendor->getOption(6));
+ EXPECT_TRUE(vendor->getOption(7));
+ EXPECT_TRUE(vendor->getOption(8));
+ EXPECT_TRUE(vendor->getOption(9));
+ EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_VENDOR_NAME));
+ EXPECT_TRUE(vendor->getOption(15));
+
+ EXPECT_FALSE(vendor->getOption(20));
+ EXPECT_FALSE(vendor->getOption(11));
+ EXPECT_FALSE(vendor->getOption(17));
+}
+
+// Checks if server is able to parse incoming docsis option and extract suboption 1 (docsis ORO)
+TEST_F(VendorOptsTest, docsisVendorORO) {
+
+ NakedDhcpv6Srv srv(0);
+
+ // Let's get a traffic capture from DOCSIS3.0 modem
+ Pkt6Ptr sol = PktCaptures::captureDocsisRelayedSolicit();
+ ASSERT_NO_THROW(sol->unpack());
+
+ // Check if the packet contains vendor specific information option
+ OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(opt);
+
+ boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+ ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
+
+ opt = vendor->getOption(DOCSIS3_V6_ORO);
+ ASSERT_TRUE(opt);
+
+ OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<OptionUint16Array>(opt);
+ EXPECT_TRUE(oro);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOption) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptions) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionMultipleVendorsMatchOne) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionsMultipleVendorsMatchOne) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionMultipleVendorsMatchAll) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionsMultipleVendorsMatchAll) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionDifferentVendorID) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS },
+ { 32768 },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionsDifferentVendorID) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS },
+ { 32768 },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionDifferentVendorIDMultipleVendorsMatchNone) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS, 3561 },
+ { 32768, 16384 },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionDifferentVendorIDMultipleVendorsMatchNone) {
+ testVendorOptionsORO({ VENDOR_ID_CABLE_LABS, 3561 },
+ { 32768, 16384 },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ false);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE, 12 },
+ false);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchOne) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ false);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchOne) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE, 12 },
+ false);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchAll) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ false);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAll) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE, 12 },
+ false);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionAddVendorOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ true);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionAddVendorOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE, 12 },
+ true);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchOneAddVendorOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ true);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchOneAddVendorOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE, 12 },
+ true);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchAllAddVendorOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ true);
+}
+
+// This test checks vendor options are parsed correctly and the persistent
+// options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAllAddVendorOption) {
+ testVendorOptionsPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE, 12 },
+ true);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOption) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptions) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionMultipleVendorsMatchOne) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionsMultipleVendorsMatchOne) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionMultipleVendorsMatchAll) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionsMultipleVendorsMatchAll) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionDifferentVendorID) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { 32768 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionsDifferentVendorID) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { 32768 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionDifferentVendorIDMultipleVendorsMatchNone) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { 32768, 16384 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionDifferentVendorIDMultipleVendorsMatchNone) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { 32768, 16384 },
+ { DOCSIS3_V6_CONFIG_FILE },
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionsNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { VENDOR_ID_CABLE_LABS },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionMultipleVendorsMatchOneNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionsMultipleVendorsMatchOneNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionMultipleVendorsMatchAllNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionsMultipleVendorsMatchAllNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { VENDOR_ID_CABLE_LABS, 3561 },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionDifferentVendorIDNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { 32768 },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionsDifferentVendorIDNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS },
+ { 32768 },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentOneOptionDifferentVendorIDMultipleVendorsMatchNoneNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { 32768, 16384 },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE });
+}
+
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROAndPersistentMultipleOptionDifferentVendorIDMultipleVendorsMatchNoneNoneRequested) {
+ testVendorOptionsOROAndPersistent({ VENDOR_ID_CABLE_LABS, 3561 },
+ { 32768, 16384 },
+ {},
+ { DOCSIS3_V6_CONFIG_FILE, 12 });
+}
+
+// This test checks if cancellation (aka never-send) flag unconditionally
+// makes the server to never add the specified option.
+TEST_F(VendorOptsTest, vendorNeverSend) {
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ " \"option-def\": [ {"
+ " \"name\": \"config-file\","
+ " \"code\": 33,"
+ " \"type\": \"string\","
+ " \"space\": \"vendor-4491\""
+ " },"
+ " {"
+ " \"name\": \"vendor-name\","
+ " \"code\": 10,"
+ " \"type\": \"string\","
+ " \"space\": \"vendor-4491\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"normal_erouter_v6.cm\","
+ " \"always-send\": true"
+ " },"
+ " {"
+ " \"name\": \"vendor-name\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"ISC\""
+ " }],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"renew-timer\": 1000, "
+ " \"rebind-timer\": 1000, "
+ " \"preferred-lifetime\": 3000,"
+ " \"valid-lifetime\": 4000,"
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\","
+ " \"option-data\": [ {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"never-send\": true"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface("eth0");
+ sol->setIndex(ETH0_INDEX);
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Let's add a vendor-option (vendor-id=4491).
+ OptionPtr vendor(new OptionVendor(Option::V6, 4491));
+ sol->addOption(vendor);
+
+ // Pass it to the server and get an advertise
+ AllocEngine::ClientContext6 ctx;
+ bool drop = !srv_.earlyGHRLookup(sol, ctx);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx, drop);
+ ASSERT_FALSE(drop);
+ Pkt6Ptr adv = srv_.processSolicit(ctx);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // There is no vendor option response.
+ EXPECT_FALSE(adv->getOption(D6O_VENDOR_OPTS));
+
+ // Add again an ORO but requesting both 10 and 33.
+ sol->delOption(D6O_VENDOR_OPTS);
+ boost::shared_ptr<OptionUint16Array> vendor_oro2(new OptionUint16Array(Option::V6,
+ DOCSIS3_V6_ORO));
+ vendor_oro2->addValue(DOCSIS3_V6_VENDOR_NAME); // Request option 10
+ vendor_oro2->addValue(DOCSIS3_V6_CONFIG_FILE); // Request option 33
+ OptionPtr vendor3(new OptionVendor(Option::V6, 4491));
+ vendor3->addOption(vendor_oro2);
+ sol->addOption(vendor3);
+
+ // Need to process SOLICIT again after requesting new option.
+ AllocEngine::ClientContext6 ctx3;
+ drop = !srv_.earlyGHRLookup(sol, ctx3);
+ ASSERT_FALSE(drop);
+ srv_.initContext(sol, ctx3, drop);
+ ASSERT_FALSE(drop);
+ adv = srv_.processSolicit(ctx3);
+ ASSERT_TRUE(adv);
+
+ // Check if there is vendor option response
+ OptionPtr tmp = adv->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(tmp);
+
+ // The response should be OptionVendor object
+ boost::shared_ptr<OptionVendor> vendor_resp =
+ boost::dynamic_pointer_cast<OptionVendor>(tmp);
+ ASSERT_TRUE(vendor_resp);
+
+ // Still no config-file (33) option.
+ EXPECT_FALSE(vendor_resp->getOption(33));
+
+ // But the vendor option response is not empty.
+ const OptionCollection& opts = vendor_resp->getOptions();
+ ASSERT_EQ(1, opts.size());
+ EXPECT_TRUE(vendor_resp->getOption(10));
+}
+
+// Test checks whether it is possible to use option definitions defined in
+// src/lib/dhcp/docsis3_option_defs.h.
+TEST_F(VendorOptsTest, vendorOptionsDocsisDefinitions) {
+ ConstElementPtr x;
+ string config_prefix = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ " \"option-data\": [ {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": ";
+ string config_postfix = ","
+ " \"data\": \"normal_erouter_v6.cm\","
+ " \"csv-format\": true"
+ " }],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"renew-timer\": 1000, "
+ " \"rebind-timer\": 1000, "
+ " \"preferred-lifetime\": 3000,"
+ " \"valid-lifetime\": 4000,"
+ " \"interface-id\": \"\","
+ " \"interface\": \"\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // There is docsis3 (vendor-id=4491) vendor option 33, which is a
+ // config-file. Its format is a single string.
+ string config_valid = config_prefix + "33" + config_postfix;
+
+ // There is no option 99 defined in vendor-id=4491. As there is no
+ // definition, the config should fail.
+ string config_bogus = config_prefix + "99" + config_postfix;
+
+ ConstElementPtr json_bogus;
+ ASSERT_NO_THROW(json_bogus = parseDHCP6(config_bogus));
+ ConstElementPtr json_valid;
+ ASSERT_NO_THROW(json_valid = parseDHCP6(config_valid));
+
+ NakedDhcpv6Srv srv(0);
+
+ // This should fail (missing option definition)
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json_bogus));
+ ASSERT_TRUE(x);
+ comment_ = isc::config::parseAnswer(rcode_, x);
+ ASSERT_EQ(1, rcode_);
+
+ // This should work (option definition present)
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json_valid));
+ ASSERT_TRUE(x);
+ comment_ = isc::config::parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+}
+
+// This test checks that the server will handle a Solicit with the Vendor Class
+// having a length of 4 (enterprise-id only).
+TEST_F(VendorOptsTest, cableLabsShortVendorClass) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create a simple Solicit with the 4-byte long vendor class option.
+ Pkt6Ptr sol = PktCaptures::captureCableLabsShortVendorClass();
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ srv.run();
+
+ // Get Advertise...
+ ASSERT_FALSE(srv.fake_sent_.empty());
+ Pkt6Ptr adv = srv.fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // This is sent back to client, so port is 546
+ EXPECT_EQ(DHCP6_CLIENT_PORT, adv->getRemotePort());
+}
+
+// Checks that it's possible to have a vendor opts (17) option in the response
+// only. Once specific client (Genexis) sends only vendor-class info and
+// expects the server to include vendor opts in the response.
+TEST_F(VendorOptsTest, vendorOpsInResponseOnly) {
+ Dhcp6Client client;
+
+ // The config defines custom vendor (17) suboption 2 that conveys
+ // a TFTP URL. The client doesn't send vendor class (16) or
+ // vendor opts (17) option, so normal vendor option processing is
+ // impossible. However, since there's a class defined that matches
+ // client's packets and that class inserts a vendor opts in the
+ // response, Kea should be able to figure out the vendor-id and
+ // then also insert the suboption 2 with the TFTP URL.
+ string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ " },"
+ " \"option-def\": ["
+ " {"
+ " \"name\": \"tftp\","
+ " \"code\": 2,"
+ " \"space\": \"vendor-25167\","
+ " \"type\": \"string\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"cpe_genexis\","
+ " \"test\": \"substring(option[15].hex,0,7) == 'HMC1000'\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"vendor-opts\","
+ " \"data\": \"25167\""
+ " },"
+ " {"
+ " \"name\": \"tftp\","
+ " \"space\": \"vendor-25167\","
+ " \"data\": \"tftp://192.0.2.1/genexis/HMC1000.v1.3.0-R.img\","
+ " \"always-send\": true"
+ " } ]"
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/64\", "
+ " \"interface\": \"eth0\" "
+ " } ]"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // A vendor-class identifier (this matches what Genexis hardware sends)
+ OptionPtr vopt(new OptionString(Option::V6, D6O_USER_CLASS,
+ "HMC1000.v1.3.0-R,Element-P1090,genexis.eu"));
+ client.addExtraOption(vopt);
+ client.requestOption(D6O_VENDOR_OPTS);
+
+ // Let's check whether the server is not able to process this packet
+ // and include vivso with appropriate sub-options
+ EXPECT_NO_THROW(client.doSolicit());
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check whether there's a response.
+ OptionPtr rsp = client.getContext().response_->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(rsp);
+
+ // Check that it includes vendor opts with vendor-id = 25167
+ OptionVendorPtr rsp_vopts = boost::dynamic_pointer_cast<OptionVendor>(rsp);
+ ASSERT_TRUE(rsp_vopts);
+ EXPECT_EQ(25167, rsp_vopts->getVendorId());
+
+ // Now check that it contains suboption 2 with appropriate content.
+ OptionPtr subopt2 = rsp_vopts->getOption(2);
+ ASSERT_TRUE(subopt2);
+ vector<uint8_t> subopt2bin = subopt2->toBinary(false);
+ string txt(subopt2bin.begin(), subopt2bin.end());
+ EXPECT_EQ("tftp://192.0.2.1/genexis/HMC1000.v1.3.0-R.img", txt);
+
+ // Check the config was not altered by unwanted side effect
+ // on the vendor option.
+
+ // Get class config:
+ ClientClassDefPtr cdef = CfgMgr::instance().getCurrentCfg()->
+ getClientClassDictionary()->findClass("cpe_genexis");
+ ASSERT_TRUE(cdef);
+ OptionDescriptor cdesc = cdef->getCfgOption()->
+ get(DHCP6_OPTION_SPACE, D6O_VENDOR_OPTS);
+ ASSERT_TRUE(cdesc.option_);
+ // If the config was altered these two EXPECT will fail.
+ EXPECT_TRUE(cdesc.option_->getOptions().empty());
+ EXPECT_FALSE(cdesc.option_->getOption(2));
+}
+
+// Checks if it's possible to have 2 vendor-class options and 2 vendor-opts
+// options with different vendor IDs.
+TEST_F(VendorOptsTest, twoVendors) {
+ Dhcp6Client client;
+
+ // The config defines 2 vendors with for each a vendor-class option,
+ // a vendor-opts option and a custom vendor suboption, all having
+ // the always send flag set to true.
+ // The encoding for the option-class option is a bit hairy: first is
+ // the vendor id (uint32) and the remaining is a binary which stands
+ // for tuples so length (uint16) x value.
+ string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ " },"
+ " \"option-def\": ["
+ " {"
+ " \"name\": \"foo\","
+ " \"code\": 123,"
+ " \"space\": \"vendor-1234\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"bar\","
+ " \"code\": 456,"
+ " \"space\": \"vendor-5678\","
+ " \"type\": \"string\""
+ " }"
+ " ],"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"vendor-class\","
+ " \"always-send\": true,"
+ " \"data\": \"1234, 0003666f6f\""
+ " },"
+ " {"
+ " \"name\": \"vendor-class\","
+ " \"always-send\": true,"
+ " \"data\": \"5678, 0003626172\""
+ " },"
+ " {"
+ " \"name\": \"vendor-opts\","
+ " \"always-send\": true,"
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"vendor-opts\","
+ " \"always-send\": true,"
+ " \"data\": \"5678\""
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"always-send\": true,"
+ " \"space\": \"vendor-1234\","
+ " \"data\": \"foo\""
+ " },"
+ " {"
+ " \"name\": \"bar\","
+ " \"always-send\": true,"
+ " \"space\": \"vendor-5678\","
+ " \"data\": \"bar\""
+ " }"
+ " ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/64\", "
+ " \"interface\": \"eth0\" "
+ " } ]"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Let's check whether the server is not able to process this packet.
+ EXPECT_NO_THROW(client.doSolicit());
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check whether there are vendor-class options.
+ const OptionCollection& classes =
+ client.getContext().response_->getOptions(D6O_VENDOR_CLASS);
+ ASSERT_EQ(2, classes.size());
+ OptionVendorClassPtr opt_class1234;
+ OptionVendorClassPtr opt_class5678;
+ for (auto opt : classes) {
+ ASSERT_EQ(D6O_VENDOR_CLASS, opt.first);
+ OptionVendorClassPtr opt_class =
+ boost::dynamic_pointer_cast<OptionVendorClass>(opt.second);
+ ASSERT_TRUE(opt_class);
+ uint32_t vendor_id = opt_class->getVendorId();
+ if (vendor_id == 1234) {
+ ASSERT_FALSE(opt_class1234);
+ opt_class1234 = opt_class;
+ continue;
+ }
+ ASSERT_EQ(5678, vendor_id);
+ ASSERT_FALSE(opt_class5678);
+ opt_class5678 = opt_class;
+ }
+
+ // Verify first vendor-class option.
+ ASSERT_TRUE(opt_class1234);
+ ASSERT_EQ(1, opt_class1234->getTuplesNum());
+ EXPECT_EQ("foo", opt_class1234->getTuple(0).getText());
+
+ // Verify second vendor-class option.
+ ASSERT_TRUE(opt_class5678);
+ ASSERT_EQ(1, opt_class5678->getTuplesNum());
+ EXPECT_EQ("bar", opt_class5678->getTuple(0).getText());
+
+ // Check whether there are vendor-opts options.
+ const OptionCollection& options =
+ client.getContext().response_->getOptions(D6O_VENDOR_OPTS);
+ ASSERT_EQ(2, options.size());
+ OptionVendorPtr opt_opts1234;
+ OptionVendorPtr opt_opts5678;
+ for (auto opt : options) {
+ ASSERT_EQ(D6O_VENDOR_OPTS, opt.first);
+ OptionVendorPtr opt_opts =
+ boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ ASSERT_TRUE(opt_opts);
+ uint32_t vendor_id = opt_opts->getVendorId();
+ if (vendor_id == 1234) {
+ ASSERT_FALSE(opt_opts1234);
+ opt_opts1234 = opt_opts;
+ continue;
+ }
+ ASSERT_EQ(5678, vendor_id);
+ ASSERT_FALSE(opt_opts5678);
+ opt_opts5678 = opt_opts;
+ }
+
+ // Verify first vendor-opts option.
+ ASSERT_TRUE(opt_opts1234);
+ OptionCollection subs1234 = opt_opts1234->getOptions();
+ ASSERT_EQ(1, subs1234.size());
+ OptionPtr sub1234 = subs1234.begin()->second;
+ ASSERT_TRUE(sub1234);
+ EXPECT_EQ(123, sub1234->getType());
+ OptionStringPtr opt_foo =
+ boost::dynamic_pointer_cast<OptionString>(sub1234);
+ ASSERT_TRUE(opt_foo);
+ EXPECT_EQ("foo", opt_foo->getValue());
+
+ // Verify second vendor-opts option.
+ ASSERT_TRUE(opt_opts5678);
+ OptionCollection subs5678 = opt_opts5678->getOptions();
+ ASSERT_EQ(1, subs5678.size());
+ OptionPtr sub5678 = subs5678.begin()->second;
+ ASSERT_TRUE(sub5678);
+ EXPECT_EQ(456, sub5678->getType());
+ OptionStringPtr opt_bar =
+ boost::dynamic_pointer_cast<OptionString>(sub5678);
+ ASSERT_TRUE(opt_bar);
+ EXPECT_EQ("bar", opt_bar->getValue());
+}
+
+// Checks if it's possible to have 3 vendor-opts options with
+// different vendor IDs selected using the 3 ways (vendor-opts in
+// response, vendor-opts in query and vendor-class in query).
+TEST_F(VendorOptsTest, threeVendors) {
+ Dhcp6Client client;
+
+ // The config defines 2 vendors with for each a vendor-opts option
+ // and a custom vendor suboption, and a suboption for DOCSIS.
+ string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ ]"
+ " },"
+ " \"option-def\": ["
+ " {"
+ " \"name\": \"foo\","
+ " \"code\": 123,"
+ " \"space\": \"vendor-1234\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"bar\","
+ " \"code\": 456,"
+ " \"space\": \"vendor-5678\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"config-file\","
+ " \"code\": 33,"
+ " \"space\": \"vendor-4491\","
+ " \"type\": \"string\""
+ " }"
+ " ],"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"vendor-opts\","
+ " \"always-send\": true,"
+ " \"data\": \"1234\""
+ " },"
+ " {"
+ " \"name\": \"vendor-opts\","
+ " \"data\": \"5678\""
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"always-send\": true,"
+ " \"space\": \"vendor-1234\","
+ " \"data\": \"foo\""
+ " },"
+ " {"
+ " \"name\": \"bar\","
+ " \"always-send\": true,"
+ " \"space\": \"vendor-5678\","
+ " \"data\": \"bar\""
+ " },"
+ " {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"data\": \"normal_erouter_v6.cm\""
+ " }"
+ " ],"
+ "\"subnet6\": [ { "
+ " \"id\": 1, "
+ " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+ " \"subnet\": \"2001:db8::/64\", "
+ " \"interface\": \"eth0\" "
+ " } ]"
+ "}";
+
+ EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+ // Add a vendor-class for vendor id 5678.
+ OptionVendorClassPtr cclass(new OptionVendorClass(Option::V6, 5678));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "bar";
+ cclass->addTuple(tuple);
+ client.addExtraOption(cclass);
+
+ // Add a DOCSIS vendor-opts with an ORO.
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, DOCSIS3_V6_ORO));
+ oro->addValue(DOCSIS3_V6_CONFIG_FILE); // Request option 33.
+ OptionVendorPtr vendor(new OptionVendor(Option::V6, VENDOR_ID_CABLE_LABS));
+ vendor->addOption(oro);
+ client.addExtraOption(vendor);
+
+ // Let's check whether the server is not able to process this packet.
+ EXPECT_NO_THROW(client.doSolicit());
+ ASSERT_TRUE(client.getContext().response_);
+
+ // Check whether there are vendor-opts options.
+ const OptionCollection& options =
+ client.getContext().response_->getOptions(D6O_VENDOR_OPTS);
+ ASSERT_EQ(3, options.size());
+ OptionVendorPtr opt_opts1234;
+ OptionVendorPtr opt_docsis;
+ OptionVendorPtr opt_opts5678;
+ for (auto opt : options) {
+ ASSERT_EQ(D6O_VENDOR_OPTS, opt.first);
+ OptionVendorPtr opt_opts =
+ boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ ASSERT_TRUE(opt_opts);
+ uint32_t vendor_id = opt_opts->getVendorId();
+ if (vendor_id == 1234) {
+ ASSERT_FALSE(opt_opts1234);
+ opt_opts1234 = opt_opts;
+ continue;
+ }
+ if (vendor_id == VENDOR_ID_CABLE_LABS) {
+ ASSERT_FALSE(opt_docsis);
+ opt_docsis = opt_opts;
+ continue;
+ }
+ ASSERT_EQ(5678, vendor_id);
+ ASSERT_FALSE(opt_opts5678);
+ opt_opts5678 = opt_opts;
+ }
+
+ // Verify first vendor-opts option.
+ ASSERT_TRUE(opt_opts1234);
+ OptionCollection subs1234 = opt_opts1234->getOptions();
+ ASSERT_EQ(1, subs1234.size());
+ OptionPtr sub1234 = subs1234.begin()->second;
+ ASSERT_TRUE(sub1234);
+ EXPECT_EQ(123, sub1234->getType());
+ OptionStringPtr opt_foo =
+ boost::dynamic_pointer_cast<OptionString>(sub1234);
+ ASSERT_TRUE(opt_foo);
+ EXPECT_EQ("foo", opt_foo->getValue());
+
+ // Verify DOCSIS vendor-opts option.
+ ASSERT_TRUE(opt_docsis);
+ OptionCollection subs_docsis = opt_docsis->getOptions();
+ ASSERT_EQ(1, subs_docsis.size());
+ OptionPtr cfile = subs_docsis.begin()->second;
+ ASSERT_TRUE(cfile);
+ EXPECT_EQ(33, cfile->getType());
+ OptionStringPtr cfile_str = boost::dynamic_pointer_cast<OptionString>(cfile);
+ ASSERT_TRUE(cfile_str);
+ EXPECT_EQ("normal_erouter_v6.cm", cfile_str->getValue());
+
+ // Verify last vendor-opts option.
+ ASSERT_TRUE(opt_opts5678);
+ OptionCollection subs5678 = opt_opts5678->getOptions();
+ ASSERT_EQ(1, subs5678.size());
+ OptionPtr sub5678 = subs5678.begin()->second;
+ ASSERT_TRUE(sub5678);
+ EXPECT_EQ(456, sub5678->getType());
+ OptionStringPtr opt_bar =
+ boost::dynamic_pointer_cast<OptionString>(sub5678);
+ ASSERT_TRUE(opt_bar);
+ EXPECT_EQ("bar", opt_bar->getValue());
+}
+
+}