diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/dhcp6/tests | |
parent | Initial commit. (diff) | |
download | isc-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 '')
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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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()); +} + +} |